From fcee4a98aed7a2acf24df310b08e08af54955375 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 21:53:04 +0900 Subject: [PATCH] docs(automation): establish governed control-plane spine --- .../workflows/pr-review-merge-scheduler.yml | 2 +- AGENTS.md | 2 +- CHANGELOG.md | 4 + CLAUDE.md | 9 +- PR_GOVERNANCE_AUDIT.md | 84 +++--- README.md | 25 +- docs/CWL-MASTER-CONTEXT.md | 8 +- docs/automation/ARCHITECTURE.md | 148 ++++++++++ docs/automation/DOCUMENTATION_COVERAGE.md | 95 +++++++ docs/automation/ERD.md | 179 ++++++++++++ docs/automation/INCIDENT_RUNBOOK.md | 117 ++++++++ docs/automation/OPERABILITY.md | 104 +++++++ docs/automation/PRD.md | 133 +++++++++ docs/automation/README.md | 77 ++++++ docs/automation/SECURITY.md | 177 ++++++++++++ docs/automation/TEST_STRATEGY.md | 109 ++++++++ docs/automation/THREAT_MODEL.md | 91 +++++++ docs/automation/TRACEABILITY.md | 129 +++++++++ docs/automation/TRD.md | 215 +++++++++++++++ docs/automation/UML.md | 170 ++++++++++++ ...riter-lease-and-read-only-fleet-auditor.md | 98 +++++++ .../0002-exact-head-and-live-base-binding.md | 92 +++++++ .../adr/0003-classified-bounded-retries.md | 92 +++++++ .../0004-minimal-reusable-workflow-secrets.md | 94 +++++++ .../adr/0005-independent-review-governance.md | 93 +++++++ ...0006-protected-main-operational-closure.md | 88 ++++++ .../adr/0007-work-conserving-automation.md | 94 +++++++ ...entral-control-plane-thin-leaf-contract.md | 95 +++++++ docs/automation/adr/README.md | 28 ++ .../review-agent-comment-invocation.md | 20 +- docs/nvidia-nim-opencode-hotfix.md | 28 +- docs/org-required-workflow-rollout.md | 29 +- docs/sbom/inventory.md | 7 +- docs/scorecard-governance.md | 6 +- .../test_automation_documentation_contract.py | 257 ++++++++++++++++++ 35 files changed, 2920 insertions(+), 79 deletions(-) create mode 100644 docs/automation/ARCHITECTURE.md create mode 100644 docs/automation/DOCUMENTATION_COVERAGE.md create mode 100644 docs/automation/ERD.md create mode 100644 docs/automation/INCIDENT_RUNBOOK.md create mode 100644 docs/automation/OPERABILITY.md create mode 100644 docs/automation/PRD.md create mode 100644 docs/automation/README.md create mode 100644 docs/automation/SECURITY.md create mode 100644 docs/automation/TEST_STRATEGY.md create mode 100644 docs/automation/THREAT_MODEL.md create mode 100644 docs/automation/TRACEABILITY.md create mode 100644 docs/automation/TRD.md create mode 100644 docs/automation/UML.md create mode 100644 docs/automation/adr/0001-writer-lease-and-read-only-fleet-auditor.md create mode 100644 docs/automation/adr/0002-exact-head-and-live-base-binding.md create mode 100644 docs/automation/adr/0003-classified-bounded-retries.md create mode 100644 docs/automation/adr/0004-minimal-reusable-workflow-secrets.md create mode 100644 docs/automation/adr/0005-independent-review-governance.md create mode 100644 docs/automation/adr/0006-protected-main-operational-closure.md create mode 100644 docs/automation/adr/0007-work-conserving-automation.md create mode 100644 docs/automation/adr/0008-central-control-plane-thin-leaf-contract.md create mode 100644 docs/automation/adr/README.md create mode 100644 tests/test_automation_documentation_contract.py diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index 8e1157060..8af66d06a 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -553,7 +553,7 @@ jobs: # published after the scheduler pass, required merge-preview checks landing # late, a base-branch policy blocker clearing) stays approved-but-unmerged # with no later trigger. This job re-runs the same trusted scheduler against - # every organization repository on an hourly heartbeat so each such PR is + # every organization repository on a fifteen-minute heartbeat so each such PR is # merged, branch-updated, or leaves a concrete per-PR blocker reason in this # log. It never bypasses policy: all mutations go through the same guarded # scheduler contract as the per-repository runs. diff --git a/AGENTS.md b/AGENTS.md index 688b33035..529d47215 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ # AGENTS.md — ContextualWisdomLab .github -> **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. +> **Agents: read the master context FIRST.** Before any work, read [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission · naruon-as-platform + inter-component UML · cross-cutting disciplines · conventions · roadmap · current state), the automation control-plane spine at [`docs/automation/README.md`](docs/automation/README.md), the live **GitHub Project #1** (work/roadmap source of truth), the full spec **ContextualWisdomLab/naruon#974**, and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not any private agent memory — is the source of truth. diff --git a/CHANGELOG.md b/CHANGELOG.md index bf30091dd..c0eadd296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Semantic Versioning where the repository publishes a release. ### Added +- Added the machine-checked automation control-plane documentation spine at + `docs/automation/README.md`: PRD, TRD, architecture, UML, conceptual ERD, + security/threat/test/operability/incident contracts, requirement traceability, + coverage assessment, and eight governing ADRs. - Added a trusted pull-request comment router for `@cwl-noema-review` and review-only `@opencode-agent` dispatches, with an organization sweep, exact-head receipts, repository allowlisting, fixed runners, immutable checkout pins, and a permanent 100% statement/branch/docstring quality gate. - Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence. diff --git a/CLAUDE.md b/CLAUDE.md index 1c7bdb2f6..dd6222162 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) (mission, ecosystem UML, cross-cutting disciplines CP-1..CP-5/G6/SEAM, binding engineering conventions in §7, roadmap), the live [GitHub Project #1](https://github.com/orgs/ContextualWisdomLab/projects/1) (work/roadmap source of -truth), and operate the Project per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). +truth), the automation control-plane contract at +[`docs/automation/README.md`](docs/automation/README.md), and operate the Project +per [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md). The repo/Project — not private agent memory — is the source of truth. This file complements those documents; it does not replace them. @@ -63,8 +65,9 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`. dependency sets (see below). - `fuzz/` + `.clusterfuzzlite/` — Atheris fuzz targets for the review-output normalizer and the ClusterFuzzLite discovery marker. -- `docs/` — master context, Project protocol, `org-required-workflow-rollout.md`, - `scorecard-governance.md`, SBOM inventory. +- `docs/` — master context, Project protocol, the canonical automation spine at + `docs/automation/README.md`, `org-required-workflow-rollout.md`, + `scorecard-governance.md`, and SBOM inventory. - `.jules/` — recorded performance (`bolt.md`) and security (`sentinel.md`) learnings from past work on `scripts/ci/`; worth scanning before optimizing or hardening those scripts. diff --git a/PR_GOVERNANCE_AUDIT.md b/PR_GOVERNANCE_AUDIT.md index bc0e31a6a..61c2d7884 100644 --- a/PR_GOVERNANCE_AUDIT.md +++ b/PR_GOVERNANCE_AUDIT.md @@ -1,6 +1,9 @@ # PR Governance Audit -Live check: 2026-06-26 17:53 KST, GitHub API via `gh` as `seonghobae`. +Baseline check: 2026-06-26 17:53 KST, GitHub API via `gh` as `seonghobae`. +Later dated observations are appended below. This is a historical evidence +ledger, not the current normative contract; use `docs/automation/README.md` and +executable workflows/tests for current repository behavior. ## Canonical Policy @@ -13,16 +16,12 @@ OpenCode decides; GitHub Actions mutates. at all when an organization required-workflow/ruleset mechanism can provide the trigger. Thick downstream sync PRs are an anti-pattern unless they are a temporary rollback bridge. -- `fork` versus `non-fork` is not the rollout boundary. Central governance - applies to every target repository that opts into the organization contract. - Runtime decisions classify the PR head capability instead: observable, - reviewable, updateable, auto-mergeable, and mergeable. External heads may be - fully reviewable while remaining non-mutable by the scheduler credential. - The same rule applies to repository onboarding: a public fork can be governed - by the same reusable workflow if it deliberately opts in, while a non-fork - PR head can still be non-mutable at runtime. The scheduler must decide from - observed PR permissions and current-head evidence, not from the repository's - `fork` flag alone. +- External-head review is a documented target, not protected-main behavior. + The current privileged scheduler, OpenCode, and Strix targeted-dispatch paths + reject a head repository that differs from the base repository. The policy + and implementation must converge through `ContextualWisdomLab/.github#889` + before an external head is described as centrally reviewable. External heads + never receive update, autofix, merge, release, or deployment authority. - GitHub workflow templates can help create thin callers, but templates are scaffolding, not centralized execution. Reusable workflows (`workflow_call`) centralize implementation while a caller or required-workflow trigger supplies @@ -33,12 +32,14 @@ OpenCode decides; GitHub Actions mutates. deadlock. The central reusable workflow owns concurrency: required-workflow PR events are isolated by pull request number, while scheduled full-queue scans stay serialized by repository/ref. -- Live organization state at the 2026-06-26 17:53 KST check: Actions are - enabled for the public non-fork target repositories, and organization ruleset - `18156473` (`CWL Central required workflows`) is active. It requires Strix, - OpenCode Review, and PR Review Merge Scheduler from `ContextualWisdomLab/.github` - for each target repository's default branch. Repository-local copies are now - cleanup candidates, not rollout prerequisites. +- Historical organization state at the 2026-06-26 17:53 KST check had Actions + enabled for the public non-fork target repositories and organization ruleset + `18156473` (`CWL Central required workflows`) active with Strix, OpenCode + Review, and PR Review Merge Scheduler. The audited protected-main contract on + 2026-08-09 has seven required workflow paths: close-empty PR, Noema, OpenCode, + merge scheduler, security scan, Strix, and Semgrep. Repository-local copies + are cleanup candidates, not rollout prerequisites; the checked-in auditor + detects drift but does not repair the live ruleset. - Strix is part of the same central governance contract, not a repo-specific security scanner to copy into each repository. The model allow-list, provider routing, fallback models, secret gate, PR-scope fetch, artifact/report @@ -46,11 +47,11 @@ OpenCode decides; GitHub Actions mutates. `ContextualWisdomLab/.github`. Target repositories supply repository content, event context, and inherited secrets; they do not redefine the Strix gate. - OpenCode may return only a decision: `UPDATE_BRANCH`, `WAIT`, `REQUEST_CHANGES`, or `NO_ACTION`. -- GitHub Actions updates only mutable PR heads with `expected_head_sha` after - current-head failed checks have been ruled out. Same-repository heads are - normally mutable; external heads are attempted only when GitHub exposes a - maintainer-writable head path, and otherwise receive explicit update - guidance instead of being skipped. +- GitHub Actions updates only mutable same-repository PR heads with + `expected_head_sha` after current-head failed checks have been ruled out. The + scheduler library has external-head guidance states, but the current + privileged targeted entrypoints reject external heads before that path; + `ContextualWisdomLab/.github#889` owns the convergence. - The GitHub REST permission surfaces are split: `update-branch` uses Pull requests write permission, while merge uses Contents write permission (GitHub REST pull request endpoint docs: @@ -67,7 +68,7 @@ OpenCode decides; GitHub Actions mutates. workflow `GITHUB_TOKEN`, and `merge_mode=disabled` reports the approved head without mutating it. Direct merge requires `CLEAN` mergeability and is a repository policy choice, not a fallback for missing evidence. -- OpenCode app-token merges are deprecated; keep app tokens for review publication, not mechanical branch mutation. +- The workflow `github.token` is preferred for same-repository mechanical mutation. The exchanged OpenCode App token remains an implemented, auditable fallback in the scheduler credential chain for scoped cross-repository operation; do not describe it as removed. - OpenCode approval publication must be bounded. Peer GitHub Checks can be awaited, but the approval step itself must time out instead of running for hours; the current central limit is a 45 minute approval step with 81 peer-check probes at 30 seconds. - Tool failures are not source findings. Model failure, API transient, update-branch `422/403`, fork/write-permission failure, conflict, failed checks, and stale review state must be reported as distinct scheduler outcomes. A failed current-head check blocks `UPDATE_BRANCH`; the scheduler must not use a branch update as a way to hide or bypass failed evidence. - Developer experience and user experience are separate review surfaces. Reviews must adopt helpful sibling-repo automation, review, setup, documentation, and product-flow patterns when they reduce friction, and flag noisy automation, false failures, misleading status, repeated waiting, or URL-only diagnostics as experience defects instead of treating them as neutral implementation detail. @@ -100,11 +101,15 @@ them to a local defect. ## Central Strix Contract The central Strix surface is the required workflow from repository -`ContextualWisdomLab/.github` through organization ruleset `18156473`. The live -ruleset pins `.github/workflows/strix.yml`, `.github/workflows/opencode-review.yml`, -and `.github/workflows/pr-review-merge-scheduler.yml` to repository ID -`1274066402` at SHA `807254a04efafd5f806e0f70cb067ecf050cfd11` for default -branches in the current target set. +`ContextualWisdomLab/.github` through organization ruleset `18156473`. The +audited protected-main contract sources seven paths from repository ID +`1274066402` at `refs/heads/main`: `.github/workflows/close-empty-pr.yml`, +`.github/workflows/noema-review.yml`, `.github/workflows/opencode-review.yml`, +`.github/workflows/pr-review-merge-scheduler.yml`, +`.github/workflows/security-scan.yml`, `.github/workflows/strix.yml`, and +`.github/workflows/sast-semgrep.yml`. A dated resolved SHA is audit evidence, +not the durable ruleset definition; live drift is reported by the read-only +auditor. Strix centralization includes these files and contracts: @@ -213,7 +218,7 @@ onboarding exception before relying on autonomous PR queue draining. | `kaefa` | Newly discovered public non-fork target at the 2026-07-02 18:15 KST refresh. It inherits org ruleset `18156473`, but current PR #60 status rollup showed only repo-local R-CMD-check, dependency-review, and CodeQL signals, with no central Strix/OpenCode/scheduler check runs. Treat it as an inherited-ruleset runtime proof gap until a new PR event or manual dispatch proves central checks on the current head. | | `naruon` | Canonical strict check source. PR #756 synced the central scheduler into `naruon`; its first head proved that widening `GITHUB_TOKEN` permissions to solve DX creates Scorecard and governance failures, so the merged rollout keeps minimal token permissions and defaults risky review-dispatch/auto-merge paths off. PR #721 remains the useful historical fixture for `BEHIND` handling: central dry-run selected `update_branch`, while the older repo-local workflow treated it as `wait`. Current PR #760 is clean, approved, and green on head `57a2f8e4`, so it is a merge-readiness sample; current dry-run with auto-merge disabled reports `wait`, as expected for the low-privilege scheduler profile. | | `newsdom-api` | Ruleset-required checks must stay GitHub-interpreted. PR #207 has merged, so it is no longer an update-branch proof candidate. The remaining open PRs #187, #203, #205, and #206 currently block because the current head has no OpenCode approval. | -| `pg-erd-cloud` | Good GitHub Actions merge samples; keep autofix workflows repo-local. | +| `pg-erd-cloud` | Good GitHub Actions merge samples; its former repo-local autofix worker was folded into the central worker and removed by `ContextualWisdomLab/pg-erd-cloud#393`. | | `scopeweave` | PR #127 is the current representative trace. Dry-run `28147098767` selected `auto_merge`, but live run `28147157319` failed with `GraphQL: Resource not accessible by integration (mergePullRequest)` because merge through GitHub Actions requires a contents-write mutation surface. Commit `6601953` proved the tempting fix, but Scorecard immediately opened a Token-Permissions review thread against job-level `contents: write`; follow-up commit `c5c5530` restores `contents: read` and keeps update-branch on the lower-privilege PR-write path. Current head `c5c5530` is clean, approved, and green; it remains unmerged because Actions-based merge is an explicit repo policy exception, not the default rollout. | | `appguardrail` | Public organization repo discovered in the 2026-06-26 refresh. It follows Git Flow on `develop`, inherits the central required workflow ruleset, has no default-branch local central-workflow copies, and has nine open PRs in the 2026-07-02 18:15 KST refresh, so it is now an active proof candidate rather than a zero-PR control repo. | | `xtrmLLMBatchPython` | Private repository discovered during PR queue draining on 2026-06-29. PR #50 is blocked by the repository-local one-approval rule because the only visible direct collaborator is also the PR author, and no current-head central OpenCode or Strix check exists. Add the private repository to the central required-workflow ruleset before continuing autonomous merges; do not force-merge and do not reduce the approval count to zero as a workaround. | @@ -240,7 +245,7 @@ both separately; a change can improve one while harming the other. |---|---|---|---| | `.github` | Same-head manual evidence and `--match-head-commit` make self-modifying workflow changes reviewable without pretending stale base-branch checks are current. | Stale `pull_request_target` failures, long polling review runs, and cancelled helper checks can become misleading review noise. | Serialize Strix before OpenCode, bound approval runtime, and require failed-check explanations instead of URL-only comments. | | `naruon` | Strict required checks, stale review dismissal, changed-file Mermaid flow DAGs, and current-head evidence make review evidence easier to audit. | Earlier repo-local scheduler drift had no update-branch path, no Strix-before-OpenCode sequencing, and no failed-check interpretation from the central script. Run `28073490721` also showed that an auto-merge permission failure can stop the whole queue before later PRs are inspected. PR #756 additionally showed that broadening workflow permissions is a tempting DX shortcut, but it degrades review trust and triggers Scorecard/governance failures. | Keep the implementation contract in `.github` required workflows, retire thick local copies only after central required runs prove stable, re-review every updated head, require an exact changed-file evidence path plus a Change Flow DAG before approval, keep `actions: read`/`contents: read` unless a separate privileged workflow is deliberately introduced, and record action failures per PR instead of aborting the scan. | -| `pg-erd-cloud` | GitHub Actions bot merges with head guards give a clear mechanical actor for merges. | Repo-local autofix workflows are useful there, but centralizing autofix would widen mutation scope too far. | Keep GitHub Actions as the merge actor and leave autofix workflows repo-local. | +| `pg-erd-cloud` | GitHub Actions bot merges with head guards give a clear mechanical actor for merges. | A thick repo-local autofix copy would drift from the guarded central worker. | Keep GitHub Actions as the merge actor and use the central autofix worker; retain only repository-specific application/security checks locally. | | `appguardrail` | Security-review subject matter makes it a useful place to verify that review automation distinguishes policy failure, tool failure, and source-code failure. | The live queue now has open PRs, so stale zero-PR assumptions can hide actual update-branch or merge behavior. | Use the current queue as a low-noise policy-vs-source review fixture after central checks are verified on each head. | | `bandscope` | Broad required checks encode repo-specific release, build, SBOM, and security expectations. | Copying the central scheduler into this repo turns one canonical contract into another repo-local drift surface. | Let GitHub native auto-merge and rulesets interpret required checks, and replace thick local governance files with a thin caller or organization required workflow. | | `newsdom-api` | Required quality gates and security checks give API changes stronger release evidence. | Central review comments that only point at failing check URLs do not help an API maintainer fix the failure. | Require failed-check root cause, source location when available, fix direction, and rerun command. | @@ -259,8 +264,9 @@ The checked-in scheduler already does the minimal central path: - skips only draft PRs and PRs whose base branch is outside the configured scheduler target branch; -- keeps external-head PRs in the same observation and review pipeline, then - gates only the write actions by PR head mutation capability; +- keeps same-repository heads in the observation/review pipeline; external-head + targeted review is currently rejected and tracked as policy drift in + `ContextualWisdomLab/.github#889`; - blocks UI `Conflicting`, API `DIRTY`, or API `CONFLICTING` with repair guidance that names the base branch, head branch, merge/rebase direction, conflict-marker cleanup, focused checks, same-branch push, and a compact `gh pr checkout` / `git fetch` / merge-or-rebase / `git status --short` command path; it explicitly does not retry `update-branch` for conflicted PRs because GitHub cannot choose the correct conflict resolution; - resolves GitHub `Outdated` unresolved review threads through `resolveReviewThread` before active blocker checks, using the scheduler workflow `GITHUB_TOKEN` inside GitHub Actions; dry-runs report the cleanup as `notes` without mutating the PR; - blocks active, non-outdated unresolved review threads; @@ -269,7 +275,9 @@ The checked-in scheduler already does the minimal central path: - waits on `ACTION_REQUIRED` check runs as workflow approval or repository-policy states, not as source-code failures; failed checks still take precedence for current-head-approved PRs, so `ACTION_REQUIRED` cannot mask a real failed `strix`, lint, build, or required-check result; - rejects OpenCode reviews whose GitHub review commit matches the PR head but whose review-body `Gate evidence` names a different `Head SHA`; this prevents stale review evidence from becoming current-head approval by attachment alone; - updates `BEHIND` only when OpenCode approved the exact current head, no current-head failed check is present, and the PR head is actually mutable by the scheduler credential, using `expected_head_sha` from the scheduler workflow `GITHUB_TOKEN` so the mechanical branch update is performed by `github-actions[bot]` inside GitHub Actions instead of an OpenCode or maintainer-local credential; the script now refuses non-dry-run `update-branch` outside GitHub Actions, and this path needs `pull-requests: write`, not `contents: write`; -- waits with `external_head_update_required` guidance when a current-head-approved external PR head is behind but is not writable by the scheduler credential, instead of treating fork/non-fork as an onboarding exception; +- contains an `external_head_update_required` library decision, but current + privileged targeted entrypoints reject the external head before end-to-end + review; do not cite the library branch as deployed support; - enables native auto-merge only for current-head OpenCode approval; - supports explicit merge policy through `merge_mode`: `auto` enables native auto-merge, `direct` performs a guarded `gh pr merge --merge @@ -278,7 +286,7 @@ The checked-in scheduler already does the minimal central path: and `disabled` records the approval without mutating the PR; - dispatches same-head Strix evidence first when the current head has no completed Strix evidence; - waits while same-head Strix evidence is still running, so OpenCode is not started just to poll a peer check; -- keeps old Strix evidence running instead of cancelling it, but scopes PR Strix concurrency by head SHA so an obsolete scan does not serialize newer current-head evidence; +- uses event/repository/PR-number Strix concurrency with `cancel-in-progress: true`, so the replacement run resolves the current PR head at execution while repository-dispatch and required-workflow event classes remain isolated; - dispatches OpenCode only after same-head Strix evidence is complete, including failed Strix evidence that OpenCode must explain from logs. - records mutation failures as `action_error` for the affected PR and continues scanning later PRs, so a permission failure on one merge/update action does not hide the rest of the queue. - writes the same per-PR decisions to the GitHub Actions step summary, so conflict repair and update-branch decisions are visible without opening raw logs. @@ -324,9 +332,9 @@ PR #381: wait: OpenCode review is already in progress required-workflow mechanism, keep only a thin caller that passes PR number, base ref/SHA, head ref/SHA, target flow, and inherited secrets/permissions into `.github`. -4. Treat fork and non-fork repositories uniformly for onboarding. At runtime, - classify only the PR head mutation capability: observable/reviewable, - updateable, auto-mergeable, or mergeable. +4. Target policy is uniform fork/non-fork onboarding with a read-only external + review capability, but protected main does not yet implement it. Complete + `ContextualWisdomLab/.github#889` before enabling that contract. 5. Do not leave an active public fork PR queue in the inventory-only state. When a public fork such as `html4tree` has open PRs targeting the organization-owned fork, it must either be included in the organization required-workflow ruleset @@ -393,7 +401,7 @@ PR #381: wait: OpenCode review is already in progress - `bandscope` also proved the large-queue scan risk: `max_prs=120` initially failed with `Resource limits for this query exceeded` while reading 80 open PRs. After reducing the GraphQL page size to 25, the same dry-run scanned all 80 open PRs and returned `{"block": 67, "update_branch": 1, "wait": 12}`, including PR #378 as `update_branch` and PR #404 as a conflict block with repair guidance. - `newsdom-api` no longer has a smaller current update-branch proof candidate in the 15:12 KST dry-run. PRs #187, #203, #205, and #206 all block before update because the current head has no OpenCode approval. - 2026-07-13 KST `.github` model-pool starvation incident: `opencode run` hangs to the full run timeout when a provider returns a fatal error (`ContextOverflowError` "Request body too large ... Max size: 4000 tokens" on `github-models/openai/gpt-5`, `html4tree` PR #166 run `29221961969`), burning 2×300s of a 600s retry budget before pool candidates 5-7 were tried. PR #511 adds an early-kill watchdog to `run_opencode_review_model_pool.sh` that polls the JSON event log while `opencode run` executes and kills hung processes within seconds of a structured fatal `"type":"error"` event. The same day exposed a central-scope review starvation loop: PRs whose changed files are all central review-process core receive the 120s/attempt fallback cadence, but since PR #508 removed the model-unavailable approve path, such PRs can only pass with a full model verdict inside 120s — `openai/gpt-5.6-luna` (the only live candidate while the GitHub Models org budget is capped) exceeded 120s twice on PR #511 itself, so the required check exhausted deterministically while the skipped evidence fallback published nothing. Recording the incident in this audit file also routes PR #511 through the standard small-change cadence (300s/attempt) — the same full-contract adversarial-probe review every other small PR receives — instead of the starved 120s fast path whose approval authority no longer exists. The durable repair is raising the central-scope cadence timeout or reviving the budget-capped candidates (org Billing → Budgets). -- `.github` PR #58 exposed that a cancelled manual Strix run can keep its manual status publisher queued and delay the next same-PR Strix run. PR #58 now skips that publisher when the workflow is cancelled, scopes Strix PR concurrency by head SHA so obsolete scans do not serialize newer evidence, and requires conflict reviews to include a concrete `gh pr checkout` / `git fetch` / merge-or-rebase / `git status --short` repair path. +- `.github` PR #58 exposed that a cancelled manual Strix run can keep its manual status publisher queued and delay the next same-PR Strix run. The current protected workflow skips that publisher when cancelled and supersedes same-event, same-PR Strix work through PR-number concurrency; repository-dispatch and required-workflow event classes remain separate. Conflict reviews also include a concrete `gh pr checkout` / `git fetch` / merge-or-rebase / `git status --short` repair path. - PR #721 in `naruon` remains the historical fixture for this proof: head `b683deaf8b4761399321799279f58d884db57141`, current-head OpenCode approval `4558310923`, unresolved review threads `0`, and `mergeStateStatus=BEHIND`. Central `.github` dry-run selected `update_branch`, but `naruon` workflow run `28073586594` used the then-stale repo-local scheduler and did not update it. PR #756 has since rolled the central scheduler into `naruon`, so the next proof must use a fresh current-head outdated PR instead of reusing stale evidence from #721. - `naruon` workflow run `28073490721` failed at `gh pr merge 694 --auto --merge --match-head-commit 76416321742af4c8dcd0f96927f64b7548d66fd8` with `GraphQL: Resource not accessible by integration (enablePullRequestAutoMerge)`. This is a DX/governance action failure, not a source-code finding, and the scheduler now records it per PR instead of aborting the scan. - `naruon` PR #756 completed the repo-local rollout for the scheduler contract. Its initial head failed backend governance and Scorecard because `actions: write`/`contents: write` were broader than the repo policy allows; the amended and merged head restores minimal `GITHUB_TOKEN` permissions, keeps `trigger_reviews` and `enable_auto_merge` defaulted off, keeps `update_branches` defaulted on, and still dry-runs PR #694/#721 as `update_branch`. diff --git a/README.md b/README.md index 332324301..890e86635 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ The audit includes repository-by-repository DX/UX transfer decisions: what the central workflow borrows because it reduces friction, and what it rejects because it adds noise or misleading review experience. +The authoritative PRD, TRD, architecture, UML, ERD, security, threat, test, +operability, incident, traceability, and ADR spine starts at +[docs/automation/README.md](docs/automation/README.md). Dated audit narratives +are evidence inputs; when prose conflicts, the canonical spine and executable +workflow/test contract govern. + ## PR review and merge policy OpenCode judges PRs; GitHub Actions performs mechanical updates and merges. @@ -78,9 +84,12 @@ push, and commits as `github-actions[bot]` only when a conservative OpenCode autofix produces a validated diff. A repository-local autofix worker remains an explicit compatibility override through `--autofix-repository`; it is no longer the default contract. -Strix keeps `cancel-in-progress: false` so old evidence is not cancelled by a -force-push, but PR-scoped concurrency includes the head SHA so an obsolete scan -does not serialize newer current-head evidence. +Strix uses event/repository/PR-number concurrency with +`cancel-in-progress: true`; a newer run for the same event class and PR cancels +the predecessor so the queued scan resolves `refs/pull//head` at execution +time and evaluates the current head. Repository-dispatch and +`pull_request_target` event classes remain separate so one cannot cancel the +other's required-check context. OpenCode approval is evidence-gated. Before approval, the review summary must name changed files, CodeGraph or structural MCP evidence, a Change Flow DAG, @@ -120,10 +129,12 @@ that discovers runtime matrices, package manifests, test, coverage, docstring, E2E, lint, security, Docker, and unpackaged-source gaps before the agent chooses commands. The configured `code-reviewer` subagent is reviewer-only: it may read, grep, -glob, and run safe local verification commands, but it must not edit files, -stage changes, commit, push, install dependencies, mutate branches, or touch -production state. Blocking findings must be source-backed, severity-labeled, -impactful, remediable, and include suggested verification. +glob, and list the trusted bounded evidence, but bash, task/subagents, network, +LSP, MCP, and external-directory access are denied. It cannot edit, stage, +commit, push, install dependencies, mutate branches, or touch production state. +Execution receipts are supplied by the trusted workflow. Blocking findings +must be source-backed, severity-labeled, impactful, remediable, and include +suggested verification. Failed GitHub Checks are not reviewed as URL lists. OpenCode must explain the failed check name, failing step, source-backed file and line when available, diff --git a/docs/CWL-MASTER-CONTEXT.md b/docs/CWL-MASTER-CONTEXT.md index bd5e6c0c4..396545e3a 100644 --- a/docs/CWL-MASTER-CONTEXT.md +++ b/docs/CWL-MASTER-CONTEXT.md @@ -2,7 +2,7 @@ > Purpose: a single, durable, agent-readable brief so ANY agent (Claude with a fresh context, Codex, Grok, Gemini) can reconstruct and continue this work WITHOUT the originating conversation. Private assistant memory is NOT the source of truth — this repo is. Keep this file current. > -> Durable sources of truth (in priority order): (1) **GitHub Project #1** "naruon Platform Roadmap" https://github.com/orgs/ContextualWisdomLab/projects/1 — live work/roadmap; (2) **naruon `docs/planning/naruon-platform-plan.md`** (PR ContextualWisdomLab/naruon#974) — full IA/User-Stories/Use-Cases/Architecture spec; (3) **`docs/agent-github-project-protocol.md`** (this repo, PR #363) — how agents operate the Project + cross-repo-ref convention; (4) this file. +> Durable sources of truth (in priority order): (1) **GitHub Project #1** "naruon Platform Roadmap" https://github.com/orgs/ContextualWisdomLab/projects/1 — live work/roadmap; (2) **naruon `docs/planning/naruon-platform-plan.md`** (PR ContextualWisdomLab/naruon#974) — full IA/User-Stories/Use-Cases/Architecture spec; (3) **`docs/agent-github-project-protocol.md`** (this repo, PR #363) — how agents operate the Project + cross-repo-ref convention; (4) **`docs/automation/README.md`** — authoritative `.github` automation PRD/TRD/architecture/security/operations/ADR spine; (5) this file. ## 0. Origin / core job-to-be-done (READ THIS — everything serves it) naruon's genesis (the user's own words): **"I can't find my emails, and the schedules that arrive by email keep changing so they're hard to track."** So the two founding jobs are: @@ -124,14 +124,16 @@ A **source-agnostic artifact-analysis service**: `submit(artifact, context) → ## 9. How work is tracked (dogfood the traceability) GitHub **Project #1** is the shared source of truth. Structure: real **Issues** (roadmap/backlog, in owning repos, custom fields Phase P0–P5/Ops/Decision + Component) and real **PRs** (delivered work, native Repository). Native workflows are ON (item added→Todo, PR merged→Done, item closed→Done). Chain: roadmap **Issue** → agent sets In Progress on pickup → implementing **PR** `Closes #N` → merge → auto Done. Operate the Project per `docs/agent-github-project-protocol.md`. Group by Phase / Component / Repository. -## 10. Current state (2026-07-08) +## 10. Historical state snapshot (observed 2026-07-08) - Renames done (keyverse/wardnet/inkspan). Planning spec = naruon#974. Project #1 populated (68 issues + 60 PRs). Protocol = .github#363. - **BLOCKER B1**: org GitHub Actions effectively HALTED (~86 queued, ~0 in_progress org-wide) — likely the Actions monthly SPENDING CAP. Blocks ALL PR checks/merges + the Cloudflare DNS run (nameservers). Fix (org-admin): raise the Actions spending limit OR add a self-hosted runner. Nothing merges until then. - **Decisions pending**: (D1) Code Security enablement vs the CodeQL-only code_scanning ruleset (osv/trivy/scorecard SARIF upload) — a private repo needs GHAS seats; reconcile or make those checks non-required. (D2) trivy `limit-severities-for-sarif: true` (gate only CRITICAL/HIGH) — held pending the user's strict-security preference. - **Built this session, PR-open, awaiting merge (B1)**: see Project #1 PRs (contextual-orchestrator cost/routing #46 + naruon#973; pg-llm-batch; keyverse Keycloak; inkspan; SBOM #361; opencode auto-retry #360; Strix neutral #349 + emit #358; appguardrail collector #254; auto-rebase #357; noema #359/naruon#970; PDF-DOM naruon#965/newsdom#300; SDP #11; fast-mlsirm GPGPU #109; scopeweave #284/naruon#971; fuzzing 10 PRs (found+fixed 2 real naruon bugs); Cloudflare DNS/Pages #362; this protocol #363; planning #974). Human step: report the mapasevo21 malware file (github user-attachments) to GitHub Abuse; rotate the xtrmLLMBatchPython-leaked keys; the org-admin runner/decisions above. --- -*Keep this current. Update Project #1 as the live tracker; this file is the narrative brief a fresh agent reads to reconstruct the whole picture.* +*Project #1 is the live tracker. Dated facts in this narrative are historical +snapshots until re-observed; the automation contract is indexed at +`docs/automation/README.md`.* ## Inter-component architecture (UML) diff --git a/docs/automation/ARCHITECTURE.md b/docs/automation/ARCHITECTURE.md new file mode 100644 index 000000000..23b73b9be --- /dev/null +++ b/docs/automation/ARCHITECTURE.md @@ -0,0 +1,148 @@ +# Architecture: CWL automation control plane + +Status: code-current bounded-context and trust-boundary view. + +Editable companion: [CWL Automation Control Plane Architecture and Evidence +ERD](https://www.figma.com/board/4x8YSMb8teJhU19nDjdkcy). The Mermaid diagrams +in this repository remain the versioned normative source. + +## Architectural style + +The system is a central control plane with thin leaf enrollment. GitHub is the +event and policy substrate. `ContextualWisdomLab/.github` supplies immutable +trusted workflow code, decision helpers, reviewer adapters, and fleet audit. +Product repositories retain source ownership and independent build/release +operation. + +```mermaid +flowchart TB + maintainer["Maintainer or trusted agent"] --> githubEvents["GitHub events and rulesets"] + leafRepo["Leaf product repository"] --> githubEvents + githubEvents --> requiredBootstrap["Required workflow bootstrap"] + githubEvents --> centralScheduler["Central review and merge scheduler"] + requiredBootstrap --> evidencePlane["Check and workflow evidence"] + centralScheduler --> strixPlane["Strix security plane"] + centralScheduler --> opencodePlane["OpenCode review plane"] + opencodePlane --> noemaPlane["Noema independent review plane"] + strixPlane --> evidencePlane + opencodePlane --> evidencePlane + noemaPlane --> evidencePlane + evidencePlane --> authorityGate["Review and merge authority gate"] + authorityGate --> protectedRef["Protected target ref"] + fleetAudit["Read-only fleet auditor"] --> githubEvents + operationalProbe["Protected-main consumer acceptance"] --> evidencePlane +``` + +## Bounded contexts + +### Enrollment and event context + +Organization rulesets and repository events create required checks and route +work. Leaf repositories provide source and repository-specific contracts, but +do not redefine central reviewer or merger authority. + +### Snapshot and provenance context + +The context materializes a trusted workflow revision and a target PR snapshot. +It validates repository/ref/SHA syntax, fetches the live PR, independently +observes the base ref, and seals evidence to that identity. It owns neither the +review verdict nor source mutation. + +### Evidence execution context + +Strix, deterministic tests, coverage/docstrings, CodeQL/security, OpenCode, and +Noema produce distinct evidence records. Sandboxed execution may run target +source only after the protected control path has validated identity and scoped +credentials. + +### Decision and mutation context + +`scripts/ci/pr_review_merge_scheduler.py` interprets evidence, review state, +thread state, mergeability, and branch freshness. It can dispatch missing +evidence, update a same-repository head, enable guarded auto-merge, or request a +head-matched merge. It does not turn a model sentence into authority. + +### Repair context + +`scripts/ci/pr_review_fix_scheduler.py` identifies a narrow candidate; +`.github/workflows/pr-review-autofix.yml` owns the edit-capable worker. The +worker verifies the snapshot before checkout and before push. Its new head +returns through every review and check gate. + +### Fleet audit and operability context + +`scripts/ci/audit_central_required_workflows.py` and scheduled workflows inspect +enrollment, ruleset, queue, and evidence drift without obtaining a source +writer lease. Operational acceptance records whether protected-main central +behavior worked in a real target repository. + +## Control plane and data plane + +- **Control plane:** events, workflow provenance, inputs, permissions, secret + requirements, state classification, review eligibility, writer leases, + dispatch, merge/release decisions, and incident state. +- **Data plane:** source archives/worktrees, patches, test output, coverage, + SARIF, SBOMs, logs, model prompts/responses, and artifacts. + +Control-plane identity is never inferred from data-plane content. PR text and +model output are untrusted data, even when they describe a desired action. + +## Trust boundaries + +1. **Untrusted PR to protected workflow:** `pull_request_target` metadata may be + read, but PR code is not executed by the privileged bootstrap. +2. **Central workflow to target repository:** repository, PR, source head, base + branch, and live-base state are revalidated before dispatch or mutation. +3. **GitHub to external model provider:** only bounded prompts and the required + model credential cross the egress boundary; model output returns as + untrusted advisory evidence. +4. **Evidence to authority:** checks/statuses/reviews are classified by type and + identity before ruleset and merge logic consume them. +5. **Source write:** only the actor holding the fresh branch writer lease may + push. The fleet auditor is outside this boundary. + +## Failure domains + +- GitHub event/API/ruleset failures affect event delivery or policy reads. +- Runner and source-materialization failures affect one execution attempt. +- Provider/model failures affect advisory review capacity. +- Credential/App/OIDC failures affect only actions requiring that authority. +- Leaf product tests affect one target head. +- Central workflow defects can affect the fleet and therefore require staged + protected-main consumer evidence before broad closure. + +## Central versus leaf deployment + +Central required workflows run in target-repository context while their trusted +definition comes from this repository. Privileged `repository_dispatch` +receivers exist only on a protected default branch. Thick leaf-local copies are +temporary rollback bridges and must not become a second source of truth. + +The strict review-only dispatch envelope in +`ContextualWisdomLab/.github#840` is pending. Architecture diagrams describe +the required identity and mutation boundary, while +[DOCUMENTATION_COVERAGE.md](DOCUMENTATION_COVERAGE.md) records the implementation +state honestly. + +The architecture also distinguishes logical target controls from deployed +guarantees. Per-workflow concurrency and compare-and-swap head guards exist, +but a cross-workflow durable writer lease is conceptual and tracked in +`ContextualWisdomLab/.github#890`. Noema is a distinct deployed review identity, +but the scheduler does not prove that a counted approval came from Noema; that +governance gap remains in `ContextualWisdomLab/.github#772`. + +## Responsibility and deployment ownership + +| Surface | GitHub platform | Central `.github` | Leaf repository | Human/security owner | +|---|---|---|---|---| +| events, refs, checks, reviews, rulesets | persist/enforce platform objects | interpret typed evidence | emit product events and source refs | configure eligible actors/rulesets | +| trusted review/security policy | execute protected workflow definition | own workflows, helpers, tests, pins | thin enrollment/caller only | review central policy change | +| source/build/product tests | host runner/ref transaction | sandbox and classify receipts | own commands, fixtures, runtime | own product correctness | +| branch repair/merge | enforce API/ruleset/head transaction | select guarded action and credential | retain branch/source ownership | provide legitimate approval/exception | +| release/deployment | enforce environment transaction | no implied authority | own release/deploy workflow | approve environment/promotion | +| incident acceptance | retain run/audit objects | define receipt and coordinate canary | execute representative consumer path | close/reopen incident | + +External model providers own service capacity and response generation only; +their output remains untrusted advisory data. A leaf compatibility override +must name its owner, bounded scope, rationale, exit condition, and known-good +central replacement. diff --git a/docs/automation/DOCUMENTATION_COVERAGE.md b/docs/automation/DOCUMENTATION_COVERAGE.md new file mode 100644 index 000000000..b3a9015a2 --- /dev/null +++ b/docs/automation/DOCUMENTATION_COVERAGE.md @@ -0,0 +1,95 @@ +# Documentation coverage assessment + +Assessment date: 2026-08-09. Baseline protected-main revision: +`6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba`. + +## Verdict + +The baseline documentation was **insufficient** for the automation control +plane described in repository code and the operating conversation. It had one +focused comment-invocation document but no discoverable PRD, TRD, architecture, +UML, ERD, security contract, threat model, test strategy, operability contract, +incident runbook, traceability matrix, or durable ADR set. Large README/audit +prose contained useful fragments but could not answer which behavior was +implemented, pending, policy-only, or operationally accepted. + +This change creates the minimum complete documentation spine and a machine +contract that prevents silent deletion or loss of discoverability. It does not +turn pending PR behavior into protected-main truth. + +The baseline audit also found executable/prose contradictions: Strix is +PR-number scoped with `cancel-in-progress: true` rather than head-SHA scoped and +non-cancelling; `code-reviewer` cannot execute commands; pg-erd-cloud uses the +central autofix worker by default; the current ruleset audit expects exactly +two approvals rather than a single-maintainer gate; and the scheduler still has +an exchanged App-token fallback. This change corrects those statements and +adds invariant tests so dated narrative cannot silently override code. + +## Coverage matrix + +| Concern | Baseline | This spine | Remaining evidence boundary | +|---|---|---|---| +| Product outcomes and users | scattered narrative | covered by [PRD.md](PRD.md) | validate changes against buyer/operator outcomes | +| Technical identity, gates, retries, secrets | partial across README/code | covered by [TRD.md](TRD.md) | keep state labels current with protected main | +| Bounded contexts/trust boundaries | absent as a coherent view | covered by [ARCHITECTURE.md](ARCHITECTURE.md) | update on new provider/trigger/authority | +| Interaction and state behavior | absent | covered by [UML.md](UML.md) | keep Mermaid rendered in GitHub; Figma is supplemental | +| Evidence/domain relationships | absent | conceptual/persistence mapping in [ERD.md](ERD.md) | add physical schema only if a dedicated store is introduced | +| Security invariants | scattered | covered by [SECURITY.md](SECURITY.md) | hardening in `.github#842` remains pending | +| Threats and abuse cases | absent | covered by [THREAT_MODEL.md](THREAT_MODEL.md) | re-evaluate after incidents/boundary changes | +| Verification and 100% owned coverage | partial implementation guidance | covered by [TEST_STRATEGY.md](TEST_STRATEGY.md) | per-change execution evidence remains required | +| SLIs, queue, rollback, handoff | absent | covered by [OPERABILITY.md](OPERABILITY.md) | telemetry availability must be reported honestly | +| Incident response/closure | scattered | covered by [INCIDENT_RUNBOOK.md](INCIDENT_RUNBOOK.md) | each incident needs protected-main consumer proof | +| Durable architectural decisions | absent | eight minimum ADRs indexed under [adr/](adr/README.md) | supersede rather than silently rewrite decisions | +| Requirement-to-code/incident/standard map | absent | covered by [TRACEABILITY.md](TRACEABILITY.md) | revise on workflow/test/issue lineage changes | +| Discoverability | no canonical entry point | root/agent/master-context links plus index | contract test enforces links | + +## Implementation honesty + +### Implemented on the audited protected main + +- central required/reusable review, security, merge-scheduler, autofix, and + fleet-audit surfaces named in [TRD.md](TRD.md); +- exact-head guards and live-base comparison in scheduler/replay paths; +- differentiated check/review/scheduler evidence and guarded merge behavior; +- sandbox, sanitization, security, coverage, SBOM/provenance, and contract-test + infrastructure represented by tracked workflows/scripts/tests. + +### Pending open changes + +- strict `cwl.agent-invocation/v2` three-key dispatch envelope and + snapshot-only review route: `ContextualWisdomLab/.github#840`; +- credential-shaped subprocess-log redaction at the publication boundary: + `ContextualWisdomLab/.github#842`; +- external-head policy/runtime alignment: `ContextualWisdomLab/.github#889`; +- shared cross-workflow writer lease and fencing: + `ContextualWisdomLab/.github#890`; +- fail-closed authoritative Strix evidence: `ContextualWisdomLab/.github#891`; +- scheduler merge-mode and mutation-credential authority alignment: + `ContextualWisdomLab/.github#892`; +- recoverable mention invocation claim state: + `ContextualWisdomLab/.github#893`; +- terminally non-passing scheduler action errors while preserving the bounded + queue summary: `ContextualWisdomLab/.github#894`. + +### Policy or conceptual contracts + +- one logical writer lease per repository/branch is the governing target; + current concurrency and live-head guards reduce races but do not globally + serialize different writer workflows; +- the ERD defines evidence identities that are currently persisted across + GitHub objects, workflow outputs, artifacts, and handoffs rather than one + database; +- work-conserving anti-idle behavior is an automation/policy contract, not a + repository queue daemon; +- protected-main consumer acceptance is recorded per operational change and + cannot be inferred globally. + +## Quality gate + +`tests/test_automation_documentation_contract.py` requires every canonical +document and ADR, validates entry-point discovery and workflow links, checks +supported Mermaid fences, requires the evidence/authority vocabulary and ERD +entities, compares the documented secret registry to workflow usage, checks +current Strix/reviewer/ruleset/autofix invariants, and rejects ambiguous +unfinished markers. Code review must still evaluate semantic accuracy; even an +executable contract cannot prove unqueried live GitHub settings. diff --git a/docs/automation/ERD.md b/docs/automation/ERD.md new file mode 100644 index 000000000..c1a54e70b --- /dev/null +++ b/docs/automation/ERD.md @@ -0,0 +1,179 @@ +# Evidence and automation domain model + +Status: normative conceptual/logical model. This is not a claim that the +entities below are persisted in one database. + +Editable companion: [CWL Automation Evidence Domain Model in +FigJam](https://www.figma.com/board/4x8YSMb8teJhU19nDjdkcy). The Mermaid ERD +below is the repository-versioned source of truth. + +## Persistence boundary + +Most entities are **conceptual** and currently materialize across GitHub PRs, +refs, workflow runs, check runs, statuses, reviews, Actions artifacts, job logs, +issues, and summaries. `handoff_record` and `operational_acceptance` may be +persisted as issue/PR comments, artifacts, or run summaries. A future service +may persist normalized rows, but it must preserve GitHub object identifiers and +source provenance rather than inventing replacement authority. + +Database implementations must use descriptive two-or-more-word `snake_case` +object names. The entity names below already follow that rule. + +```mermaid +erDiagram + repository_target ||--o{ automation_run : receives + repository_target ||--o{ pull_request_snapshot : owns + repository_target ||--o{ writer_lease : grants + repository_target ||--o{ operational_acceptance : proves + automation_run }o--|| workflow_evidence : produces + automation_run ||--o{ secret_requirement : materializes + automation_run ||--o{ handoff_record : creates + automation_run ||--o{ incident_hypothesis : investigates + pull_request_snapshot }o--|| source_revision : binds + pull_request_snapshot }o--|| base_revision : observes + pull_request_snapshot ||--o{ check_evidence : evaluates + pull_request_snapshot ||--o{ review_evidence : evaluates + pull_request_snapshot ||--o{ dependency_evidence : constrains + pull_request_snapshot ||--o{ workflow_evidence : executes + pull_request_snapshot ||--o| writer_lease : controls + workflow_evidence ||--o{ check_evidence : publishes + workflow_evidence ||--o{ review_evidence : publishes + incident_hypothesis ||--o{ handoff_record : records + operational_acceptance }o--|| source_revision : validates + operational_acceptance }o--|| workflow_evidence : exercises + + repository_target { + string repository_name PK + string default_branch + string enrollment_state + datetime observed_at + } + automation_run { + string automation_run_id PK + string repository_name FK + string trigger_event + string run_state + datetime started_at + datetime completed_at + } + pull_request_snapshot { + string snapshot_key PK + string repository_name FK + int pull_request_number + string source_revision_sha FK + string base_revision_sha FK + datetime observed_at + } + source_revision { + string source_revision_sha PK + string source_ref_name + string source_repository + datetime committed_at + } + base_revision { + string base_revision_sha PK + string base_ref_name + string base_repository + datetime resolved_at + } + check_evidence { + string check_evidence_id PK + string snapshot_key FK + string check_name + string check_conclusion + string producer_identity + } + review_evidence { + string review_evidence_id PK + string snapshot_key FK + string review_state + string reviewer_identity + bool counted_approval + } + workflow_evidence { + string workflow_evidence_id PK + string automation_run_id FK + string workflow_path + string workflow_revision_sha + string run_conclusion + } + dependency_evidence { + string dependency_evidence_id PK + string snapshot_key FK + string dependency_type + string dependency_identity + string dependency_state + } + incident_hypothesis { + string incident_hypothesis_id PK + string automation_run_id FK + string failure_boundary + string failure_class + string hypothesis_state + } + handoff_record { + string handoff_record_id PK + string automation_run_id FK + string target_lane + string deferred_reason + datetime next_eligible_at + } + operational_acceptance { + string acceptance_record_id PK + string repository_name FK + string source_revision_sha FK + string workflow_evidence_id FK + string consumer_repository + string acceptance_state + } + secret_requirement { + string secret_requirement_id PK + string automation_run_id FK + string secret_name + string required_scope + string materialization_step + } + writer_lease { + string writer_lease_id PK + string repository_name FK + string branch_name + string holder_identity + string expected_head_sha + datetime acquired_at + datetime expires_at + } +``` + +## Identity and cardinality rules + +1. One snapshot binds exactly one source revision and one independently + observed base revision. A new source head or live-base observation creates a + new snapshot identity. +2. Evidence records are append-only observations. A newer record may supersede + authority, but history is not rewritten. +3. A review record carries `counted_approval` because review state and reviewer + eligibility are separate facts. +4. A writer lease is optional for a read-only run and exclusive for a branch + mutation. It never grants merge, release, or deployment authority by itself. +5. A secret requirement identifies the exact step and scope. It does not store + the secret value. +6. Operational acceptance references both the integrated source revision and + the workflow evidence exercised by a real consumer. + +## Mapping to current systems + +| Entity | Current materialization | +|---|---| +| `automation_run` | GitHub Actions workflow run/job | +| `repository_target` | GitHub repository plus ruleset enrollment | +| `pull_request_snapshot` | Live PR API/GraphQL payload plus scheduler decision | +| `source_revision`, `base_revision` | Git refs and 40-character commit SHAs | +| `check_evidence` | Check run or required context | +| `review_evidence` | GitHub pull-request review object | +| `workflow_evidence` | Workflow source SHA, run, job, and artifact metadata | +| `dependency_evidence` | Stack/base/reusable-workflow/ruleset condition | +| `incident_hypothesis` | Issue/PR RCA and evidence note | +| `handoff_record` | Deferred-lane ledger, issue/PR note, or run summary | +| `operational_acceptance` | Protected-main consumer run evidence | +| `secret_requirement` | Workflow input/secret contract and job environment | +| `writer_lease` | Fresh branch/ref checks plus single-writer policy | diff --git a/docs/automation/INCIDENT_RUNBOOK.md b/docs/automation/INCIDENT_RUNBOOK.md new file mode 100644 index 000000000..82323fab1 --- /dev/null +++ b/docs/automation/INCIDENT_RUNBOOK.md @@ -0,0 +1,117 @@ +# Incident runbook: CWL automation control plane + +Status: executable operator procedure for security, integrity, availability, +and protected-main acceptance incidents. + +## Activate and classify + +Open an incident when automation may have written the wrong ref, reused stale +evidence, bypassed an eligible review or ruleset, exposed a credential, lost +workflow provenance, affected multiple repositories, or repeatedly fails a +protected-main consumer path. + +| Severity | Condition | Initial action | +|---|---|---| +| SEV-0 | confirmed unauthorized protected-ref/release mutation or active credential exfiltration | contain affected authority immediately; notify security owner | +| SEV-1 | credible cross-repository escape, forged approval/evidence, fleet-wide central failure | freeze affected mutation class; preserve read-only audit | +| SEV-2 | one repository/PR blocked or incorrectly classified with no unauthorized write | defer exact item; continue unrelated safe lanes | +| SEV-3 | documentation, telemetry, or non-blocking diagnostic defect | record bounded repair and owner | + +## Record the incident identity + +Capture before changing state: + +- repository, PR, source ref/head, base branch, observed live-base SHA; +- workflow file/ref, run/job/attempt, event, actor, App/token class; +- check, status, review, thread, artifact, dispatch, and merge transaction ids; +- writer-lease holder and competing worker identity; +- first observed time, last known-good protected revision, and affected + consumers; +- exact bounded error/command result after credential-safe transformation. + +Do not copy raw authorization headers or suspected secrets into the incident. + +## Contain + +Choose the narrowest reversible containment: + +1. Stop or disable only the affected mutation/credential path; keep read-only + fleet audit and unaffected repositories running. +2. Cancel obsolete runs only when cancellation cannot remove newer valid + evidence or cause a stale request to win. +3. Revoke or rotate only confirmed/credible affected credentials and App + installations. Do not fall back automatically to a broader token. +4. Prevent merge/release/deploy while evidence identity or reviewer + eligibility is uncertain. Never weaken the ruleset to clear the queue. +5. Preserve logs, workflow inputs, GitHub objects, and exact SHAs with bounded + access and retention. + +## Diagnose + +Classify the first failing boundary, not the last visible symptom: + +| Symptom | Questions | Safe next action | +|---|---|---| +| stale or wrong-head evidence | Which producer/head/base/run created it? Did either ref move? | invalidate predecessor evidence; re-run on the current tuple | +| unexpected write or merge | Who held the lease and credential? Did final head match? | freeze that authority, verify protected audit log, prepare reviewed revert | +| 401/403/ruleset denial | Is identity eligible and repository-scoped? | repair legitimate authority/configuration; no retry loop | +| API 5xx/rate limit/runner wait | Is it classified transient and within budget? | bounded retry, then defer exact item | +| provider timeout/exhaustion | Did deterministic gates pass? Is another configured provider within budget? | try distinct provider or defer; no synthetic approval | +| checksum/TLS/provenance failure | Which immutable input failed? | fail closed; repair source/pin/trust path | +| secret-shaped output | Was it published, retained, or sent externally? | restrict evidence, rotate affected credential, test expanded redaction fixture | +| central merge but consumer failure | Which protected-main workflow and target ran? | keep incident open; reproduce against real consumer boundary | +| no counted approval | Is reviewer independent, eligible, current, and formal? | wait for legitimate review; continue other work | + +After three materially distinct failed remedies, reassess the architecture, +permissions, trigger, or dependency contract instead of repeating variants of +the same repair. + +## Repair and verify + +1. Add a realistic failing test or replay fixture for the incident identity. +2. Apply the narrowest fix on a branch based on the exact live protected tip. +3. Run focused tests, the full affected matrix, security/provenance gates, + documentation contracts, and full repository checks. +4. Obtain an eligible independent review where policy requires it. +5. Merge with final exact-head protection. +6. Exercise the changed protected-main path in at least one enrolled real + consumer and record target, run/job, source head, live base, conclusion, and + recovery behavior. + +An incident is not resolved because source merged or a model commented. + +## Common recovery procedures + +### Unauthorized or wrong revision mutation + +Freeze the actor/credential, compare the protected ref and audit log to the +recorded expected head, preserve the unexpected revision, and prepare a normal +reviewed revert from the current protected tip. Re-run every gate and consumer +acceptance; do not rewrite protected history. + +### Credential disclosure + +Restrict/remove the published evidence through the platform's supported +mechanism, revoke or rotate the credential, inspect its audit scope from the +first exposure time, replace any derived credentials, add the exact output +shape as a synthetic fixture, and verify publication-boundary redaction. + +### Stuck queue or provider outage + +Record the exact deferred item and next trigger, stop redundant polling, and +continue other safe lanes. Resume only after a new head/base/review/check, +provider recovery, explicit authority change, or final sweep. + +### Central workflow regression + +Limit affected dispatches, identify the last known-good protected revision, +use a reviewed revert or explicit known-good caller pin, and validate one +representative consumer before restoring fleet-wide mutation. + +## Closure and follow-up + +Closure requires confirmed scope, root cause at the correct boundary, tests +that fail before and pass after the fix, credential/ruleset state verified, +protected-main consumer evidence, recovery/rollback exercised, threat-model and +ADR impact reviewed, and a durable issue/PR/run record. Any missing condition +keeps the incident in mitigated or monitoring state. diff --git a/docs/automation/OPERABILITY.md b/docs/automation/OPERABILITY.md new file mode 100644 index 000000000..352c2ec65 --- /dev/null +++ b/docs/automation/OPERABILITY.md @@ -0,0 +1,104 @@ +# Operability: CWL automation control plane + +Status: normative service-level and operating contract. Targets apply after the +relevant signal is measurable; absence of telemetry is reported as unknown, +not success. + +## Service model + +The control plane is an asynchronous governance service. It is healthy when it +turns eligible events into current, attributable evidence and safe guarded +actions while continuing unrelated lanes during local deferrals. It is not +measured by the number of comments, retries, polls, or commits produced. + +## SLIs and targets + +| SLI | Measurement | Policy target | +|---|---|---| +| Snapshot integrity | decisions whose repository/PR/source-head/base/live-base tuple is complete and current | 100% | +| Guarded mutation | writes/merges with a freshly verified expected head and legitimate authority | 100% | +| Evidence attribution | required evidence records with producer, type, revision, and run identity | 100% | +| Secret containment | published logs/artifacts with no credential disclosure | 100% | +| Work conservation | eligible runs with safe writable work that produce one substantive verified mutation | 100% | +| Review dispatch latency | eligible non-deferred review requests dispatched within two scheduler sweep periods (30 minutes at the current fifteen-minute cadence) | at least 99% over 30 days | +| Protected-main acceptance | central operational fixes with real-consumer evidence before closure | 100% | +| Fleet enrollment visibility | enrolled repositories covered by a current read-only audit | at least 99% over 24 hours | + +The error budget never permits bypassing identity, integrity, secret, +independent-review, ruleset, or final head-match controls. Availability targets +may instead defer model-dependent work or reduce concurrency. + +## Required telemetry + +Every run or handoff should emit bounded structured fields: + +- automation mode, run/job/attempt, repository, PR, source ref and head; +- base branch, observed live-base SHA, snapshot time, and dependency identity; +- evidence type, producer, authority class, conclusion, and freshness; +- retry class, attempt/budget, deferral reason, next eligible trigger, and + handoff owner; +- writer-lease key and lease result for mutation paths; +- expected-head result, merge/release/deploy actor, and final transaction id; +- consumer repository/run identity for operational acceptance. + +Credential values, raw authorization headers, installation tokens, and +unbounded command output are never telemetry fields. + +## Dashboards and alerts + +Operators need views by repository and workflow for queue age, deferred reason, +provider health, API error class, stale-evidence rejection, writer contention, +ruleset drift, unresolved review state, redaction events, and acceptance debt. +Alerts should fire on unauthorized mutation attempts, credential disclosure, +protected-ref surprises, repeated permanent failures, fleet-wide dispatch +failure, audit drift, or an accepted incident without consumer evidence. + +One pending PR or provider wait does not page the fleet. Repeated local failure +creates an issue/handoff with exact identity; widespread or security-impacting +failure activates [INCIDENT_RUNBOOK.md](INCIDENT_RUNBOOK.md). + +## Queue and capacity behavior + +Queue identity is `(repository, PR, exact head, dependency state, operation)`. +After one fresh observation, an unchanged external wait is deferred. The loop +continues a different repository, branch, PR, product gap, documentation gap, +or read-only audit lane. An unchanged item is revisited only after external +state changes, another substantive acceptance/mutation occurs, or a final +sweep is due. + +Mention invocation artifacts currently provide at-most-once forwarding for 30 +days. A claim written before a failed forward is a dead-letter, not completed +work; the operator uses a new trusted comment until the recoverable claim state +tracked in `ContextualWisdomLab/.github#893` is implemented. + +Provider pools have attempt and wall-clock budgets. Capacity exhaustion does +not synthesize review evidence. GitHub API backoff honors classified transient +responses while permanent authorization or integrity errors surface at once. + +## Release, rollback, and acceptance + +Central changes use exact-head PR gates, protected-default-branch integration, +and staged real-consumer exercise. Rollback selects a known-good protected +revision through a reviewed revert or explicit caller pin; it never force-moves +protected refs or disables rulesets as a convenience. + +After rollback, verify event delivery, evidence attribution, redaction, +expected-head behavior, and one representative consumer. Reopen the incident +if the recovery path only passes in the central repository. + +## Ownership and handoff + +Each deferred or incident item records the current owner, exact target, evidence +already collected, last material state change, next valid trigger, and the +action that remains unauthorized or unsafe. Handoffs cannot transfer a writer +lease implicitly. A new actor acquires and revalidates ownership before write. + +## Routine reviews + +- Each run: current queue identity, lease, evidence freshness, and useful + diagnostics. +- Daily: fleet enrollment/drift and prolonged deferrals. +- Weekly: provider/retry distributions, stale-evidence rejection, acceptance + debt, and expiring exceptions. +- After every incident or architecture change: SLI definition, threat model, + runbook, test matrix, and ADR supersession review. diff --git a/docs/automation/PRD.md b/docs/automation/PRD.md new file mode 100644 index 000000000..5654dcfcf --- /dev/null +++ b/docs/automation/PRD.md @@ -0,0 +1,133 @@ +# Product requirements: CWL automation control plane + +Status: approved product contract; implementation states are tracked in +[DOCUMENTATION_COVERAGE.md](DOCUMENTATION_COVERAGE.md). + +## Product statement + +The `ContextualWisdomLab/.github` repository is the organization control plane +that turns repository evidence into safe review, repair, merge, security, and +operational decisions without forcing every product repository to copy +privileged automation. It must keep each product independently operable while +providing a consistent, auditable organization-wide governance layer. + +## Users and stakeholders + +- **Repository maintainer:** needs actionable review and a safe path from an + open PR to a protected merge. +- **Contributor:** needs deterministic gates, useful diagnostics, and no + mutation of a head that was not reviewed. +- **Independent reviewer:** needs exact revision evidence and a clear boundary + between advisory model output and counted approval. +- **Security and compliance operator:** needs least privilege, provenance, + incident evidence, and controls compatible with SOC 2 and CSAP preparation. +- **Product owner and buyer:** needs reliable fleet governance that reduces + integration risk without coupling products to a single runtime. +- **Automation agent:** needs a work-conserving queue, explicit authority, and a + durable handoff instead of reconstructing decisions from conversation. + +## Problems + +1. PR evidence becomes stale when the source head or live base moves. +2. Checks, statuses, model verdicts, formal reviews, merge authority, and + release authority can be mistaken for one another. +3. Privileged workflows copied into leaf repositories drift and widen the + attack surface. +4. Long model reviews, provider outages, approvals, and CI queues tempt an + automation loop to idle or report instead of doing other safe work. +5. Incident closure is often declared at source merge even though a real + protected-main consumer path has not been exercised. +6. Durable decisions were scattered across large audit files, PR bodies, and + conversations, leaving no complete ADR/PRD/TRD/UML/ERD spine. + +## Product modes + +### PR maintenance mode + +The loop consumes open PRs in dependency order: refetch exact state, review all +current feedback, reproduce valid defects, apply the narrowest safe repair, +verify the new exact head, resolve only addressed threads, and merge only when +repository policy is satisfied. A queued check, provider cooldown, or approval +wait defers only that PR/head. + +### Product development mode + +When no existing PR or issue can safely advance, the loop selects one bounded +buyer-visible or operator-visible control-plane gap from the live protected +main, implements it test-first, documents its durable contracts, opens one +reviewable PR, and returns to the PR drain. Product development must not create +a competing writer for an active branch. + +The hourly schedule is continuation capacity, not permission to stop after one +action. External task scheduling is an orchestration concern; repository truth +is expressed through issues, branches, PRs, tests, and protected-main evidence. + +## Functional requirements + +| ID | Requirement | +|---|---| +| PRD-001 | Bind every decision to target repository, PR number, exact source head, source ref, base branch, and independently observed current live-base state. | +| PRD-002 | Keep central privileged implementations in `.github`; leaf repositories use required workflows or thin explicit callers. | +| PRD-003 | Separate check evidence, status evidence, workflow evidence, formal review evidence, merge authority, release authority, and deployment authority. | +| PRD-004 | Fail closed on malformed identity, untrusted workflow provenance, missing required evidence, credential ambiguity, and stale review evidence. | +| PRD-005 | Permit bounded retries only for classified transient failures; integrity, authentication, authorization, TLS, ref, and policy failures do not receive blind retries. | +| PRD-006 | Enforce a single writer lease per repository branch and a separate read-only fleet-audit role. | +| PRD-007 | Preserve independent counted non-author approval where rulesets require it; automated reviews remain advisory unless GitHub records an eligible formal review. | +| PRD-008 | Treat CI/review waiting as a local deferred state and continue a different safe queue lane. | +| PRD-009 | Require protected-main and real consumer evidence before operational incident closure. | +| PRD-010 | Use explicit minimal secret contracts, short-lived OIDC/App credentials where supported, and `NVIDIA_NIM_API_KEY` only at the actual model-call boundary. | +| PRD-011 | Preserve useful failure and timeout diagnostics while redacting credential-shaped evidence before it reaches logs or summaries. | +| PRD-012 | Maintain 100% owned production statement, branch, and public-docstring coverage with realistic security, concurrency, rollback, and consumer tests. | +| PRD-013 | Keep the canonical documentation graph code-current and machine-checkable. | + +## Buyer and operator outcomes + +- A buyer can identify which component has authority for review, mutation, + merge, release, and incident closure without reading implementation code. +- A maintainer sees the first failing boundary and a concrete rerun or repair + path instead of a URL-only failure. +- A compromised or stale PR head cannot reuse predecessor evidence. +- A provider outage reduces review capacity but does not stop unrelated safe + work or silently relax governance. +- A central repair is not called deployed until at least one enrolled consumer + proves the protected-main path. + +## Acceptance and closure criteria + +1. Every mutation uses a freshly revalidated target identity and a head guard. +2. Required evidence on the unchanged exact head is present, complete, and + authoritative for its own gate; queued, skipped, neutral-required, + cancelled, absent, predecessor, or synthetic-only evidence is non-passing. +3. Zero valid unresolved review or security findings remain. +4. Ruleset-required independent approval is a counted eligible formal review. +5. Source tests, security gates, provenance/SBOM gates, and documentation + contracts pass on the exact head. +6. Operational fixes have protected-main consumer evidence with run/job and + target revision identity. +7. Rollback and incident-reopen conditions are documented before closure. + +## Degraded behavior + +- Missing model credentials or provider exhaustion produces no synthetic + approval. Deterministic gates continue; model-dependent work is deferred. +- Missing cross-repository authority prevents only the cross-repository action. + Same-repository and read-only work continue. +- GitHub API transient failures use bounded retry/fallback paths. Permanent + permission, identity, integrity, TLS, and ref failures surface immediately. +- Queue saturation never converts pending work into success and never justifies + a meaningless commit to retrigger review. +- A logging redactor that cannot safely transform evidence fails closed at the + publication boundary; it does not change the command that was executed. + +## Non-goals + +- Replacing GitHub branch protection, rulesets, release environments, or the + formal review model. +- Giving an LLM merge, release, or deployment authority by virtue of its + textual verdict. +- Maintaining thick, synchronized copies of central workflows in every product. +- Masking all PII indiscriminately. Access control, purpose limitation, + retention, auditability, and bounded disclosure are preferred controls where + masking would make the work unusable. +- Treating documentation, a check mark, or a source merge as operational proof + by itself. diff --git a/docs/automation/README.md b/docs/automation/README.md new file mode 100644 index 000000000..c3814c98f --- /dev/null +++ b/docs/automation/README.md @@ -0,0 +1,77 @@ +# CWL automation control-plane documentation + +Status: authoritative documentation index for `ContextualWisdomLab/.github`. + +This directory is the canonical, code-reviewable documentation spine for the +organization automation control plane. It describes what is implemented on the +protected default branch, what is policy, and what remains pending in an open +pull request. Conversation history, automation prompts, PR bodies, and check +summaries may provide evidence, but they do not replace this spine. + +## Reading order + +| Document | Question answered | +|---|---| +| [README.md](README.md) | Which document is authoritative? | +| [PRD.md](PRD.md) | Who needs the control plane and what outcomes must it deliver? | +| [TRD.md](TRD.md) | What are the technical contracts and authority boundaries? | +| [ARCHITECTURE.md](ARCHITECTURE.md) | Where are the bounded contexts and trust boundaries? | +| [UML.md](UML.md) | How do the main interactions and state transitions work? | +| [ERD.md](ERD.md) | What evidence identities and relationships must be preserved? | +| [SECURITY.md](SECURITY.md) | What security invariants bind implementation? | +| [THREAT_MODEL.md](THREAT_MODEL.md) | Which threats, controls, and residual risks exist? | +| [TEST_STRATEGY.md](TEST_STRATEGY.md) | What proves a change at source, exact head, and protected main? | +| [OPERABILITY.md](OPERABILITY.md) | Which SLIs, SLOs, and operating signals matter? | +| [INCIDENT_RUNBOOK.md](INCIDENT_RUNBOOK.md) | How is an automation incident classified, contained, and closed? | +| [TRACEABILITY.md](TRACEABILITY.md) | Which requirements, ADRs, code, tests, incidents, and standards connect? | +| [DOCUMENTATION_COVERAGE.md](DOCUMENTATION_COVERAGE.md) | What was missing and how complete is this documentation set? | +| [adr/README.md](adr/README.md) | Which durable decisions govern the control plane? | + +The focused [review-agent comment invocation](review-agent-comment-invocation.md) +document remains authoritative for the `@cwl-noema-review` and +`@opencode-agent` comment path. It is subordinate to the shared identity, +authority, security, and operability contracts in this directory. + +## Truth labels + +- **Implemented** means the behavior exists on the protected default branch and + is backed by a named workflow, script, or test. +- **Pending** means a linked open PR contains the candidate implementation. It + is not operational truth until protected-main acceptance passes. +- **Policy** means the requirement governs future and current changes even when + no persistence or enforcement mechanism exists yet. +- **Conceptual** means the domain object is required for reasoning and + traceability but is not necessarily stored in a database. + +The documents were baselined against protected main at +`6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba` on 2026-08-09. That SHA is dated +audit evidence, not a timeless architecture constant. + +## Compact glossary + +| Term | Meaning | +|---|---| +| exact head | immutable source commit to which evidence is bound | +| live base | target branch tip independently resolved at the decision boundary | +| evidence | typed observation with producer, revision, provenance, and conclusion | +| authority | legitimate permission and eligibility to perform a named action | +| writer lease | logical exclusive source-write ownership for one repository/branch | +| deferred item | exact queue item waiting on an external state change or authority | +| operational acceptance | real enrolled consumer proof from protected-main central code | + +## Ownership + +The CWL automation maintainers own this spine. Workflow owners update it in the +same PR that changes a governed boundary. Security owners review credential, +trust, provenance, and incident-policy changes; repository maintainers retain +leaf product/build/release ownership. ADR status never substitutes for an +eligible code review or protected-main acceptance. + +## Change rule + +A change that alters triggers, workflow provenance, snapshot identity, +evidence authority, permissions, secrets, retries, concurrency, writer +ownership, reviewer eligibility, merge/release authority, or incident closure +must update the applicable PRD/TRD/architecture/UML/ERD/ADR/traceability files +in the same bounded change. A roadmap behavior must never be described as +implemented until the protected default branch and a real consumer run prove it. diff --git a/docs/automation/SECURITY.md b/docs/automation/SECURITY.md new file mode 100644 index 000000000..640f0dfce --- /dev/null +++ b/docs/automation/SECURITY.md @@ -0,0 +1,177 @@ +# Security contract: CWL automation control plane + +Status: normative security policy; implementation evidence is linked from +[TRACEABILITY.md](TRACEABILITY.md). + +## Security objectives + +1. A PR-controlled value cannot select privileged workflow code, widen a job's + permissions, expose a credential, or authorize a mutation. +2. Every check, review, branch update, merge, release, and incident-closure + claim is bound to its real producer, target repository, and exact revision. +3. A single compromised provider, runner, model response, or leaf repository + cannot silently obtain fleet-wide write authority. +4. Failures preserve useful diagnostics without publishing authentication + material or credential-shaped command output. +5. Supply-chain artifacts and dependencies remain attributable, reproducible, + and reviewable. + +## Non-negotiable invariants + +- PR text, issue text, review comments, workflow inputs, model output, archives, + patches, and target source are untrusted data. +- A `pull_request_target` bootstrap may inspect metadata but must not execute + PR-controlled code while holding a secret or write permission. +- Privileged workflow code comes from a protected default branch. Repository, + ref, SHA, PR number, source head, base branch, and current live-base identity + are revalidated after every trust-boundary crossing. +- A textual verdict is never merge authority, release authority, deployment + authority, or ruleset approval. The corresponding eligible GitHub object and + all independent gates must exist. +- The branch writer lease is exclusive for `(repository, branch)`. The fleet + auditor is read-only and cannot borrow a writer credential. +- Identity, integrity, authorization, TLS, checksum, signature, and ref + failures fail closed. Retrying them without a material state change is + prohibited. + +## Identity and authorization + +| Actor | Minimum authority | Explicitly excluded | +|---|---|---| +| Required-workflow bootstrap | PR metadata and check publication | PR source execution, branch write, merge | +| Strix | exact-head evidence publication | formal approval, merge, release | +| OpenCode and Noema adapters | bounded review publication under named identity | unilateral counted approval or merge | +| Merge scheduler | dispatch plus guarded branch/merge action for one target | release or deployment | +| Autofix worker | guarded same-repository PR-branch write | protected-base write, cross-repository branch mutation | +| Fleet auditor | organization/repository policy read | dispatch, review, source write, merge | +| Release/deploy workflow | environment-scoped release or deployment | retroactive review evidence | + +Repository selection and requested action are authorization inputs, not merely +strings. Allow-list membership, installation scope, actor association, event +origin, and live repository state are checked before a privileged dispatch. +Self-authored, bot-authored, dismissed, stale, or otherwise ineligible reviews +do not satisfy an independent-approval rule. + +## Permission and secret contract + +- Workflows default to `contents: read`; each job declares only the permissions + required for its API calls. +- `id-token: write` is scoped to the job that exchanges OIDC identity. GitHub + App installation tokens are short lived and target-repository scoped. +- Long-lived compatibility credentials are separated by purpose. In + particular, review publication, branch mutation, merge, release, and deploy + credentials are not interchangeable. +- `NVIDIA_NIM_API_KEY` is materialized only for an actual model request after + deterministic eligibility and identity gates pass. +- `COPILOT_GITHUB_TOKEN` is not an accepted development-agent credential. +- Reusable workflows declare named secret inputs. Blanket `secrets: inherit` + is a documented migration risk, including the current caller guidance for + `.github/workflows/deploy-pages.yml`. +- Secrets are never passed through PR-authored command lines, persisted in + artifacts, included in model prompts, or echoed for debugging. + +Credential rotation invalidates the affected installation/session, updates the +smallest secret scope, and exercises a non-destructive authenticated probe +before source mutation resumes. A failed probe does not trigger broader-token +fallback automatically. + +## Secret and sensitive-configuration registry + +This table is value-free. It covers the complete union of `secrets.*` names in +tracked workflows at the audited revision. “Optional” means the named feature +degrades or uses its documented narrower alternative; it does not permit a +synthetic success. + +| Name | Consumer and minimum scope | Requirement and owner | Rotation or revocation | +|---|---|---|---| +| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare DNS/Pages account identifier | required for Cloudflare action; infrastructure owner | update on account migration; remove from runners/callers when retired | +| `CLOUDFLARE_API_TOKEN` | Cloudflare DNS/Pages token limited to managed zones/projects | required for apply/deploy; infrastructure owner | revoke on exposure/role change; replace and dry-run before apply | +| `GCP_SA_KEY` | Vertex provider service-account JSON | optional provider path; security/model owner; long-lived migration risk | revoke key immediately on exposure and prefer workload identity/OIDC | +| `NOEMA_GITHUB_APP_PRIVATE_KEY` | Noema App token minting for target repository | optional when approved OIDC/PAT path exists; review-platform owner | rotate App key, revoke installations, verify one non-destructive review probe | +| `NOEMA_LLM_API_KEY` | Noema model endpoint only | optional model path; review-platform owner | rotate at provider and verify bounded model call; no broader fallback on auth error | +| `NOEMA_REVIEW_TOKEN` | Noema review publication compatibility token | optional App/OIDC fallback; review-platform owner | revoke on exposure or eligibility change; audit review objects since exposure | +| `NVIDIA_NIM_API_KEY` | OpenCode/Noema/Strix NVIDIA NIM model-call steps | optional provider path; model-platform owner | rotate at NVIDIA; never accept legacy `NVIDIA_API_KEY` as a workflow secret fallback | +| `OPENAI_API_KEY` | OpenCode/Noema/Strix OpenAI-compatible model calls | optional provider path; model-platform owner | rotate at provider; test only provider-scoped step and inspect egress logs | +| `OPENCODE_APPROVE_TOKEN` | review publication and scheduler compatibility mutation | optional App/merge-token fallback; automation owner | revoke on exposure/scope change; audit reviews/writes and prefer scoped App/token | +| `OPENCODE_ZEN_API_KEY` | OpenCode Zen model-call step | optional provider path; model-platform owner | rotate at provider and verify bounded model call | +| `OPENROUTER_API_KEY` | OpenRouter model-call step | optional provider path; model-platform owner | rotate at provider and verify bounded model call | +| `PR_REVIEW_MERGE_TOKEN` | cross-repository scheduler/router/autofix/SBOM read-write actions | required only for uncovered cross-repository operations; automation owner | revoke on exposure or repository-scope change; audit every affected target and replace narrowly | +| `SBOM_INVENTORY_TOKEN` | organization repository read for SBOM aggregation | optional App-token fallback; supply-chain owner | revoke on exposure; verify read-only inventory scope before resuming | +| `STRIX_GITHUB_MODELS_TOKEN` | GitHub Models call for Strix/OpenCode evidence | optional provider token; security/model owner | revoke on exposure; fall back only to an independently configured provider within budget | +| `STRIX_OPENAI_API_KEY` | Strix-specific OpenAI provider call | optional provider token; security owner | rotate at provider and verify Strix model boundary | +| `VERTEX_LOCATION` | Vertex region value stored through the secret context, not authentication material | optional configuration; model-platform owner; migrate to a non-secret variable | update on region/policy change; removal does not require credential rotation | + +Every new `secrets.*` name updates this registry in the same change. Each entry +must identify consumer, minimum scope, required/optional behavior, owner, and +rotation/revocation. Configuration identifiers should migrate to `vars` or an +approved configuration store when secrecy is unnecessary. + +## Execution and egress controls + +`scripts/ci/sandboxed_verify.py` and `scripts/ci/sandboxed_web_e2e.py` start +with a credential-scrubbed environment. Network access and each environment +variable are explicit exceptions supported by an evidence note. Scratch +worktrees and service processes are bounded to the runner lifecycle. + +External providers receive only the minimum source/evidence slice required for +the requested review. Provider output is untrusted and is parsed with size, +schema, timeout, and authority limits. Provider failure never yields synthetic +approval and does not relax deterministic gates. + +## Logging, privacy, and retention + +Structured records retain repository, PR, workflow/run/job identity, source +head, live base, evidence class, producer, conclusion, attempt, and bounded +failure detail. Before output reaches logs, summaries, comments, or artifacts, +credential-shaped values in stdout, stderr, service output, and timeout tails +are transformed by the publication boundary. + +The stronger subprocess-log redaction implementation is pending in +`ContextualWisdomLab/.github#842`; it is not represented as protected-main +behavior. Until it lands and passes consumer acceptance, operators must treat +raw command output as sensitive and restrict audience and retention. + +PII is controlled through purpose limitation, least-privilege audience, +minimized collection, bounded retention, encrypted transport/storage, and +auditable access. Indiscriminate masking is not required when it would destroy +necessary review or incident context. Authentication material is always +secret, regardless of whether it is personal data. + +## Supply-chain controls + +- Workflow actions are pinned according to repository policy and privileged + definitions are sourced from protected refs. +- Python bootstrap dependencies use hash-locked requirements and trusted + materialization paths. Integrity failures are permanent failures. +- CodeQL, secret scanning, OSV/dependency scanning, Scorecard, SBOM generation, + and scheduled security workflows remain separate evidence producers; none is + inferred from another producer's success. +- A skipped or neutral job is not authoritative Strix scan evidence. The + current fail-closed terminal-gate gap is tracked in + `ContextualWisdomLab/.github#891` and remains an operational risk until + protected-main consumer acceptance. +- Generated SBOM/provenance evidence identifies the source revision and build + invocation. Missing or predecessor-revision artifacts are non-passing when + required. +- A central change is staged through its own exact-head checks and a real + protected-main consumer before fleet-wide operational closure. + +## Vulnerability and exception handling + +Confirmed secret exposure, unauthorized write, evidence forgery, protected-ref +movement, or cross-repository authority escape is a security incident. Follow +[INCIDENT_RUNBOOK.md](INCIDENT_RUNBOOK.md), preserve audit evidence, rotate only +affected credentials, and reopen every dependent acceptance claim. + +A temporary policy exception names the owner, exact repository/ref, rationale, +expiry, compensating control, detection signal, and rollback. It cannot waive +exact-head binding, protected workflow provenance, legitimate reviewer +eligibility, or final head-match protection. + +## Verification + +Security changes require realistic negative tests for malformed identity, +stale heads, unauthorized actors, missing/over-broad credentials, malicious +output, timeout tails, replay, concurrency, and cross-repository scope. The +exact test strategy and protected-main evidence requirements are defined in +[TEST_STRATEGY.md](TEST_STRATEGY.md). diff --git a/docs/automation/TEST_STRATEGY.md b/docs/automation/TEST_STRATEGY.md new file mode 100644 index 000000000..ae2a58897 --- /dev/null +++ b/docs/automation/TEST_STRATEGY.md @@ -0,0 +1,109 @@ +# Test strategy: CWL automation control plane + +Status: normative verification and acceptance policy. + +## Test objectives + +Tests must prove that the control plane preserves exact identity, authority, +least privilege, fail-closed behavior, useful diagnostics, and protected-main +consumer operation under success, adversarial, failure, recovery, concurrency, +and rollback conditions. A passing happy path alone is insufficient. + +## Coverage contract + +- Every changed executable production statement and branch is exercised; new + behavior has 100% owned line and branch coverage. +- Every changed public Python callable satisfies the repository docstring gate. +- Coverage thresholds cannot be lowered, exclusions broadened, or test files + omitted to make a PR pass. +- Generated/vendor code and genuinely unreachable platform branches require a + narrowly documented owner-reviewed exception rather than silent exclusion. +- Documentation-only changes have 100% coverage of their declared contract + through `tests/test_automation_documentation_contract.py`, link resolution, + Mermaid structure checks, and repository-native source checks. + +Coverage is evidence for executed paths, not proof of correctness. Realistic +assertions, mutation-sensitive negative cases, and operational acceptance are +also required. + +## Test layers + +| Layer | Scope | Required evidence | +|---|---|---| +| Static contract | YAML, shell, Python, path/ref/input schemas, docs links, Mermaid fences | syntax/compile success and explicit contract assertions | +| Unit | parsers, classifiers, retries, evidence binding, redaction, authority decisions | deterministic boundary and error-path assertions | +| Property/fuzz | repository/ref/SHA, payload, output, status, archive, and token-shaped inputs | invariants hold across generated malformed/adversarial values | +| Component | workflow helper plus mocked GitHub/provider boundary | exact requests, permissions, idempotency, and bounded fallback | +| Integration | central workflow against representative repository fixtures | event-to-evidence behavior on exact source and live base | +| Concurrency | two workers, head movement, cancellation, lease loss | one writer, stale worker abort, no valid current evidence lost | +| Security | untrusted source/output, credential scope, replay, injection, redaction | fail-closed result with useful non-secret diagnostics | +| Recovery | transient outage, permanent failure, rollback, partial publication | classified retry/defer/reopen behavior and retained identity | +| Consumer acceptance | protected-main central workflow in an enrolled real repository | run/job, target, source head, live base, conclusion, recovery evidence | + +## Required scenario matrix + +Each decision or mutation path covers: + +1. current exact head and current live base; +2. source head moves before and after materialization; +3. base moves after the PR snapshot; +4. missing, queued, cancelled, skipped, neutral, failed, predecessor, and + synthetic evidence; +5. eligible, ineligible, self, bot, dismissed, stale, and change-request + reviews; +6. transient API/provider failure versus permanent identity, integrity, + authorization, TLS, and policy failure; +7. absent, narrowly scoped, expired, and over-broad credentials; +8. same-repository versus fork and cross-repository source refs; +9. normal, malicious, oversized, timeout, and credential-shaped output; +10. two competing workers plus a read-only fleet auditor; +11. rollback to the previous protected implementation; +12. real protected-main consumer success and failure. + +## RED, GREEN, refactor + +A behavioral fix starts with the smallest realistic failing test that +reproduces the defect. The test must fail for the intended reason. The narrowest +implementation makes it pass, after which the full affected matrix and full +repository suite run. Refactoring occurs only while those tests remain green. +Production code is not weakened to accommodate a mock that contradicts GitHub +or provider semantics. + +## Evidence commands + +The baseline documentation contract is runnable without third-party test +dependencies: + +```bash +python3 -m unittest tests.test_automation_documentation_contract +``` + +The repository suite and owned coverage use the pinned project environment: + +```bash +coverage run -m pytest tests +coverage report --show-missing +python3 -m compileall -q scripts tests +git diff --check +``` + +Repo-native shell contract tests and workflow-specific validators run when +their owned files change. Sandboxed product or web execution uses +`scripts/ci/sandboxed_verify.py` or `scripts/ci/sandboxed_web_e2e.py` and cites +the structured result marker. + +## Merge and operational gates + +A PR is not test-complete until the exact current head has all required tests, +security/provenance evidence, eligible review, and no valid unresolved finding. +A central operational repair is not incident-complete at merge: a protected-main +real-consumer run must exercise the changed boundary. Queue or provider waits +are deferred states and never converted into passing evidence. + +## Test data and evidence hygiene + +Fixtures use synthetic repositories, refs, actors, token shapes, and logs; real +secrets and customer content are prohibited. Failure output is bounded and +redacted at the publication boundary. Retained test evidence records the test +command, exact revision, environment contract, result, and relevant artifact or +run identity. diff --git a/docs/automation/THREAT_MODEL.md b/docs/automation/THREAT_MODEL.md new file mode 100644 index 000000000..e92442da3 --- /dev/null +++ b/docs/automation/THREAT_MODEL.md @@ -0,0 +1,91 @@ +# Threat model: CWL automation control plane + +Status: living STRIDE-informed model for the architecture in +[ARCHITECTURE.md](ARCHITECTURE.md). Review on every trust-boundary, authority, +credential, trigger, or persistence change. + +## Scope and assets + +The model covers central workflow definitions and helpers, GitHub events and +API objects, target PR source, model/provider calls, runner workspaces, +credentials, logs/artifacts, branch updates, merges, releases, and +protected-main operational evidence. + +The primary assets are protected refs, exact-head evidence, independent review +state, GitHub App/OIDC/PAT credentials, release/deploy authority, workflow +provenance, audit history, and the availability of the work-conserving queue. + +## Actors and assumptions + +- Trusted maintainers can approve policy changes but may make mistakes or have + a compromised account. +- Contributors and fork authors control PR source and most PR metadata. +- GitHub supplies identity, refs, checks, reviews, rulesets, and merge + transactions but its API may be delayed or unavailable. +- Model providers and model output are fallible external dependencies, not + authorities. +- Runners are ephemeral processing environments and may handle hostile source + or output. +- Leaf repositories are independently operated security domains. + +## Trust boundaries + +| Boundary | Untrusted input | Required control | +|---|---|---| +| PR to protected bootstrap | source, metadata, comment instructions | metadata-only validation; no privileged PR code execution | +| Bootstrap to dispatch | repository/ref/SHA/action envelope | strict schema, allow-list, live re-fetch, idempotency | +| Central workflow to leaf source | archive/worktree and repo-native commands | protected workflow provenance, sandbox, scoped environment | +| Runner to model provider | bounded prompt/source/evidence | minimal egress, secret isolation, timeout, output validation | +| Evidence to decision | checks, statuses, reviews, artifacts | type/producer/revision separation and eligibility checks | +| Decision to write/merge | target repository/ref/head | writer lease, live-base refresh, expected-head guard | +| Output to observer | stdout, stderr, summaries, artifacts | bounded diagnostics and publication-boundary redaction | +| Central main to fleet | reusable/required workflow revision | staged acceptance in a real enrolled consumer | + +## Threat register + +| ID | STRIDE | Threat and impact | Prevent/detect/recover control | Residual risk | +|---|---|---|---|---| +| TM-001 | Spoofing | A comment or dispatch claims a trusted actor/repository. | Verify GitHub actor association, installation scope, event origin, target allow-list, and live repository identity; retain actor/run evidence. | A compromised eligible account remains possible; revoke and rotate on detection. | +| TM-002 | Tampering | A force-push or base movement reuses predecessor checks/reviews. | Exact source-head and independently observed live-base binding; expected-head and `--match-head-commit` guards; invalidate stale evidence. | State can change between reads; the final atomic GitHub guard is authoritative. | +| TM-003 | Tampering | PR source changes the privileged workflow or executes during `pull_request_target`. | Use protected-base workflow code; metadata-only bootstrap; dispatch privileged work from protected default branch. | A defect already merged to central main has fleet impact; staged consumer acceptance limits closure. | +| TM-004 | Repudiation | An agent claims it tested, approved, or deployed without attributable evidence. | Record producer, run/job, exact revision, command/result, review identity, and consumer target; separate evidence types. | Third-party telemetry retention can expire; preserve bounded durable receipts. | +| TM-005 | Information disclosure | Credentials appear in subprocess output, timeout tails, artifacts, or model prompts. | Credential-scrubbed execution, minimal prompt, audience/retention controls, and publication-boundary redaction; pending hardening is tracked in `.github#842`. | Novel token shapes may evade pattern matching; rotate and expand fixtures after incident. | +| TM-006 | Information disclosure | Blanket reusable-workflow inheritance exposes unrelated secrets. | Named secret interfaces, job-scoped materialization, OIDC/App tokens, secret-use tests. | Existing `secrets: inherit` caller guidance is a migration risk. | +| TM-007 | Denial of service | Provider timeouts, API throttling, or one queued PR stalls the fleet loop. | Classified bounded retries, provider budget/fallback, per-item deferral, and work-conserving queue lanes. | A platform-wide outage can pause all GitHub-dependent work; preserve queue identity. | +| TM-008 | Denial of service | Stale work cancels or serializes current-head evidence. | Head-aware concurrency, safe cancellation rules, and current-head refetch before decision. | Runner allocation remains externally controlled. | +| TM-009 | Elevation of privilege | Advisory model output is treated as counted approval or merge authority. | Formal review eligibility, ruleset enforcement, independent non-author review, and separate guarded merger identity. | Misconfigured rulesets can weaken the outer gate; read-only fleet audit detects drift. | +| TM-010 | Elevation of privilege | A fleet auditor or repair worker gains broader write scope. | Role-specific credentials, read-only auditor policy, same-repository repair constraint, writer lease, scope tests. | Over-broad long-lived compatibility tokens remain higher impact until migrated. | +| TM-011 | Supply chain | A dependency/action/archive is replaced or fetched without integrity. | Protected workflow provenance, pinned actions, hash-locked dependencies, digest/SBOM/provenance checks, fail-closed materialization. | Upstream compromise before pinning or signing is still possible; monitor and rotate pins. | +| TM-012 | Injection | PR text or model output injects shell, YAML, path, summary, or GitHub output content. | Structured arguments, syntax validators, output sanitization, path/ref validation, no `eval`, adversarial fixtures. | Native repository test commands still execute target code only inside the bounded runner. | +| TM-013 | Replay | A previously valid dispatch, receipt, or handoff is replayed for a new state. | Idempotency key plus exact snapshot identity; reject completed/predecessor dispatch; record receipt authority. | A replay against an unchanged snapshot may be harmless but still consumes capacity. | +| TM-014 | Integrity | Malformed status/check names or synthetic success hide a missing required gate. | Treat check, status, workflow, review, dependency, and operational evidence as distinct typed records; required absent/neutral/skipped states fail closed. | Ambiguous repository policy needs an explicit mapping and audit. | + +## Abuse cases that must remain executable tests + +1. A fork changes workflow code and embeds a secret-print command. +2. A current-head approval exists, then the source head changes before merge. +3. The PR API exposes an old base SHA while the protected base ref advances. +4. A bot posts approval prose without an eligible formal review. +5. A provider returns a credential-shaped string in stderr or only after a + timeout. +6. Two workers acquire the same repository/branch candidate concurrently. +7. A leaf repository asks a central dispatcher to mutate another repository. +8. A permanent checksum or TLS failure is mislabeled transient. +9. A protected-main source merge is called resolved without a consumer run. +10. A malicious output tries to forge GitHub output, Markdown, or summary + boundaries. + +## Risk treatment + +High-impact threats to protected refs, credentials, reviewer eligibility, or +cross-repository scope require preventive fail-closed controls and detection. +Availability risks may degrade by deferring only the affected item. Residual +risks are accepted only through the time-bounded exception process in +[SECURITY.md](SECURITY.md); an LLM cannot accept risk on behalf of an owner. + +## Review triggers + +Re-evaluate this model when a new trigger, reusable workflow, provider, token +class, persistence store, deployment target, release path, auto-repair mode, or +evidence type is introduced; when GitHub changes event or token semantics; and +after every security or protected-main acceptance incident. diff --git a/docs/automation/TRACEABILITY.md b/docs/automation/TRACEABILITY.md new file mode 100644 index 000000000..62d2de6f9 --- /dev/null +++ b/docs/automation/TRACEABILITY.md @@ -0,0 +1,129 @@ +# Traceability: requirements, decisions, implementation, and evidence + +Status: authoritative map; implementation state is relative to protected main +`6eb06cdd08c79a06f7b390069d4ffa49e2eb7dba` observed on 2026-08-09. + +## Requirement-to-evidence matrix + +| Requirement | Governing decision | Implementation/evidence surface | Verification and state | +|---|---|---|---| +| PRD-001 exact source/live-base binding | [ADR-0002](adr/0002-exact-head-and-live-base-binding.md) | `scripts/ci/pr_review_merge_scheduler.py`, `scripts/ci/pr_head_replay_guard.py` | scheduler/replay-guard tests; dispatch-envelope hardening pending in `ContextualWisdomLab/.github#840` | +| PRD-002 central control plane/thin leaf | [ADR-0008](adr/0008-central-control-plane-thin-leaf-contract.md) | required/reusable workflows and `.github/workflows/audit-central-ruleset.yml` | workflow-contract and ruleset-audit tests; implemented with documented migration edges | +| PRD-003 evidence/authority separation | [ADR-0005](adr/0005-independent-review-governance.md) | review gates, scheduler classifiers, GitHub rulesets | scheduler/review-gate tests; formal independent approval still externally supplied | +| PRD-004 fail closed | [ADR-0002](adr/0002-exact-head-and-live-base-binding.md), [ADR-0004](adr/0004-minimal-reusable-workflow-secrets.md) | validation/materialization/review helpers | adversarial, workflow, security-boundary, and integrity tests | +| PRD-005 bounded classified retry | [ADR-0003](adr/0003-classified-bounded-retries.md) | provider runners, GitHub API callers, dispatch/status helpers | timeout, provider exhaustion, permanent-failure, and retry-budget tests; terminal scheduler failure propagation tracked by `ContextualWisdomLab/.github#894` | +| PRD-006 one writer/read-only audit | [ADR-0001](adr/0001-writer-lease-and-read-only-fleet-auditor.md) | workflow concurrency, scheduler guards, fleet audit | concurrency and audit contract tests; lease is currently distributed across GitHub concurrency/live-head checks | +| PRD-007 eligible independent review | [ADR-0005](adr/0005-independent-review-governance.md) | formal GitHub reviews and protected rulesets | current-head reviewer eligibility; organization remediation tracked by `ContextualWisdomLab/.github#772` | +| PRD-008 work conservation | [ADR-0007](adr/0007-work-conserving-automation.md) | hourly automation prompt and queue/handoff contract | prompt updated 2026-08-09; repository persistence is policy/docs rather than a dedicated queue service | +| PRD-009 protected-main closure | [ADR-0006](adr/0006-protected-main-operational-closure.md) | real consumer workflow run and handoff record | required for incident closure; evidence is per change | +| PRD-010 minimal secrets | [ADR-0004](adr/0004-minimal-reusable-workflow-secrets.md) | explicit job permissions, App/OIDC/token selection | security-boundary/workflow tests; remaining `secrets: inherit` guidance is a migration gap | +| PRD-011 safe diagnostics | [ADR-0003](adr/0003-classified-bounded-retries.md), [SECURITY.md](SECURITY.md) | sandbox/sanitization/redaction helpers | sanitization/sandbox tests; subprocess-log hardening pending in `ContextualWisdomLab/.github#842` | +| PRD-012 complete realistic tests | [TEST_STRATEGY.md](TEST_STRATEGY.md) | `tests/`, coverage/docstring gates, sandbox helpers | changed behavior requires 100% owned statement/branch coverage plus security/concurrency/consumer cases | +| PRD-013 code-current docs | this documentation spine | `docs/automation/` and entry-point links | `tests/test_automation_documentation_contract.py` | + +## Threat-to-control trace + +| Threat | Preventive/detective contract | Required verification family | +|---|---|---| +| TM-001 actor/repository spoofing | [SECURITY.md](SECURITY.md) identity/authorization; ADR-0002 | actor association, App installation, allow-list, live-target negative tests | +| TM-002 stale source/base tampering | ADR-0002 exact source/live-base binding | force-push, base movement, predecessor evidence, final head mismatch | +| TM-003 privileged PR workflow tampering | [TRD.md](TRD.md) event trust boundary; ADR-0008 | `pull_request_target` metadata-only and protected-dispatch contract tests | +| TM-004 repudiated evidence | typed evidence in [ERD.md](ERD.md) and authority trace below | producer/run/revision/command/consumer receipt completeness | +| TM-005 credential output disclosure | [SECURITY.md](SECURITY.md) publication boundary | stdout, stderr, service-log, timeout-tail, artifact redaction fixtures | +| TM-006 inherited secret disclosure | ADR-0004 and the secret registry | workflow secret union, absence, scope, fork, and prompt-leak tests | +| TM-007 provider/API denial of service | ADR-0003 and ADR-0007 | retry budget, provider exhaustion, exact-item deferral, other-lane progress | +| TM-008 stale cancellation/serialization | ADR-0001 plus executable Strix concurrency contract | competing run, same/different event class, current-head resolution | +| TM-009 advisory-to-authority elevation | ADR-0005 | self/bot/ineligible/stale review and ruleset rejection tests | +| TM-010 auditor/repair privilege escape | ADR-0001, ADR-0004, ADR-0008 | read-only audit and same-repository repair scope tests | +| TM-011 dependency/provenance compromise | [SECURITY.md](SECURITY.md) supply-chain controls | pin/hash/digest/SBOM/attestation mismatch fail-closed tests | +| TM-012 shell/path/output injection | strict schemas and sandbox/output sanitization | ref/path/YAML/shell/GitHub-output adversarial fixtures | +| TM-013 dispatch/receipt replay | ADR-0002 identity and idempotency | exact-key duplicate, altered-field, predecessor, completion replay tests | +| TM-014 synthetic/mis-typed evidence | [TRD.md](TRD.md) evidence taxonomy | absent/skipped/neutral/cancelled/similar-name/predecessor gate tests | + +## Authority trace + +| Claim | Authoritative object | Non-authoritative substitutes | +|---|---|---| +| check evidence | named GitHub check run bound to exact revision | comment text, unrelated status, predecessor run | +| status evidence | named commit-status context and producer | check with a similar display name | +| formal review evidence | eligible GitHub review object on current head | model verdict text, check conclusion, self/bot comment | +| merge authority | protected ruleset plus guarded GitHub merge transaction | approval prose, workflow success alone | +| release authority | release workflow/environment and its scoped actor | merged PR or review approval | +| operational closure | protected-main real-consumer run with target/run/revision identity | central source merge or synthetic fixture | + +## Incident and change lineage + +- `ContextualWisdomLab/.github#840` is the open exact dispatch-envelope and + snapshot-only review-route change. Its behavior remains pending until merged + and accepted from protected main. +- `ContextualWisdomLab/.github#842` is the open replacement for + credential-shaped subprocess-log redaction. Closed, unmerged + `ContextualWisdomLab/.github#841` is historical attempt evidence, not current + implementation. +- `ContextualWisdomLab/.github#772` tracks the missing organization path for a + counted independent non-author approval. Automation cannot self-satisfy it. +- `ContextualWisdomLab/.github#889` aligns external-head policy and runtime; + `ContextualWisdomLab/.github#890` implements a shared writer lease; + `ContextualWisdomLab/.github#891` makes Strix evidence fail closed; + `ContextualWisdomLab/.github#892` aligns merge modes and mutation authority; + `ContextualWisdomLab/.github#893` makes mention claims recoverable; and + `ContextualWisdomLab/.github#894` makes material scheduler action failures + terminally non-passing after the bounded queue scan. +- `ContextualWisdomLab/naruon#974` is merged product-planning evidence and + informs the product-development lane; it does not prove this repository's + operational controls. + +## Standards alignment + +| Contract area | Primary reference | Applied interpretation | +|---|---|---| +| secure development and provenance | NIST SP 800-218, Secure Software Development Framework | protect source/build integrity, review changes, preserve provenance, and respond to vulnerabilities | +| identity and least privilege | NIST SP 800-53 Rev. 5, AC/IA/AU/SC control families | job-scoped permission, attributable actors, audit records, protected transport | +| software supply chain | SLSA v1.2 specification and GitHub artifact-attestation guidance | verify subject digest, source/builder identity, immutable workflow revision, parameters, and verifier result; generation alone is not conformance | +| CI hardening | OpenSSF Scorecard and OWASP Top 10 CI/CD Security Risks | use heuristic checks as detective signals and model CI/CD trust/credential/artifact abuse cases, not as certification | +| GitHub event trust | GitHub secure-use, token, OIDC, organization-policy, and branch-protection documentation | full-SHA pins, restricted default permissions, conditional OIDC claims, protected workflow source, current final-revision approval | +| incident response | NIST SP 800-61 Rev. 3 | analyze root cause, restore verified integrity, close by explicit criteria, and feed lessons back into controls | +| evidence retention | NIST AU/CA controls and GitHub organization audit-log documentation | protect structured audit evidence and export required history before platform retention expires | + +## References + +GitHub. (n.d.). *About protected branches*. GitHub Docs. Retrieved August 9, +2026, from +https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches + +GitHub. (n.d.). *Artifact attestations*. GitHub Docs. Retrieved August 9, 2026, +from https://docs.github.com/en/actions/concepts/security/artifact-attestations + +GitHub. (n.d.). *Disabling or limiting GitHub Actions for your organization*. +GitHub Docs. Retrieved August 9, 2026, from +https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization + +GitHub. (n.d.). *OpenID Connect reference*. GitHub Docs. Retrieved August 9, +2026, from https://docs.github.com/en/actions/reference/security/oidc + +GitHub. (n.d.). *Secure use reference*. GitHub Docs. Retrieved August 9, 2026, +from https://docs.github.com/en/actions/reference/security/secure-use + +Joint Task Force. (2020). *Security and privacy controls for information +systems and organizations* (NIST Special Publication 800-53, Revision 5). +National Institute of Standards and Technology. +https://doi.org/10.6028/NIST.SP.800-53r5 + +Nelson, A., Rekhi, S., Souppaya, M., & Scarfone, K. (2025). *Incident response +recommendations and considerations for cybersecurity risk management: A CSF +2.0 community profile* (NIST Special Publication 800-61, Revision 3). National +Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-61r3 + +Open Source Security Foundation. (n.d.). *OpenSSF Scorecard*. GitHub. Retrieved +August 9, 2026, from https://github.com/ossf/scorecard + +OWASP Foundation. (n.d.). *OWASP Top 10 CI/CD security risks*. Retrieved August +9, 2026, from https://owasp.org/www-project-top-10-ci-cd-security-risks/ + +Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development +Framework (SSDF) version 1.1: Recommendations for mitigating the risk of +software vulnerabilities* (NIST Special Publication 800-218). National +Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 + +Supply-chain Levels for Software Artifacts. (n.d.). *SLSA specification, +version 1.2*. Retrieved August 9, 2026, from https://slsa.dev/spec/v1.2/ diff --git a/docs/automation/TRD.md b/docs/automation/TRD.md new file mode 100644 index 000000000..46aea475b --- /dev/null +++ b/docs/automation/TRD.md @@ -0,0 +1,215 @@ +# Technical requirements: CWL automation control plane + +Status: normative technical contract. See +[TRACEABILITY.md](TRACEABILITY.md) for implementation evidence. + +## System boundary + +The central repository owns trusted workflow definitions, review and merge +decision helpers, sandboxed evidence tooling, organization audit logic, and +shared tests. Target repositories own product source, repository-specific +tests, branch policy, and thin enrollment/caller configuration. GitHub owns the +event bus, rulesets, formal review objects, check runs, statuses, refs, and +merge transaction. + +## Trigger and event semantics + +| Path | Trusted entrypoint | Purpose | Mutation authority | +|---|---|---|---| +| Required review bootstrap | `.github/workflows/opencode-review.yml` | Stable ruleset context; no PR code execution | None | +| Privileged OpenCode review | `.github/workflows/opencode-review-dispatch.yml` | Default-branch authenticated exact-head evidence and formal model review | Review publication only through configured identity; scheduler follow-up is separate | +| Noema review | `.github/workflows/noema-review.yml` | Independent advisory review with App/OIDC/PAT credential selection | Formal review publication by Noema identity | +| Strix evidence | `.github/workflows/strix.yml` | Exact-head changed-path security evidence | Status/check publication; no merge authority | +| Merge scheduler | `.github/workflows/pr-review-merge-scheduler.yml` | Classify current PR state, dispatch missing evidence, update/merge with guards | Explicit job-scoped mutation credential | +| Autofix scheduler | `.github/workflows/pr-review-fix-scheduler.yml` | Select conservative current-head repair candidates | Dispatch only | +| Autofix worker | `.github/workflows/pr-review-autofix.yml` | Produce a validated source repair on one same-repository PR head | Branch write only after repeated snapshot guards | +| Fleet auditor | `.github/workflows/audit-central-ruleset.yml` | Read organization ruleset enrollment and drift | Read-only by policy | +| Mention router | `.github/workflows/agent-mention-router.yml` | Validate trusted comments and enqueue bounded review requests | Repository dispatch only | + +On the audited protected main, Strix concurrency is scoped by event, +repository, and PR number and uses `cancel-in-progress: true`. A replacement in +the same event class cancels its predecessor and resolves the current PR head +when it executes. `repository_dispatch` and `pull_request_target` are distinct +event classes so one cannot cancel the other's required-check context. This is +the executable behavior, replacing older prose that described head-SHA-scoped, +non-cancelling Strix runs. + +`pull_request_target` entrypoints must use protected-base workflow code and must +not execute PR-controlled source while holding secrets or write permission. +Privileged execution is moved to a default-branch `repository_dispatch` path +that re-fetches live PR metadata. `workflow_call` is used only when its explicit +inputs and secret contract are sufficient; callers cannot select an unreviewed +privileged implementation ref. + +## Revision identity + +A `pull_request_snapshot` is valid only for one tuple: + +`(repository, pr_number, source_ref, source_head_sha, base_branch, live_base_sha, observed_at)`. + +- `source_head_sha` is the exact source revision under review. +- `base_branch` is the target ref name. +- `live_base_sha` is resolved independently from the current base ref at the + decision boundary. A PR API base snapshot is historical evidence, not an + alias for the current protected ref tip. +- The scheduler's compare-API path resolves branch freshness from the base ref + and head ref. Mutating actions additionally use expected-head or + `--match-head-commit` protection. +- Any source-head movement invalidates predecessor checks, statuses, reviews, + test claims, and merge simulation. Any live-base movement requires a new + freshness and mergeability decision. + +The strict three-key `cwl.agent-invocation/v2` dispatch envelope and +snapshot-only review route are **pending** in +`ContextualWisdomLab/.github#840`; they are not protected-main behavior in this +baseline. Until integrated, existing dispatch fields remain subject to the +same identity requirements and GitHub's ten-property `client_payload` limit. + +## Evidence and authority taxonomy + +| Evidence class | What it proves | What it cannot authorize | +|---|---|---| +| Check evidence | A named check run evaluated a revision and concluded a state | Formal approval, merge, release | +| Status evidence | A producer published a context/state for a commit | Formal approval or replacement of a required check | +| Workflow evidence | A workflow/run/job executed from a known provenance with recorded inputs | Correctness outside the observed scope | +| Formal review evidence | GitHub recorded a review by a named identity on a head | Merge unless reviewer eligibility and all other gates pass | +| Dependency evidence | A predecessor/base/reusable-workflow condition is satisfied | Product correctness | +| Operational acceptance | Protected-main behavior succeeded in an enrolled real consumer | Universal fleet health | + +Check evidence, status evidence, workflow evidence, and formal review evidence +remain separate records. Merge authority belongs only to the guarded scheduler +or GitHub native merge transaction under ruleset enforcement. Release authority +belongs to the repository release workflow/environment and is never implied by +review approval. Deployment authority is separately scoped. A model verdict is +advisory content until an eligible identity creates the required GitHub object. + +## Review and merge gates + +The same exact head must satisfy all applicable gates: + +1. PR is open, non-draft when policy requires, and has valid repository/ref/SHA + identity. +2. The live base and mergeability state are freshly resolved. +3. Required checks and security/provenance gates are successful; pending, + skipped-required, cancelled, absent, neutral-required, action-required, + predecessor-head, or synthetic-only states are not success. +4. No valid unresolved review thread or current-head change request remains. +5. Automated OpenCode/Noema/Strix evidence is current-head when required. +6. Ruleset-required counted non-author approval is present and not stale. +7. The final mutation compares the expected head. A head mismatch aborts. + +The organization ruleset auditor's executable contract requires exactly two +eligible approving reviews for enrolled repositories, stale-review dismissal, +last-pusher protection, and review-thread resolution. Historical rollout +entries showing zero or one review are dated migration evidence. The central +`.github` repository's missing reliable counted-independent-review path remains +tracked by `ContextualWisdomLab/.github#772`; automation cannot self-satisfy it. + +The configured `code-reviewer` model subagent can read, grep, glob, and list +trusted bounded evidence. Bash, task/subagents, network, LSP, MCP, +external-directory access, edits, installation, and mutation are denied. +Execution receipts are prepared by the trusted workflow, not run by the model. + +## Permissions, identities, and secrets + +- Workflow defaults are `contents: read`; jobs elevate only the permissions + they use. +- `id-token: write` is job-scoped to OIDC exchange paths. +- GitHub App installation tokens are repository-scoped and short lived. +- PAT-like fallbacks (`PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, + `NOEMA_REVIEW_TOKEN`) are compatibility paths, not justification for broader + permissions. +- The scheduler prefers `github.token` for same-repository mechanical work but + still implements an exchanged repository-scoped OpenCode App token in its + cross-repository credential chain. It is not described as removed. +- `NVIDIA_NIM_API_KEY` is exposed only to a real model-call step. Deterministic + metadata, eligibility, open-PR, syntax, and release gates precede model-secret + materialization. +- `COPILOT_GITHUB_TOKEN` is not an accepted development-agent credential. +- Reusable workflows define named secret requirements. Blanket + `secrets: inherit` is a migration gap where still present, including the + current Cloudflare Pages caller guidance. +- Reviewer, writer, merger, releaser, and deployer identities are independently + auditable and must not be collapsed to make a gate pass. + +The complete current secret-name inventory, purpose, owner, absence behavior, +and rotation/revocation contract is maintained in [SECURITY.md](SECURITY.md). + +## Audited implementation state and gaps + +Protected main currently centralizes seven required workflows through ruleset +`18156473`: close-empty PR, Noema, OpenCode, merge scheduler, security scan, +Strix, and Semgrep. `scripts/ci/audit_central_required_workflows.py` detects +drift and review-rule weakening but does not mutate or repair the ruleset. +The audited paths are `.github/workflows/close-empty-pr.yml`, +`.github/workflows/noema-review.yml`, `.github/workflows/opencode-review.yml`, +`.github/workflows/pr-review-merge-scheduler.yml`, +`.github/workflows/security-scan.yml`, `.github/workflows/strix.yml`, and +`.github/workflows/sast-semgrep.yml`. + +| ID | Audited protected-main behavior | Required closure | +|---|---|---| +| IG-001 | Mention wrappers validate review-only flags and exact identity, but the authoritative OpenCode/Noema route does not preserve every flag/SHA through the final review boundary. | End-to-end strict dispatch snapshot, live-base, and review-only semantics in `ContextualWisdomLab/.github#840`. | +| IG-002 | Noema is a distinct review identity; its handoff is non-blocking, and the merge scheduler checks generic formal approval/ruleset state rather than requiring that identity. | Reliable counted independent non-author review in `ContextualWisdomLab/.github#772`. | +| IG-003 | Privileged scheduler/OpenCode/Strix targeted dispatch rejects external heads, while older policy prose described them as reviewable. | Select one safe product contract and align all entrypoints in `ContextualWisdomLab/.github#889`. | +| IG-004 | Per-workflow concurrency, immutable invocation claims, and mutation head guards exist; there is no shared writer owner/TTL/heartbeat/fencing record across merge, autofix, and rebase workflows. | Durable cross-workflow writer lease in `ContextualWisdomLab/.github#890`. | +| IG-005 | A provider/backend outage can produce a skipped or neutral Strix shape without authoritative scan evidence. | Terminal fail-closed security evidence gate in `ContextualWisdomLab/.github#891`. | +| IG-006 | The scheduler prefers the workflow token for same-repository work but still selects an exchanged OpenCode App token; merge-mode/state policy is not one machine-checked table. | Align mutation credentials and merge modes in `ContextualWisdomLab/.github#892`. | +| IG-007 | Mention artifacts are claimed before forwarding; failure after claim is at-most-once dead-letter behavior and requires a new comment during the 30-day claim window. | Recoverable claim state/fencing in `ContextualWisdomLab/.github#893`. | +| IG-008 | The scheduler preserves queue progress by recording PR-local mutation failures as `action_error`, but its CLI still returns success after the scan. | Preserve the structured summary and make material action failures terminally non-passing in `ContextualWisdomLab/.github#894`. | + +These gaps are non-passing where their stronger target contract is required. +They must not be inferred as implemented from the PRD, ADR acceptance status, +UML, ERD, FigJam, or prompt. + +## Failure classification and retry + +| Class | Examples | Response | +|---|---|---| +| Transient infrastructure | GitHub 5xx, bounded DNS/network reset, runner allocation delay | Bounded retry with jitter or documented fallback; retain original evidence | +| Provider capacity | Model timeout, rate limit, exhausted pool | Try a distinct configured provider within budget; otherwise defer without approval | +| Permanent input | Malformed repo/ref/SHA, unsupported payload shape, size/property limit | Fail closed; repair producer/contract | +| Integrity/security | Digest mismatch, untrusted workflow ref, checksum/signature failure, TLS validation failure | Immediate fail closed; no blind retry | +| Authority | 401/403, missing installation, ineligible reviewer, ruleset denial | Defer affected action; obtain legitimate authority or continue another lane | +| Product/source | Test, security, compatibility, migration, or behavior failure | RCA, realistic RED, narrow fix, GREEN, full relevant verification | + +Retries are capped by attempt count and wall-clock budget. Repeated identical +failure does not become evidence of success. Three materially distinct failed +remedies across layers trigger architecture reassessment. + +## Concurrency and writer leases + +The policy unit of source-write ownership is `(repository, branch)`. Before +every write, the actor re-fetches the target ref, PR state, review state, and +base tip. A source-affecting move freezes writes to that branch for the run. +Read-only fleet audit, different branches, and different repositories may +continue. The current per-workflow controls do not globally serialize different +writer workflows; the durable lease implementation is tracked in +`ContextualWisdomLab/.github#890`. + +Concurrency groups may cancel predecessor work only when doing so cannot allow +a stale request to cancel or replace newer valid evidence. A queued or running +review is a deferred state, not a repository-wide lock. The work-conserving +automation contract is specified in +[ADR-0007](adr/0007-work-conserving-automation.md). + +## Diagnostics and PII + +Sandboxed commands scrub ambient credentials and publish bounded structured +result markers. Credential-shaped values in stdout, stderr, timeout evidence, +and service logs must be redacted before publication while retaining the +ordinary error, exit code, timeout, failing step, and useful non-secret tail. +The replacement implementation is pending in +`ContextualWisdomLab/.github#842`. + +PII is not indiscriminately masked when doing so prevents review or incident +response. Instead use least-privilege access, purpose limitation, bounded +retention, encrypted transport/storage, audience-scoped disclosure, and audit +records. Secrets and authentication material are never treated as ordinary PII. + +## Protected-main acceptance + +Source merge is integration evidence, not operational closure. A central +workflow repair closes its incident only after an enrolled consumer executes +the protected-main path and records target repository, workflow source, +source-head, live-base, run/job identity, conclusion, and recovery behavior. diff --git a/docs/automation/UML.md b/docs/automation/UML.md new file mode 100644 index 000000000..25ef9f75e --- /dev/null +++ b/docs/automation/UML.md @@ -0,0 +1,170 @@ +# UML and interaction views + +Status: normative diagram-as-code views. The prose contracts in +[TRD.md](TRD.md) take precedence if a renderer changes layout. + +Editable architecture, ERD, sequence, and runtime-state companion: +[CWL Automation Control Plane FigJam](https://www.figma.com/board/4x8YSMb8teJhU19nDjdkcy). + +## Component and bounded-context view + +```mermaid +flowchart LR + eventContext["Enrollment and event context"] --> snapshotContext["Snapshot and provenance context"] + snapshotContext --> evidenceContext["Evidence execution context"] + evidenceContext --> decisionContext["Decision and mutation context"] + decisionContext --> protectedRef["Protected repository ref"] + repairContext["Repair context"] --> snapshotContext + decisionContext --> repairContext + auditContext["Read-only fleet audit context"] --> eventContext + acceptanceContext["Operational acceptance context"] --> evidenceContext + externalModels["External model providers"] -. "Advisory output" .-> evidenceContext +``` + +## PR maintenance sequence + +```mermaid +sequenceDiagram + participant GitHub + participant Scheduler + participant Strix + participant OpenCode + participant Noema + participant Ruleset + + GitHub->>Scheduler: PR event with identity + Scheduler->>GitHub: Fetch head and live base + Scheduler->>Strix: Dispatch exact-head scan + Strix-->>GitHub: Publish security evidence + Scheduler->>OpenCode: Dispatch exact-head review + OpenCode-->>GitHub: Publish advisory review + OpenCode->>Noema: Handoff eligible head + Noema-->>GitHub: Publish independent review evidence + Scheduler->>Ruleset: Evaluate current gates + Ruleset-->>Scheduler: Eligible or deferred + Scheduler->>GitHub: Head-guarded update or merge +``` + +The scheduler does not wait in place between the Strix, OpenCode, and Noema +messages. A queued/running item enters a deferred set while another safe lane is +processed. + +This sequence is the target interaction contract. On the audited protected +main, Noema handoff is non-blocking and the scheduler does not validate the +Noema identity; `ContextualWisdomLab/.github#772` owns that governance gap. +End-to-end mention snapshot and review-only semantics remain pending in +`ContextualWisdomLab/.github#840`. The current scheduler records a PR-local +mutation failure as `action_error` and continues the queue, but the CLI can +still exit successfully; terminal workflow propagation is tracked in +`ContextualWisdomLab/.github#894`. + +## Product-development sequence + +```mermaid +sequenceDiagram + participant Maintainer + participant DeterministicGates + participant ModelPlane + participant GitHub + participant Consumer + + Maintainer->>GitHub: Create bounded branch from protected main + Maintainer->>DeterministicGates: Run RED regression + DeterministicGates-->>Maintainer: Confirm intended failure + Maintainer->>GitHub: Implement narrow repair + Maintainer->>DeterministicGates: Run focused and full gates + DeterministicGates-->>GitHub: Publish exact-head evidence + GitHub->>ModelPlane: Request optional model review + ModelPlane-->>GitHub: Publish advisory verdict + GitHub->>GitHub: Enforce counted approval and ruleset + GitHub->>Consumer: Run protected-main acceptance + Consumer-->>GitHub: Record operational evidence +``` + +Model credentials are not materialized until deterministic identity and +eligibility gates pass. A provider failure cannot replace deterministic or +formal governance evidence. + +## Evidence and gate state machine + +```mermaid +stateDiagram-v2 + direction LR + [*] --> Observed + Observed --> Invalid: identity mismatch + Observed --> Collecting: identity valid + Collecting --> Deferred: gate running + Deferred --> Collecting: state changed + Collecting --> Failed: authoritative failure + Failed --> Collecting: repaired head + Collecting --> Complete: all evidence complete + Complete --> Stale: head or live base moved + Stale --> Collecting: new snapshot + Complete --> Eligible: authority gates pass + Eligible --> Integrated: guarded merge + Integrated --> Accepted: consumer proof + Invalid --> [*] + Accepted --> [*] +``` + +## Reviewer and merge authority state flow + +```mermaid +stateDiagram-v2 + direction LR + [*] --> Unreviewed + Unreviewed --> AdvisoryReviewed: model verdict + AdvisoryReviewed --> ChangesRequested: valid finding + ChangesRequested --> Unreviewed: new repaired head + AdvisoryReviewed --> AwaitingCountedApproval: advisory clean + AwaitingCountedApproval --> Approved: eligible formal review + Approved --> StaleApproval: new head + StaleApproval --> Unreviewed: refresh evidence + Approved --> MergeEligible: all other gates pass + MergeEligible --> Merged: expected head matches + Merged --> [*] +``` + +An OpenCode, Noema, Strix, CodeRabbit, check, status, comment, or reaction does +not skip `AwaitingCountedApproval` when the ruleset requires an eligible +independent review. + +## Deployment and control-plane topology + +```mermaid +flowchart TB + orgRuleset["Organization ruleset"] --> leafA["Leaf repository A"] + orgRuleset --> leafB["Leaf repository B"] + centralDefault["Protected .github default branch"] --> leafA + centralDefault --> leafB + leafA --> centralDispatch["Central default-branch dispatch receivers"] + leafB --> centralDispatch + centralDispatch --> githubApp["Repository-scoped GitHub App or OIDC token"] + centralDispatch --> modelProvider["NVIDIA NIM or configured model provider"] + centralDispatch --> targetEvidence["Target checks, statuses, and reviews"] + fleetAuditor["Read-only fleet auditor"] --> orgRuleset + consumerProbe["Protected-main consumer probe"] --> targetEvidence +``` + +## Incident retry and failure-classification flow + +```mermaid +flowchart TD + failure["Observe exact failing boundary"] --> classify{"Classify failure"} + classify -->|"Transient infrastructure"| boundedRetry["Bounded retry with backoff"] + classify -->|"Provider capacity"| alternateProvider["Use distinct configured provider"] + classify -->|"Input or policy"| repairContract["Repair producer or contract"] + classify -->|"Integrity or security"| failClosed["Fail closed and contain"] + classify -->|"Authority"| deferAction["Defer affected action"] + classify -->|"Product source"| redRegression["Reproduce RED and fix root cause"] + boundedRetry --> verify["Verify exact-head outcome"] + alternateProvider --> verify + repairContract --> verify + redRegression --> verify + failClosed --> incidentRecord["Record and investigate incident"] + deferAction --> otherLane["Continue another safe lane"] + verify --> consumerProof["Run protected-main consumer acceptance"] + consumerProof --> closeOrReopen{"Closure evidence complete?"} + closeOrReopen -->|"Yes"| closeIncident["Close incident"] + closeOrReopen -->|"No"| incidentRecord +``` diff --git a/docs/automation/adr/0001-writer-lease-and-read-only-fleet-auditor.md b/docs/automation/adr/0001-writer-lease-and-read-only-fleet-auditor.md new file mode 100644 index 000000000..b53bd1009 --- /dev/null +++ b/docs/automation/adr/0001-writer-lease-and-read-only-fleet-auditor.md @@ -0,0 +1,98 @@ +# ADR-0001: Writer lease and read-only fleet auditor + +Status: Accepted +Date: 2026-08-09 +Owner: CWL automation maintainers + +## Context + +The control plane can review, repair, update, and merge across many +repositories. Scheduled automation, review callbacks, autofix workers, and +maintainers can observe the same branch concurrently. Without an explicit +ownership rule, two actors can race, overwrite a valid repair, publish evidence +for different heads, or let a stale worker cancel current work. Fleet audit has +organization-wide breadth, which would magnify the impact if its credential +were also allowed to mutate source. + +## Decision drivers + +- Preserve contributor work and exact-head evidence. +- Allow concurrency across independent repositories and branches. +- Keep organization-wide inspection low impact and least privilege. +- Make lease loss and stale state observable and recoverable. +- Avoid a centralized lock service unless GitHub-native controls prove + insufficient. + +## Considered alternatives + +1. Serialize the whole organization. This is safe but creates unnecessary + head-of-line blocking and violates work conservation. +2. Let each workflow write optimistically and repair conflicts. This permits + stale or duplicate mutations and makes review evidence ambiguous. +3. Give the fleet auditor the writer credential for convenience. This creates + a high-impact cross-repository authority path. +4. Use a logical branch-scoped lease backed by GitHub concurrency, live ref + validation, expected-head guards, and explicit handoff. This fits current + infrastructure and allows independent lanes. + +## Decision + +Exactly one actor may hold source-write ownership for `(repository, branch)` at +a time. The holder records target identity, purpose, run, observed head, and +lease generation; it re-fetches PR/ref/base/review state immediately before +each mutation. A head mismatch, conflicting source-affecting action, lost +concurrency ownership, or invalid handoff ends write authority for that run. + +GitHub workflow concurrency and the scheduler's live-head/expected-head guards +implement partial per-workflow race controls. They do not form one distributed +lease across merge, autofix, rebase, and other writers. A dedicated owner, +TTL/heartbeat, fencing generation, and takeover record is tracked in +`ContextualWisdomLab/.github#890`. Different repositories/branches and +read-only work may run concurrently. The fleet auditor uses a credential and +workflow role that cannot dispatch mutation, publish formal approval, push, +merge, release, or deploy. + +## Consequences + +Throughput scales by independent branch rather than by organization. A stale +worker may finish computation but cannot publish authoritative mutation for a +new head. Each write path needs repeated live validation and deterministic +lease-key construction. Handoffs are more explicit, and duplicate work can +still occur before the final guarded mutation. + +## Failure and recovery + +On contention, defer the losing item with its exact target and continue another +lane. On lease loss or head movement, discard mutation intent, retain bounded +diagnostics, and rebuild evidence for the new snapshot. If the lease mechanism +itself is inconsistent, freeze only the affected mutation class; preserve +read-only audit. Recover through a reviewed change and verify one competing +worker scenario plus a real consumer. + +## Security and governance + +Lease ownership does not grant review, merge, release, or deployment authority. +The holder still needs the correct job-scoped credential and every external +rule. Auditor credentials remain read-only even during an incident. Protected +branch history is never force-rewritten to repair lease failure. + +## Verification + +Tests cover two writers selecting the same head, head movement before push, +safe concurrency on distinct branches, stale cancellation, idempotent replay, +lease handoff, and an auditor attempting a write. The final GitHub action must +show expected-head protection. Fleet audit verifies no mutation permissions. + +## Migration and rollback + +Every writer adopts the common lease key and pre-mutation revalidation. Existing +workflow concurrency/live-head guards remain in force during migration. +Rollback disables the new writer path or reverts to the last known-good guarded +path; it does not disable branch protection. Introduce external lease storage +only with a new ADR and dual-read validation plan. + +## Supersession + +This ADR is current. A future ADR may replace the GitHub-native distributed +lease with a persisted lease service, but must retain branch scope, lease loss, +head guards, read-only audit, migration, and rollback semantics. diff --git a/docs/automation/adr/0002-exact-head-and-live-base-binding.md b/docs/automation/adr/0002-exact-head-and-live-base-binding.md new file mode 100644 index 000000000..f1cf048a6 --- /dev/null +++ b/docs/automation/adr/0002-exact-head-and-live-base-binding.md @@ -0,0 +1,92 @@ +# ADR-0002: Exact-head and live-base binding + +Status: Accepted +Date: 2026-08-09 +Owner: CWL automation maintainers + +## Context + +A pull-request object contains a source head and a base snapshot, but both refs +can change while review, testing, dispatch, or merge is in flight. Checks or +approvals for a predecessor head cannot justify a successor. Likewise, a stale +base snapshot cannot answer whether the current protected branch makes the PR +behind, conflicting, or unsafe to merge. Cross-repository dispatch adds another +point where identity can be truncated, malformed, or replayed. + +## Decision drivers + +- Prevent time-of-check/time-of-use and predecessor-evidence reuse. +- Distinguish source revision from target branch freshness. +- Preserve enough identity for audit, retry, handoff, and incident response. +- Fit GitHub's dispatch payload and final merge transaction constraints. +- Fail closed on ambiguity without serializing unrelated work. + +## Considered alternatives + +1. Trust the PR API's embedded base SHA throughout a run. It is historical and + can differ from the live protected ref. +2. Bind only to PR number and branch names. Branches are mutable and therefore + insufficient evidence identity. +3. Bind only to source SHA. This misses base movement and dependency freshness. +4. Seal source and independently observed live-base identity at every decision + boundary, with a final expected-head guard. This is selected. + +## Decision + +The canonical snapshot tuple is `(repository, pr_number, source_ref, +source_head_sha, base_branch, live_base_sha, observed_at)`. Evidence additionally +records producer, type, workflow provenance, run/job, and conclusion. + +The source head comes from the live PR/ref and is immutable for that evidence. +The live base is resolved independently from the current base branch at the +decision boundary; the PR's base SHA is retained only as historical context. +Source movement invalidates predecessor tests, statuses, checks, reviews, +patches, and merge simulations. Base movement forces a new freshness, +mergeability, and dependency decision. Writes use expected-head semantics and +merges use a head-matched transaction. + +The three-key `cwl.agent-invocation/v2` envelope and snapshot-only route in +`ContextualWisdomLab/.github#840` are pending, not protected-main behavior. + +## Consequences + +Runs refetch more often and some expensive evidence becomes stale by design. +Operators can explain exactly why evidence was accepted or rejected. Dispatch +schemas stay bounded and versioned. A check on the same commit may still need +rerun after a meaningful base movement because integration assumptions changed. + +## Failure and recovery + +Malformed repository/ref/SHA, missing live base, unexpected head, or schema +version failure is permanent for that attempt and fails closed. API 5xx or +bounded network reset may retry under ADR-0003. On movement, abandon the old +mutation, record the new identity, and enqueue a fresh snapshot; do not edit +evidence labels to make old results appear current. + +## Security and governance + +All identity fields are validated as untrusted inputs. Protected workflow code +re-fetches rather than trusting dispatcher assertions. Exact-head binding does +not make the producer authoritative: formal review eligibility, rulesets, +permissions, and separate merge/release authority still apply. + +## Verification + +Tests cover force-push, deleted/ref-created branch, stale PR base snapshot, live +base movement, cross-repository head, malformed SHA/ref, replayed dispatch, +payload property limits, predecessor review/check, and movement immediately +before update or merge. Verification asserts the final expected-head value. + +## Migration and rollback + +Consumers first accept both legacy and versioned dispatch formats while +producers emit the strict format; receipts expose which schema was used. Remove +legacy acceptance only after protected-main consumer evidence. Rollback keeps +exact-head/live-base validation while reverting the envelope producer/consumer +pair; weakening identity is not an allowed rollback. + +## Supersession + +This ADR is current. A successor may change transport or snapshot storage, but +must preserve independent source/live-base observations, typed provenance, and +final atomic head protection. diff --git a/docs/automation/adr/0003-classified-bounded-retries.md b/docs/automation/adr/0003-classified-bounded-retries.md new file mode 100644 index 000000000..14ec90eba --- /dev/null +++ b/docs/automation/adr/0003-classified-bounded-retries.md @@ -0,0 +1,92 @@ +# ADR-0003: Classified bounded retries + +Status: Accepted +Date: 2026-08-09 +Owner: CWL automation maintainers + +## Context + +GitHub APIs, runners, networks, and model providers can fail transiently. +Identity, authorization, integrity, TLS, policy, and source tests can also fail +permanently. Treating every failure as retryable wastes capacity, hides root +causes, repeats unsafe actions, and can publish secrets repeatedly. Treating no +failure as retryable makes ordinary service fluctuation unnecessarily +disruptive. + +## Decision drivers + +- Preserve correctness and security while tolerating bounded platform noise. +- Keep useful diagnostics and original failure identity. +- Prevent one failing item from monopolizing an automation run. +- Make retry behavior deterministic, testable, and observable. +- Avoid duplicate source mutation and synthetic success. + +## Considered alternatives + +1. Unlimited exponential retry. This can run indefinitely and amplify + persistent or unsafe failures. +2. Retry by HTTP status alone. Provider semantics, idempotency, and integrity + context cannot be captured by status alone. +3. Never retry. This reduces complexity but turns recoverable platform noise + into operator work. +4. Classify failures and apply per-operation attempt and wall-clock budgets, + idempotency, jitter, and safe fallback. This is selected. + +## Decision + +Only classified transient infrastructure failures and provider-capacity +failures may retry. Each operation declares maximum attempts, total wall-clock +budget, idempotency behavior, and whether a distinct configured provider or +safe read-only fallback exists. The original error and every attempt remain +attributable. + +Malformed input, missing/ambiguous identity, 401/403 authority failures, +ruleset/reviewer ineligibility, checksum/signature/provenance mismatch, TLS +validation failure, unsupported payload shape, unexpected ref/head, and +product/test/security defects do not receive blind retries. They require a +material state/configuration/source change. A retry never converts missing +evidence into success, and a write is revalidated and idempotent at each +attempt. + +## Consequences + +Callers need explicit error taxonomies instead of broad exception loops. +Temporary capacity problems recover automatically within budget; persistent +problems surface earlier. Some false-negative classification is preferable to +an unsafe retry and can be corrected through reviewed taxonomy changes. + +## Failure and recovery + +When budget expires, defer the exact item with its classification, attempts, +last useful non-secret error, and next valid trigger; continue another queue +lane. A distinct provider may be tried only within the declared provider pool +and budget. After three materially different unsuccessful remedies, reassess +architecture or dependencies. Recovery reruns from a fresh snapshot, not from +an assumed in-memory state. + +## Security and governance + +Retry logs pass through bounded publication redaction. Credentials are not +broadened or swapped automatically after authority failure. Integrity and TLS +errors fail closed. Duplicate dispatch/mutation is constrained by idempotency +keys, writer leases, and expected-head checks. + +## Verification + +Tests cover each failure class, `Retry-After`, jitter bounds, attempt and time +budgets, provider exhaustion, partial publication, timeout stdout/stderr, +credential-shaped output, idempotent replay, and head movement between +attempts. Metrics distinguish attempt count from substantive completion. + +## Migration and rollback + +Replace broad loops incrementally with a shared classification vocabulary and +operation-specific budgets. Preserve old error evidence during comparison. +Rollback may restore a known-good bounded implementation, but cannot restore an +unbounded loop or make permanent security failures retryable. + +## Supersession + +This ADR is current. Provider-specific successors may refine categories and +budgets while preserving fail-closed classes, bounded attempts/time, +idempotency, diagnostics, and work-conserving deferral. diff --git a/docs/automation/adr/0004-minimal-reusable-workflow-secrets.md b/docs/automation/adr/0004-minimal-reusable-workflow-secrets.md new file mode 100644 index 000000000..8b7bc5813 --- /dev/null +++ b/docs/automation/adr/0004-minimal-reusable-workflow-secrets.md @@ -0,0 +1,94 @@ +# ADR-0004: Minimal reusable-workflow secrets + +Status: Accepted; migration in progress +Date: 2026-08-09 +Owner: CWL automation and security maintainers + +## Context + +Central reusable/required workflows operate in many repository contexts and may +need GitHub App, OIDC, model-provider, branch-mutation, release, or deployment +credentials. Blanket secret inheritance is convenient but conceals the +interface, exposes unrelated credentials, makes audit difficult, and expands +the impact of untrusted source or a central defect. Long-lived fallback tokens +also risk collapsing review, mutation, merge, and deploy authority. + +## Decision drivers + +- Least privilege and explicit cross-repository interfaces. +- Short-lived, attributable, repository-scoped credentials. +- Compatibility with current GitHub reusable/required workflow behavior. +- Deterministic behavior when a secret is absent or an installation is missing. +- Safe migration without claiming existing broad callers are already fixed. + +## Considered alternatives + +1. Use `secrets: inherit` everywhere. This is simple but over-broad and opaque. +2. Use one organization PAT for all actions. This creates a high-impact shared + authority and weak attribution. +3. Remove all secrets and privileged automation. This loses required review, + mutation, and deployment capability. +4. Declare named secret contracts and prefer short-lived OIDC/App credentials, + with separated purpose-specific compatibility tokens. This is selected. + +## Decision + +Every reusable workflow declares the minimum named secret inputs needed by +each privileged job. Secrets are optional at the workflow boundary only when +the absence has a defined fail-closed or deterministic degraded path. Jobs +receive credentials only after non-secret identity, eligibility, and input +validation passes. + +Prefer job-scoped OIDC exchange or repository-scoped GitHub App installation +tokens. Keep review publication, branch mutation, merge, release, and deploy +authority separate. `NVIDIA_NIM_API_KEY` is available only to actual model-call +steps. `COPILOT_GITHUB_TOKEN` is not a development-agent credential. PAT-like +fallbacks are compatibility mechanisms with explicit scope and telemetry, not +a reason to widen the default token. + +Current `secrets: inherit` guidance, including the Pages/Cloudflare reusable +caller path, is an acknowledged migration gap. + +## Consequences + +Callers become more verbose but their security contract is reviewable. Missing +secrets fail at a named capability boundary rather than leaking into unrelated +steps. Repository/App enrollment and rotation require more deliberate +operations. Some legacy workflows need staged dual compatibility. + +## Failure and recovery + +Missing authority defers only the dependent action; read-only and deterministic +work continues. A 401/403 or installation-scope failure does not trigger +fallback to a broader token automatically. On exposure, restrict evidence, +revoke/rotate the affected secret, audit its exact scope, and verify a +non-destructive authenticated probe before resuming mutation. + +## Security and governance + +Workflow defaults remain read-only, with job-local permission elevation. +Secrets do not enter PR-controlled commands, prompts, artifacts, summaries, or +logs. Review and merge identities remain independently eligible. Exception +records are owner/target/expiry/compensating-control scoped. + +## Verification + +Contract tests inspect permissions, named secret references, model-secret +boundaries, fork/untrusted events, absent secret behavior, App scope, OIDC job +scope, and over-broad inheritance. Adversarial tests attempt echo, timeout, +subprocess, output, artifact, and model-prompt disclosure. + +## Migration and rollback + +Inventory inherited secrets, map actual use, add named inputs and explicit +capability errors, migrate representative callers, exercise protected-main +consumers, then remove inheritance. Rollback may temporarily restore the last +known-good caller for availability under a time-bounded exception; it must not +combine authority classes or expose secrets to untrusted PR execution. + +## Supersession + +This ADR is current until all callers use explicit interfaces. A successor may +mandate a specific federation mechanism, but must preserve job scoping, +purpose-separated authority, deterministic absence behavior, and audited +migration. diff --git a/docs/automation/adr/0005-independent-review-governance.md b/docs/automation/adr/0005-independent-review-governance.md new file mode 100644 index 000000000..eb06760ac --- /dev/null +++ b/docs/automation/adr/0005-independent-review-governance.md @@ -0,0 +1,93 @@ +# ADR-0005: Independent review governance + +Status: Accepted +Date: 2026-08-09 +Owner: CWL governance maintainers + +## Context + +OpenCode, Noema, Strix, checks, and schedulers produce valuable evidence, but a +GitHub ruleset may require a counted approval from an eligible non-author. A +model verdict, bot comment, status, or check is not the same object and may be +self-authored, stale, or ineligible. Letting automation reinterpret its own +output as independent approval would erase separation of duties and could +bypass governance. Today, `ContextualWisdomLab/.github#772` tracks the missing +organization-level path for reliable counted independent review. + +## Decision drivers + +- Preserve ruleset semantics and separation of duties. +- Distinguish advice, evidence, formal review, and transaction authority. +- Prevent authors/bots from manufacturing approval for their own changes. +- Keep safe non-governed work moving while an eligible reviewer is pending. +- Give operators an explicit escalation and handoff path. + +## Considered alternatives + +1. Treat a successful automated check or approval phrase as approval. GitHub + does not count it as an eligible review and the authority is wrong. +2. Let the authoring automation approve with another token. The nominal + identity changes but independence does not. +3. Remove the review requirement for automation PRs. This weakens protected + governance at the highest-impact repository. +4. Keep automated output advisory and require the exact eligible GitHub formal + review object when the ruleset demands it. This is selected. + +## Decision + +Check evidence, status evidence, workflow evidence, model/reviewer content, +formal GitHub review evidence, merge authority, and release authority are +separate types. An automated system may analyze and publish advice under an +auditable identity, but it satisfies a counted independent-approval gate only +when GitHub records a current, non-dismissed formal review from an actor who is +eligible under the ruleset and genuinely independent of the author/change +producer. + +The guarded scheduler may merge only after GitHub and repository policy confirm +all gates. It cannot self-approve, dismiss valid findings for convenience, or +use merge authority to manufacture review authority. A missing independent +review defers that exact PR/head while unrelated safe work continues. + +## Consequences + +Some otherwise clean PRs will wait for a legitimate reviewer. That delay is a +governance dependency, not a CI defect. Automation can prepare precise evidence +and reduce reviewer effort but cannot eliminate the independent decision. The +organization needs a sustainable reviewer pool and clear ownership. + +## Failure and recovery + +If approval is absent, stale, dismissed, self-authored, bot-authored without +eligibility, or superseded by a new head, report the exact reason and hand off +to an eligible reviewer. Do not rerun unchanged checks or post repeated +comments. After a new formal review or head change, refetch every gate. A +mistaken merge triggers the incident runbook and reviewed revert path. + +## Security and governance + +Reviewer credentials and merge credentials are separate and auditable. Token +substitution does not create independence. Thread resolution occurs only for +actually addressed findings and does not erase the review record. Exceptions +cannot silently waive protected branch rules. + +## Verification + +Tests cover author, co-author/change-producer, eligible independent human/App, +ineligible bot, dismissed review, stale head, new change request, unresolved +thread, similar check/status names, and final ruleset denial. Live acceptance +confirms GitHub counts the intended review object. + +## Migration and rollback + +Maintain current rulesets while establishing the reviewer path tracked by +`.github#772`. Add eligibility telemetry and reviewer handoff without changing +gate semantics. Rollback removes faulty automation integration but retains the +GitHub approval requirement; disabling the requirement is not an operational +rollback. + +## Supersession + +This ADR is current. A successor may describe an organization-approved +independent App or reviewer service only if its ownership, identity, +eligibility, separation, revocation, audit, and protected-main acceptance are +proven. diff --git a/docs/automation/adr/0006-protected-main-operational-closure.md b/docs/automation/adr/0006-protected-main-operational-closure.md new file mode 100644 index 000000000..56f52a3b6 --- /dev/null +++ b/docs/automation/adr/0006-protected-main-operational-closure.md @@ -0,0 +1,88 @@ +# ADR-0006: Protected-main operational closure + +Status: Accepted +Date: 2026-08-09 +Owner: CWL automation and operations maintainers + +## Context + +Central workflows can pass unit and integration tests on a PR head yet fail +after merge because required-workflow resolution, repository context, rulesets, +permissions, Apps, secrets, event delivery, or a leaf repository differ from +fixtures. Calling an incident resolved when source merges hides this deployment +boundary and has repeatedly encouraged premature closure. + +## Decision drivers + +- Prove the exact protected implementation works in its real execution context. +- Keep source correctness separate from deployment and operational acceptance. +- Preserve target/run/revision identity for audit and rollback. +- Limit blast radius through staged representative consumers. +- Define an unambiguous reopen condition. + +## Considered alternatives + +1. Close at PR merge. This proves integration only. +2. Use a central synthetic workflow run. This misses target-repository rules, + secrets, permissions, and event context. +3. Wait for passive fleet usage with no explicit receipt. This is slow and + difficult to attribute. +4. Require a staged real-consumer execution from protected main and retain its + identity before operational closure. This is selected. + +## Decision + +For an operational defect in central automation, source merge moves the +incident to deployed/monitoring, not resolved. Closure requires at least one +enrolled real consumer to execute the changed protected-main path with recorded +central workflow revision, target repository, event, source head, current live +base, run/job/attempt, conclusion, relevant evidence objects, and expected +failure/recovery behavior. + +The consumer is representative of the changed boundary: for example, a +cross-repository dispatch change needs a target repository dispatch and receipt; +a redaction change needs real subprocess output through the publication path; +a merge-scheduler change needs a ruleset-governed target state. High-blast-radius +changes expand the canary set before fleet-wide closure. + +## Consequences + +Incidents remain open longer than their PRs, and operational acceptance becomes +a first-class artifact. Some changes need a non-destructive probe or prepared +fixture in a real repository. False confidence falls, but operators must own +consumer selection and evidence retention. + +## Failure and recovery + +If the protected-main consumer fails, preserve the exact receipt, contain the +affected path, and choose a reviewed corrective change or revert from the +current protected tip. Do not retry permanent authority/integrity failures or +call a different synthetic path equivalent. After recovery, repeat the same +boundary and verify rollback behavior when applicable. + +## Security and governance + +Consumer probes are least privilege, non-destructive where possible, and do not +weaken rulesets. Secrets stay scoped to the real action boundary. Operational +acceptance cannot substitute for formal review or source gates; it follows +them. Evidence audience and retention follow the security contract. + +## Verification + +Pre-merge tests simulate event/context and negative paths. Post-merge evidence +must link a successful real run/job and target revision and demonstrate the +changed code path, not merely an unrelated workflow success. Failure-path fixes +also show the expected diagnostic and recovery signal. + +## Migration and rollback + +Add acceptance fields to incident/handoff records and start with highest-impact +central workflow changes. Existing resolved incidents are not retroactively +rewritten but can be sampled for gaps. Rollback is a normal reviewed revert or +known-good explicit pin followed by the same real-consumer verification. + +## Supersession + +This ADR is current. A future automated promotion system may collect acceptance +receipts, but must retain real-consumer context, exact provenance, staged blast +radius, failure/reopen behavior, and an auditable owner. diff --git a/docs/automation/adr/0007-work-conserving-automation.md b/docs/automation/adr/0007-work-conserving-automation.md new file mode 100644 index 000000000..a7b83219d --- /dev/null +++ b/docs/automation/adr/0007-work-conserving-automation.md @@ -0,0 +1,94 @@ +# ADR-0007: Work-conserving automation + +Status: Accepted +Date: 2026-08-09 +Owner: CWL automation maintainers + +## Context + +The organization automation runs periodically and manages many PRs, +repositories, operational debts, and product-control gaps. Checks, provider +reviews, eligible humans, queues, and rate limits often require external state +changes. Repeatedly polling one unchanged item consumes a run without improving +the repository and creates misleading progress. Stopping after a status report +also leaves other safe, independent work undone. + +## Decision drivers + +- Maximize substantive progress without competing writers. +- Respect CI, reviewer, provider, and ruleset authority boundaries. +- Avoid repeated reads, meaningless commits, and user-visible elapsed-time + narration. +- Preserve exact deferred state and deterministic resumption. +- Permit product/documentation hardening when the PR queue cannot advance. + +## Considered alternatives + +1. Block the entire loop on the highest-priority PR. This wastes independent + capacity and encourages polling. +2. Always create a commit to show activity. Meaningless mutations add risk and + review noise. +3. Only report status and wait for the next schedule. Safe work remains idle. +4. Defer only the unchanged external dependency and continue another bounded + safe lane under writer-lease rules. This is selected. + +## Decision + +A read, inventory, poll, wait, review request, dispatch, rerun, or CI start is +not substantive completion. After one fresh observation of an external wait, +record the exact repository/PR/head/dependency and next valid trigger. Do not +perform two consecutive reads of the same unchanged deferred item. Revisit it +only after external state changes, a substantive mutation/acceptance completes +in another lane, or a final sweep is due. + +If a safe writable item exists, the run produces and exactly verifies at least +one substantive mutation. Candidate lanes include another PR/branch/repository, +a bounded buyer/operator control-plane gap, documentation/traceability debt, or +security/test hardening. The actor first checks writer ownership and branches +from the exact live protected tip. It never invents a low-value change merely +to satisfy the mutation rule. + +## Consequences + +Queue selection and handoff state become explicit. The loop makes progress +during local outages and approval waits. More than one item may be considered +per run, but source writes remain branch-scoped and reviewable. Runs with no +safe writable item can finish with a precise deferred record rather than fake +completion. + +## Failure and recovery + +If the selected mutation becomes unsafe or loses its lease, abandon it and +continue a different lane. If all lanes are externally blocked, persist owners, +exact identities, evidence, and next triggers, then stop cleanly. Provider/API +failures follow ADR-0003. Repeated failure across three distinct remedies +triggers architecture reassessment. + +## Security and governance + +Work conservation never bypasses independent review, checks, rulesets, +credentials, head guards, release gates, or protected-main acceptance. Parallel +work must not share a writer lease or mutate production. Read-only fleet audit +remains safe when mutation authority is unavailable. + +## Verification + +Prompt/queue contract tests or review verify one fresh read then defer, no +unchanged consecutive polling, progress on an independent lane, correct lease +behavior, no meaningless commit, exact handoff identity, and final sweep. Run +metrics count verified mutations/acceptances separately from observations. + +## Migration and rollback + +The hourly automation prompt was updated on 2026-08-09 with explicit anti-idle +and deferred-read caps plus a documentation-spine defect rule. Repository docs +make the contract durable. Rollback may simplify lane selection while retaining +one-read deferral and all authority gates; returning to indefinite polling is +not acceptable. + +## Supersession + +This ADR is current. A persisted queue/scheduler may supersede prompt-based +orchestration only if it preserves exact item identity, next-trigger semantics, +branch writer leases, work conservation, and the distinction between activity +and verified completion. diff --git a/docs/automation/adr/0008-central-control-plane-thin-leaf-contract.md b/docs/automation/adr/0008-central-control-plane-thin-leaf-contract.md new file mode 100644 index 000000000..7d7f42460 --- /dev/null +++ b/docs/automation/adr/0008-central-control-plane-thin-leaf-contract.md @@ -0,0 +1,95 @@ +# ADR-0008: Central control plane and thin leaf contract + +Status: Accepted +Date: 2026-08-09 +Owner: CWL platform and automation maintainers + +## Context + +The organization needs consistent review, security, merge, and audit policy +across independently operable products. Copying privileged workflows and helper +scripts into each repository causes drift, inconsistent fixes, duplicated +secrets, and a larger attack surface. Fully centralizing product build/release +logic would instead couple products to one runtime and reduce repository +autonomy. + +## Decision drivers + +- One reviewed source for privileged organization policy. +- Independent product operation, tests, release, rollback, and ownership. +- Minimal cross-repository secret and permission surface. +- Fleet-wide auditability and staged rollout. +- Compatibility with GitHub required/reusable workflow semantics. + +## Considered alternatives + +1. Thick copy in every leaf. This maximizes local control but creates policy + drift and expensive fleet remediation. +2. One monolithic central pipeline for all product build/release behavior. This + creates runtime coupling and an excessive blast radius. +3. Advisory central templates with no enforcement. Repositories can silently + diverge from required controls. +4. Centralize privileged control policy and keep leaf enrollment/callers thin, + while leaf repositories own product source/build/release contracts. This is + selected. + +## Decision + +`ContextualWisdomLab/.github` is the source of truth for trusted review, +security dispatch, evidence classification, merge scheduling, safe autofix, +sandboxing, and read-only fleet audit. Organization rulesets and protected +default-branch dispatch/reusable workflows select reviewed central code. + +Leaf repositories retain source, domain architecture, repository-specific +tests, runtime/build/deploy definitions, environment policy, rollback, and +product ownership. Their central integration is a required-workflow enrollment +or small explicit caller/configuration with versioned inputs and named secrets. +No leaf may silently redefine a central evidence name or merge authority. A +leaf-local compatibility worker is an explicit temporary override with owner, +scope, and exit condition. + +## Consequences + +A central defect can affect many repositories, so protected-main consumer +canaries and rollback are mandatory. Central fixes are reviewable once and +fleet audit can detect enrollment drift. Leaf repositories remain independently +buildable and releasable, but central policy changes require careful interface +compatibility. + +## Failure and recovery + +If central behavior regresses, limit the affected dispatch/required-workflow +path, select the last known-good protected revision, use a reviewed revert or +explicit caller pin, and validate a representative consumer before restoring +fleet operation. One broken leaf defers only that repository unless evidence +shows a central boundary defect. + +## Security and governance + +Privileged code is protected and cannot be selected by PR-controlled refs. +Cross-repository inputs are strict and revalidated. Secret interfaces follow +ADR-0004; review/merge authority follows ADR-0005. The fleet auditor remains +read-only. Centralization does not grant the control plane release or deployment +authority for leaf products. + +## Verification + +Contract tests cover required/reusable workflow provenance, allowed inputs, +named secrets, default permissions, target-repository context, central-vs-leaf +responsibility, drift detection, and compatibility pins. Protected-main +acceptance runs the central definition in a real enrolled leaf. + +## Migration and rollback + +Inventory thick copies and repository-specific deviations, define the stable +central interface, enroll a canary leaf, migrate cohorts, audit drift, and +remove copies only after acceptance. Preserve each product's native tests and +release path. Rollback uses a known-good central revision or bounded leaf +compatibility bridge; it does not fork policy indefinitely. + +## Supersession + +This ADR is current. A successor may introduce another control-plane platform +only if it retains thin explicit leaf contracts, product independence, +least-privilege cross-repository boundaries, fleet audit, staged acceptance, +and rollback. diff --git a/docs/automation/adr/README.md b/docs/automation/adr/README.md new file mode 100644 index 000000000..d9f8eb2d2 --- /dev/null +++ b/docs/automation/adr/README.md @@ -0,0 +1,28 @@ +# Architecture decision records + +These ADRs are the minimum durable decision set for the CWL automation control +plane. An accepted ADR records policy and design intent; its implementation +state is separately identified in the ADR and in +[../DOCUMENTATION_COVERAGE.md](../DOCUMENTATION_COVERAGE.md). New evidence may +supersede an ADR, but must not silently rewrite its historical decision. + +| ADR | Decision | Status | +|---|---|---| +| [ADR-0001](0001-writer-lease-and-read-only-fleet-auditor.md) | One branch writer lease; fleet audit remains read-only | Accepted | +| [ADR-0002](0002-exact-head-and-live-base-binding.md) | Bind evidence to exact source head and independently observed live base | Accepted | +| [ADR-0003](0003-classified-bounded-retries.md) | Retry only classified transient failures within a fixed budget | Accepted | +| [ADR-0004](0004-minimal-reusable-workflow-secrets.md) | Reusable workflows use explicit minimal secret interfaces | Accepted; migration in progress | +| [ADR-0005](0005-independent-review-governance.md) | Keep advisory automation separate from counted independent approval | Accepted | +| [ADR-0006](0006-protected-main-operational-closure.md) | Operational incidents close only after protected-main consumer evidence | Accepted | +| [ADR-0007](0007-work-conserving-automation.md) | Local waits defer one item; the automation continues another safe lane | Accepted | +| [ADR-0008](0008-central-control-plane-thin-leaf-contract.md) | Centralize privileged policy and keep leaf integrations thin | Accepted | + +## Lifecycle + +1. **Proposed:** reviewable decision with explicit alternatives and migration. +2. **Accepted:** governing decision; not necessarily fully implemented. +3. **Superseded:** retained for history and linked to its replacement. +4. **Rejected:** retained when its reasoning is useful. + +Implementation evidence belongs in code, tests, exact-head checks, and +protected-main consumer receipts, not in an ADR status label alone. diff --git a/docs/automation/review-agent-comment-invocation.md b/docs/automation/review-agent-comment-invocation.md index 51c84dcde..8e3f5feaf 100644 --- a/docs/automation/review-agent-comment-invocation.md +++ b/docs/automation/review-agent-comment-invocation.md @@ -11,6 +11,18 @@ Trusted ContextualWisdomLab maintainers can invoke the existing review planes fr The router never checks out or executes pull-request-controlled code. It reads live PR metadata, binds the request to the current head SHA and base branch, and dispatches the already deployed central workflows in `ContextualWisdomLab/.github`. +### Audited end-to-end limitation + +At protected-main baseline `6eb06cdd`, that binding is authoritative for the +router/wrapper claim, not yet for the complete downstream review path. The +OpenCode wrapper's review-only flags and the OpenCode/Noema exact SHA fields are +not all revalidated at the authoritative execution/publication boundary; +downstream scheduling can re-enter the default merge mode. The strict +snapshot-only route is pending in `ContextualWisdomLab/.github#840`. Until that +lands and passes consumer acceptance, a mention acknowledgement or immutable +claim is dispatch evidence, not proof that the final review used the claimed +snapshot or remained review-only. + ## Architecture GitHub organization ruleset workflows support `pull_request`, `pull_request_target`, and `merge_group`, but not `issue_comment`. Separately, an `issue_comment` workflow runs only when that workflow file exists on the commented repository's default branch. Therefore, a workflow stored only in the central `.github` repository cannot directly receive comments created in sibling repositories. @@ -45,8 +57,8 @@ This preserves the central MSA boundary without copying privileged workflow code - `contents: write` is intentionally retained only on jobs that call GitHub's create-repository-dispatch endpoint. GitHub documents that endpoint as requiring Contents repository permission at write level. Removing it would disable the bounded central dispatch path; broad workflow-default write access is not granted. - The organization sweep uses the established cross-repository credential chain for reading target comments, while the central repository's own short-lived job token dispatches the central workflows. - OpenCode dispatch is restricted to the exact `OPENCODE_REPOSITORY_DISPATCH_TARGETS` allowlist. -- An invocation cannot merge: `enable_auto_merge=false`, `update_branches=false`, and `merge_mode=disabled` are explicit in the dispatch payload. -- Every dispatch is bound to live PR number, current head SHA, base branch, source comment, requested agent, and requesting actor metadata fetched or validated immediately before dispatch. +- The router requests review-only execution with `enable_auto_merge=false`, `update_branches=false`, and `merge_mode=disabled`; end-to-end preservation is pending in `.github#840`. +- Every router/wrapper claim is bound to live PR number, current head SHA, base branch, source comment, requested agent, and requesting actor metadata fetched or validated immediately before dispatch. The authoritative review boundary must revalidate the tuple before this becomes end-to-end evidence. - Router jobs use the fixed `ubuntu-24.04` runner and an immutable `actions/checkout` v7.0.1 commit pin; checkout credentials are not persisted. - A branch-selectable `workflow_dispatch` trigger is intentionally absent. This prevents a repository writer from choosing an unreviewed branch version of the central router while the job holds dispatch permissions. @@ -54,7 +66,9 @@ This preserves the central MSA boundary without copying privileged workflow code - `AGENT_MENTION_LOOKBACK_HOURS`: default `168`, allowed range 1–720. - `AGENT_MENTION_MAX_DISPATCHES`: default `20`, allowed range 1–100. The bound counts source requests that actually queue at least one new agent, not historical no-ops. -- Durable invocation claims use 30-day artifact retention. A new source comment creates a new invocation key when an intentional retry is required. +- Durable invocation claims use 30-day artifact retention and at-most-once + forwarding. A failed forward after claim requires a new source comment today; + recoverable claim states are tracked in `ContextualWisdomLab/.github#893`. - Operators request immediate work by writing an exact trusted mention on the target pull request; otherwise, the five-minute protected-default-branch sweep processes it. - The sweep fails visibly when no cross-repository credential is available. - `PR_REVIEW_MERGE_TOKEN` or `OPENCODE_APPROVE_TOKEN` takes precedence. Otherwise, the workflow exchanges its OIDC token for the existing OpenCode installation token and enumerates that installation's repositories. diff --git a/docs/nvidia-nim-opencode-hotfix.md b/docs/nvidia-nim-opencode-hotfix.md index df8c193b2..4173b6048 100644 --- a/docs/nvidia-nim-opencode-hotfix.md +++ b/docs/nvidia-nim-opencode-hotfix.md @@ -1,5 +1,9 @@ # NVIDIA NIM OpenCode model priority (hotfix) +Status: historical rollout note. Current secret and governance contracts are +normative in `docs/automation/SECURITY.md`; administrative bypass is not an +accepted activation or rollback path. + ## Why OpenCode Agent failed to produce a usable review on the PR thread starting at @@ -14,25 +18,24 @@ still emit APPROVE / REQUEST_CHANGES when GitHub Models / free tiers stall. - `enabled_providers`: `nvidia-nim` first, then `github-models` - default `model` / `small_model` prefer NIM Nemotron / Llama 3.3 - new OpenAI-compatible provider `nvidia-nim` → `https://integrate.api.nvidia.com/v1` - with `apiKey: {env:NVIDIA_API_KEY}` + with process-local `apiKey: {env:NVIDIA_API_KEY}`, sourced only from the + scoped organization secret `NVIDIA_NIM_API_KEY` 2. `.github/workflows/opencode-review-dispatch.yml` - `OPENCODE_MODEL_CANDIDATES` prefixes six NIM models before existing pool - - binds `NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}` + - binds process-local `NVIDIA_API_KEY` from `${{ secrets.NVIDIA_NIM_API_KEY }}` 3. `scripts/ci/run_opencode_review_model_pool.sh` - - skips `nvidia-nim/*` when `NVIDIA_API_KEY` is unset (same pattern as OpenRouter) - -## Temporary permission bypass (hotfix only) + - skips `nvidia-nim/*` when scoped `NVIDIA_NIM_API_KEY` is unset -For this merge-aid hotfix only: +## Historical activation constraint -- Branch-protection / ruleset admin override may be used to land the central - `.github` change if required checks conflict during the hotfix window. +- Branch protection and rulesets are not bypassed to activate a model-provider + change; eligible review and protected-main consumer evidence remain required. - **Do not** permanently weaken Security Scan, trivy-fs, osv-scan, or CodeQL gates. - **Do not** flip OpenCode agent `permission.edit` / `bash` from `deny` to `allow` permanently; review agents remain read-only. -- Org secret `NVIDIA_API_KEY` must be set on ContextualWisdomLab for NIM pool - entries to execute; without it the pool falls through to prior candidates. +- Org secret `NVIDIA_NIM_API_KEY` must be set on ContextualWisdomLab for NIM + pool entries to execute; without it the pool falls through to prior candidates. ## Rollback @@ -42,8 +45,9 @@ catalog reliability is restored. ## Secret name -Org secret is **`NVIDIA_NIM_API_KEY`**. Workflows bind it to process env `NVIDIA_API_KEY` -(fallback: `secrets.NVIDIA_API_KEY` if present) so `opencode.jsonc` `{env:NVIDIA_API_KEY}` resolves. +Org secret is **`NVIDIA_NIM_API_KEY`**. Workflows bind it to process env +`NVIDIA_API_KEY`; there is no fallback to a legacy `secrets.NVIDIA_API_KEY`, so +`opencode.jsonc` `{env:NVIDIA_API_KEY}` resolves only from the scoped secret. ## Large-repo OpenCode timeouts (~1 hour) diff --git a/docs/org-required-workflow-rollout.md b/docs/org-required-workflow-rollout.md index 9c42ab063..cc2d35833 100644 --- a/docs/org-required-workflow-rollout.md +++ b/docs/org-required-workflow-rollout.md @@ -43,7 +43,11 @@ The central `.github/workflows/opencode-review.yml` is now part of the active or - Model token posture: use the organization `STRIX_GITHUB_MODELS_TOKEN` secret for GitHub Models calls, with `github.token` as the fallback; live workflow evidence showed `github.token` alone can return 403 from `models.github.ai/inference` - Write posture: OpenCode may create review/comment side effects through the OpenCode app token when available; the workflow token is limited to the same-repository PR context and publication failures remain visible - Coverage execution posture: PR-controlled package, test, build, R, Rust, and Docker inputs are never executed from `pull_request_target`; the dispatch workflow runs bounded low-privilege coverage only after exact live metadata and scheduler identity validation -- Fork posture: PR heads are fetched through `refs/pull//head` when direct head-SHA fetch is not available, so review can inspect fork PR source as data without executing it in the trusted workflow context +- Fork posture: the lower-level materializer can fetch `refs/pull//head`, + but current privileged scheduler/OpenCode/Strix targeted entrypoints reject a + head repository different from the base. External-head review is not an + end-to-end deployed capability; alignment is tracked in + `ContextualWisdomLab/.github#889`. - Runtime posture: pre-model failed-check evidence waits are capped at about five minutes; the later approval gate rechecks current-head peer checks and extends its bounded wait only while image-validation checks remain pending, logging the reason before approval - Model-exhaustion posture: command exit codes and deterministic checks cannot synthesize an approval. Exhaustion remains `MODEL_OUTPUT_UNAVAILABLE`; only a prior real-model approval bound to the exact current head can satisfy the review gate after all checks, alerts, and threads are revalidated. - Adversarial-evidence posture: every probe must cite its exact changed path and positive in-range line in the materialized current-head source tree. Unrelated paths, nonexistent lines, circular claims, and missing observed results fail closed with a concrete rejection reason. @@ -127,11 +131,14 @@ The central `.github/workflows/pr-review-merge-scheduler.yml` is now part of the - Token posture: the workflow passes the first available mutation credential in this order: `PR_REVIEW_MERGE_TOKEN`, `OPENCODE_APPROVE_TOKEN`, exchanged OpenCode GitHub App token, then the target repository workflow token. The scheduler reports the non-secret token source and expected actor class in every mutation decision. - Flow posture: default branches named `main` or `master` are treated as GitHub Flow; default branches named `develop` are treated as Git Flow unless a repository explicitly sets `PROJECT_FLOW` - Merge posture: the default merge mode is `direct_or_auto`. When a current-head approved PR is same-repository and the scheduler has no failed-check, action-required, unresolved-thread, or conflict blocker, it requests an immediate guarded squash merge with `--match-head-commit`. This includes PRs where native GitHub auto-merge is already enabled; native auto-merge is a fallback queue, not the scheduler's first stop when direct merge is possible. -- Fork posture: fork or external-head PRs remain reviewable, but the scheduler does not direct-merge them and does not enable auto-merge for them. A maintainer must make the final merge decision after same-head OpenCode approval, same-head Strix evidence, required checks, and unresolved-thread checks are clean. +- Fork posture: current privileged targeted dispatch rejects fork/external + heads. They are neither centrally reviewable nor mutable through this path; + `ContextualWisdomLab/.github#889` will choose a safe review-only contract or a + consistently documented rejection/import path. - Branch freshness posture: the scheduler also runs after protected base-branch pushes to `main`, `develop`, or `master`, because those pushes can create the GitHub UI state where reviews are satisfied, auto-merge is enabled, checks are stale or failed, and the PR shows `Update branch` without a PR `synchronize` event. - Auto-merge posture: `auto_merge_enabled` PR events trigger the scheduler so an already stale branch is refreshed immediately after native auto-merge is turned on instead of waiting for the periodic schedule. If the same PR is already mergeable, the scheduler attempts the guarded direct merge immediately. - Automation boundary: current-head failed checks and `ACTION_REQUIRED` checks are reported before branch updates, so an update attempt does not hide the concrete reason a PR cannot merge. `update-branch` handles approved `BEHIND` PRs and already queued auto-merge PRs only when there is no current-head failed or action-required check to diagnose first. `DIRTY` or `CONFLICTING` PRs still require author or maintainer conflict resolution guidance; current-head approved conflicts may keep or queue native GitHub auto-merge as a wait state while the conflict is repaired, but the scheduler must not treat queued auto-merge as a conflict resolver. -- Retry posture: before retrying OpenCode, the scheduler force-cancels older active OpenCode runs for the same PR number and a previous head SHA. It does not automatically cancel Strix runs because security evidence should not be silently discarded by force-push churn. +- Retry posture: before retrying OpenCode, the scheduler force-cancels older active OpenCode runs for the same PR number and a previous head SHA. Strix itself uses event/repository/PR-number concurrency with `cancel-in-progress: true`; the replacement run resolves the current PR head at execution time, while repository-dispatch and required-workflow event classes cannot cancel each other. Do not centralize the scheduler by running a `.github` scheduled job against other repositories with the `.github` repository token. That would either fail permission checks or use the wrong mutation actor. The central path is a required workflow executed in each target repository context. @@ -211,9 +218,14 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list. 4. Git Flow repositories are those whose default branch is `develop`. 5. OpenCode remains responsible for review judgment and structured decisions. 6. GitHub Actions remains responsible for mechanical branch updates and merges. -7. A merge is acceptable only when the current head has required checks passing, distinct current-head OpenCode and Noema approvals, no unresolved review threads, and a clean or mergeable merge state. +7. A merge is acceptable only when the current head has required checks, + exactly two eligible current-head formal approvals, no unresolved review + threads, and a clean or policy-accepted merge state. Noema is deployed as a + distinct identity, but the scheduler does not require that one counted + approval came from Noema; `ContextualWisdomLab/.github#772` tracks that gap. 8. Previous-head approvals or checks are not merge evidence. -9. Same-repository approved PRs should merge immediately when GitHub reports `CLEAN`; fork or external-head PRs are excluded from scheduler merge and auto-merge. +9. Same-repository approved PRs may merge only through the guarded policy; + fork/external-head targeted review/update/merge is currently rejected. ## Evidence from this rollout @@ -251,7 +263,10 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list. - Live non-fork inventory on 2026-07-02 18:15 KST found 17 public non-fork repositories, inherited ruleset `18156473` on `kaefa` and `waf-ids-ai-soc`, and no default-branch copies of `opencode-review.yml`, `strix.yml`, or `pr-review-merge-scheduler.yml` outside `.github`. - `ContextualWisdomLab/waf-ids-ai-soc` PR `#6` merged at `e1c0a85fd4a8e6dd67039be43eb7f659fec22abd` after central required workflow proof on head `43b62b5f347d1532c81b5ae38d8e41b4494fd486`; PR `#8` current head `48d8b56a0f995829fc95de4fed129d1c33aaadff` is now the open runtime proof fixture with central and local Rust checks queued at the 2026-07-02 18:15 KST refresh. - `ContextualWisdomLab/kaefa` inherits ruleset `18156473`, but PR `#60` current head `13c9089855fcdd34391173560ccf6935bac1eebe` showed only repo-local R-CMD-check, dependency-review, and CodeQL signals in status rollup. Treat this as a runtime proof gap until a new PR event or manual dispatch proves central OpenCode, Strix, and scheduler checks on a kaefa current head. -- `.github` scheduler default merge mode is now `direct_or_auto`: approved same-repository `CLEAN` PRs request immediate guarded merge, approved non-clean same-repository PRs can queue native auto-merge, and fork or external-head PRs are left for maintainer merge. +- `.github` scheduler default merge mode is `direct_or_auto`; its complete + state/credential decision table and external-head contract are tracked in + `ContextualWisdomLab/.github#892` and + `ContextualWisdomLab/.github#889`, respectively. - OpenCode approval runs the trusted central merge scheduler script directly with `pr_number` and `max_prs=1`, so the just-reviewed PR is inspected immediately even when organization required workflows are not repo-local `workflow_dispatch` targets. - `.github` PR `#74` changed OpenCode review model order to DeepSeek R1 first and added a catalog fallback pool. - `.github` PR `#75` removed the Strix finding against the scheduler command wrapper by using `subprocess.run(..., check=True)` and preserving the existing scrubbed failure contract. @@ -301,7 +316,7 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list. - Existing open PRs may need a new push or base update before the latest required workflow SHA appears on their current head. - The central OpenCode workflow now retries DeepSeek R1, DeepSeek V3, GPT-5, and a catalog fallback pool. Keep model/tooling failures out of PR comments unless there is a source-backed failed-check diagnosis. -- The central OpenCode config includes a read-only `code-reviewer` subagent for focused review passes. The subagent may read, grep, glob, and run safe local verification commands, but it must not edit files, stage changes, commit, push, install dependencies, mutate branches, or touch production state. +- The central OpenCode config includes a read-only `code-reviewer` subagent for focused review passes. It may read, grep, glob, and list trusted bounded evidence, but bash, task/subagents, network, LSP, MCP, external-directory access, edit, stage, commit, push, dependency installation, branch mutation, and production access are denied. Trusted workflow receipts supply execution evidence. - OpenCode execution evidence must be sandboxed in the CI workspace or an isolated temporary directory, with a credential-scrubbed environment by default and no persistent mutation outside test caches or scratch files. Prefer `python3 scripts/ci/sandboxed_verify.py --repo-root -- ` when the central helper is available, and cite its `SANDBOXED_VERIFY_RESULT` line. When repo-native verification legitimately needs network access or GitHub Secrets, pass only the needed names with `--allow-env`, record `--network required`, and explain it with `--evidence-note` without printing secret values. The helper does not replace existing bash, task, webfetch, websearch, lsp, CodeGraph, DeepWiki, Context7, or web_search review policy. If a verification cannot be sandboxed without changing the result, the review must say so instead of presenting an unsafe run as evidence. - Web application reviews should run backend, frontend, and repository-native E2E checks together through `python3 scripts/ci/sandboxed_web_e2e.py --repo-root --backend-cmd --frontend-cmd --e2e-cmd ` when those contracts exist, then cite `SANDBOXED_WEB_E2E_RESULT`. If backend/frontend/E2E/readiness contracts are missing, the review must name the gap instead of treating unit or lint evidence as full E2E proof. - Bounded OpenCode evidence includes `Review execution contracts`, which inventories runtime matrices, package manifests, test, coverage, docstring, E2E, lint, security, Docker, and unpackaged-source gaps before the model chooses verification commands. diff --git a/docs/sbom/inventory.md b/docs/sbom/inventory.md index 8892aa3b3..e63b82b69 100644 --- a/docs/sbom/inventory.md +++ b/docs/sbom/inventory.md @@ -1,6 +1,11 @@ # Organization SBOM inventory -Generated: pending first scheduled run +Generated: no successful scheduled inventory persisted at this revision + +The zero rows below are an unmaterialized generated-artifact state, not proof +that the organization has zero repositories/components or no policy findings. +Use an attributable successful `SBOM Inventory Scheduler` run before making a +fleet claim. One central view of every managed repository's software components, versions, and licenses. Feeds license and vulnerability governance diff --git a/docs/scorecard-governance.md b/docs/scorecard-governance.md index 80f6cc130..cc45ee2eb 100644 --- a/docs/scorecard-governance.md +++ b/docs/scorecard-governance.md @@ -15,9 +15,9 @@ or the inherited organization ruleset must require all of the following: to pass against the latest head commit before merge; - stale approvals to be dismissed after a push; - current-head OpenCode review evidence from the central required workflow; -- code owner review coverage through CODEOWNERS-owned workflow and CI paths, - with the organization required-workflow ruleset carrying the enforceable - single-maintainer approval gate; +- code owner coverage for workflow and CI paths, with the organization + required-workflow ruleset carrying the enforceable exactly two eligible + approvals, stale-dismissal, and last-pusher protection; - review thread resolution before merge; - last-pusher approval protection; - force-push and branch deletion protection. diff --git a/tests/test_automation_documentation_contract.py b/tests/test_automation_documentation_contract.py new file mode 100644 index 000000000..ca18ab498 --- /dev/null +++ b/tests/test_automation_documentation_contract.py @@ -0,0 +1,257 @@ +"""Contract tests for the central automation documentation spine.""" + +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +AUTOMATION_DOCS = ROOT / "docs" / "automation" + +REQUIRED_DOCUMENTS = ( + "README.md", + "PRD.md", + "TRD.md", + "ARCHITECTURE.md", + "UML.md", + "ERD.md", + "SECURITY.md", + "THREAT_MODEL.md", + "TEST_STRATEGY.md", + "OPERABILITY.md", + "INCIDENT_RUNBOOK.md", + "TRACEABILITY.md", + "DOCUMENTATION_COVERAGE.md", +) + +REQUIRED_ADRS = ( + "0001-writer-lease-and-read-only-fleet-auditor.md", + "0002-exact-head-and-live-base-binding.md", + "0003-classified-bounded-retries.md", + "0004-minimal-reusable-workflow-secrets.md", + "0005-independent-review-governance.md", + "0006-protected-main-operational-closure.md", + "0007-work-conserving-automation.md", + "0008-central-control-plane-thin-leaf-contract.md", +) + +ADR_SECTIONS = ( + "## Context", + "## Decision drivers", + "## Considered alternatives", + "## Decision", + "## Consequences", + "## Failure and recovery", + "## Security and governance", + "## Verification", + "## Migration and rollback", + "## Supersession", +) + +PRD_IDS = tuple(f"PRD-{number:03d}" for number in range(1, 14)) +THREAT_IDS = tuple(f"TM-{number:03d}" for number in range(1, 15)) + + +class AutomationDocumentationContractTests(unittest.TestCase): + """Keep the control-plane documentation complete, linked, and code-current.""" + + def test_required_documentation_spine_exists_and_is_indexed(self) -> None: + """Every canonical document must exist and be linked from the index.""" + + index = (AUTOMATION_DOCS / "README.md").read_text(encoding="utf-8") + for relative_path in REQUIRED_DOCUMENTS: + path = AUTOMATION_DOCS / relative_path + self.assertTrue(path.is_file(), relative_path) + self.assertIn(f"]({relative_path})", index, relative_path) + + def test_required_adrs_have_complete_decision_records(self) -> None: + """Every minimum ADR must be indexed and contain the governed sections.""" + + index_path = AUTOMATION_DOCS / "adr" / "README.md" + self.assertTrue(index_path.is_file()) + index = index_path.read_text(encoding="utf-8") + for relative_path in REQUIRED_ADRS: + path = index_path.parent / relative_path + self.assertTrue(path.is_file(), relative_path) + self.assertIn(f"]({relative_path})", index, relative_path) + body = path.read_text(encoding="utf-8") + self.assertRegex(body, r"(?m)^# ADR-\d{4}: ") + self.assertRegex(body, r"(?m)^Status: (?:Accepted|Proposed|Superseded|Rejected)") + self.assertRegex(body, r"(?m)^Date: \d{4}-\d{2}-\d{2}$") + self.assertRegex(body, r"(?m)^Owner: \S.+$") + for section in ADR_SECTIONS: + self.assertIn(section, body, f"{relative_path}: {section}") + + def test_mermaid_diagrams_are_balanced_and_supported(self) -> None: + """Diagram-as-code fences must be balanced and use supported diagram types.""" + + allowed = ("flowchart ", "sequenceDiagram", "stateDiagram-v2", "erDiagram") + for relative_path in ("ARCHITECTURE.md", "UML.md", "ERD.md"): + body = (AUTOMATION_DOCS / relative_path).read_text(encoding="utf-8") + blocks = re.findall(r"```mermaid\n(.*?)\n```", body, flags=re.DOTALL) + self.assertTrue(blocks, relative_path) + self.assertEqual(body.count("```mermaid"), len(blocks), relative_path) + for block in blocks: + first_line = block.lstrip().splitlines()[0] + self.assertTrue(first_line.startswith(allowed), first_line) + + def test_workflow_references_resolve_to_tracked_files(self) -> None: + """Backticked workflow paths in canonical docs must resolve in this tree.""" + + for relative_path in REQUIRED_DOCUMENTS: + body = (AUTOMATION_DOCS / relative_path).read_text(encoding="utf-8") + for workflow in set(re.findall(r"`(\.github/workflows/[^`]+\.yml)`", body)): + self.assertTrue((ROOT / workflow).is_file(), f"{relative_path}: {workflow}") + + def test_evidence_authorities_and_domain_entities_are_explicit(self) -> None: + """Authority separation and evidence identity must not collapse into prose shortcuts.""" + + technical = (AUTOMATION_DOCS / "TRD.md").read_text(encoding="utf-8") + traceability = (AUTOMATION_DOCS / "TRACEABILITY.md").read_text(encoding="utf-8") + data_model = (AUTOMATION_DOCS / "ERD.md").read_text(encoding="utf-8") + for term in ( + "check evidence", + "status evidence", + "formal review evidence", + "merge authority", + "release authority", + ): + self.assertIn(term, technical.lower()) + for entity in ( + "automation_run", + "repository_target", + "pull_request_snapshot", + "source_revision", + "base_revision", + "check_evidence", + "review_evidence", + "workflow_evidence", + "dependency_evidence", + "incident_hypothesis", + "handoff_record", + "operational_acceptance", + "secret_requirement", + "writer_lease", + ): + self.assertIn(entity, data_model) + self.assertIn("conceptual", data_model.lower()) + self.assertIn("persisted", data_model.lower()) + self.assertIn("ContextualWisdomLab/.github#840", traceability) + self.assertIn("ContextualWisdomLab/.github#842", traceability) + + def test_entry_points_link_the_authoritative_index(self) -> None: + """Agent and maintainer entry points must discover the canonical spine.""" + + expected_link = "docs/automation/README.md" + for relative_path in ( + "README.md", + "AGENTS.md", + "CLAUDE.md", + "docs/CWL-MASTER-CONTEXT.md", + ): + body = (ROOT / relative_path).read_text(encoding="utf-8") + self.assertIn(expected_link, body, relative_path) + + def test_local_markdown_links_resolve(self) -> None: + """Canonical relative Markdown links must resolve to a file in this tree.""" + + for path in AUTOMATION_DOCS.rglob("*.md"): + body = path.read_text(encoding="utf-8") + for target in re.findall(r"\[[^\]]+\]\(([^)]+)\)", body): + target = target.split("#", 1)[0].split("?", 1)[0] + if not target or "://" in target or target.startswith("mailto:"): + continue + self.assertTrue((path.parent / target).resolve().is_file(), f"{path}: {target}") + + def test_secret_registry_exactly_covers_workflow_secret_names(self) -> None: + """Every workflow secret name needs a value-free governed registry row.""" + + workflow_text = "\n".join( + path.read_text(encoding="utf-8") + for path in sorted((ROOT / ".github" / "workflows").glob("*.yml")) + ) + used_names = set(re.findall(r"secrets\.([A-Z][A-Z0-9_]*)", workflow_text)) + security = (AUTOMATION_DOCS / "SECURITY.md").read_text(encoding="utf-8") + registry_names = set( + re.findall(r"(?m)^\| `([A-Z][A-Z0-9_]*)` \|", security) + ) + self.assertEqual(used_names, registry_names) + self.assertIn("minimum scope", security.lower()) + self.assertIn("rotation or revocation", security.lower()) + + def test_requirement_and_threat_ids_are_fully_traced(self) -> None: + """No product requirement or registered threat may be orphaned.""" + + product = (AUTOMATION_DOCS / "PRD.md").read_text(encoding="utf-8") + threats = (AUTOMATION_DOCS / "THREAT_MODEL.md").read_text(encoding="utf-8") + trace = (AUTOMATION_DOCS / "TRACEABILITY.md").read_text(encoding="utf-8") + for requirement_id in PRD_IDS: + self.assertIn(requirement_id, product) + self.assertIn(requirement_id, trace) + for threat_id in THREAT_IDS: + self.assertIn(threat_id, threats) + self.assertIn(threat_id, trace) + + technical = (AUTOMATION_DOCS / "TRD.md").read_text(encoding="utf-8") + for gap_id in (f"IG-00{number}" for number in range(1, 9)): + self.assertIn(gap_id, technical) + for issue_number in (772, 840, 842, 889, 890, 891, 892, 893, 894): + self.assertIn(f"ContextualWisdomLab/.github#{issue_number}", trace) + + def test_documented_live_invariants_match_executable_contracts(self) -> None: + """Prevent known dated-prose contradictions from returning.""" + + strix = (ROOT / ".github" / "workflows" / "strix.yml").read_text( + encoding="utf-8" + ) + concurrency = strix.split("concurrency:", 1)[1].split("permissions:", 1)[0] + self.assertIn("cancel-in-progress: true", concurrency) + self.assertIn("github.event.pull_request.number", concurrency) + self.assertNotIn("github.event.pull_request.head.sha", concurrency) + + root_readme = (ROOT / "README.md").read_text(encoding="utf-8") + rollout = (ROOT / "docs" / "org-required-workflow-rollout.md").read_text( + encoding="utf-8" + ) + audit = (ROOT / "PR_GOVERNANCE_AUDIT.md").read_text(encoding="utf-8") + for body in (root_readme, rollout, audit): + self.assertNotIn("cancel-in-progress: false", body) + self.assertNotIn("scopes PR Strix concurrency by head SHA", body) + self.assertIn("PR-number concurrency", root_readme) + + reviewer_prompt = (ROOT / "code-reviewer-prompt.md").read_text(encoding="utf-8") + reviewer_config = (ROOT / "opencode.jsonc").read_text(encoding="utf-8") + self.assertIn("Bash, task/subagents, webfetch", reviewer_prompt) + for denied_tool in ('"bash": "deny"', '"task": "deny"', '"webfetch": "deny"'): + self.assertIn(denied_tool, reviewer_config) + for body in (root_readme, rollout): + self.assertNotIn("run safe local verification commands", body) + + scorecard = (ROOT / "docs" / "scorecard-governance.md").read_text( + encoding="utf-8" + ) + ruleset_auditor = ( + ROOT / "scripts" / "ci" / "audit_central_required_workflows.py" + ).read_text(encoding="utf-8") + self.assertIn("exactly two eligible", scorecard) + self.assertIn('approving_reviews != 2', ruleset_auditor) + self.assertNotIn("single-maintainer approval gate", scorecard) + + self.assertIn("central autofix worker", rollout) + self.assertNotIn("keep autofix workflows repo-local", audit.lower()) + self.assertNotIn("fork or external-head PRs remain reviewable", rollout) + self.assertIn("ContextualWisdomLab/.github#889", rollout) + + def test_canonical_docs_have_no_placeholders(self) -> None: + """Canonical documents must not carry ambiguous unfinished markers.""" + + placeholder = re.compile(r"\b(?:TBD|TODO|FIXME)\b") + for relative_path in REQUIRED_DOCUMENTS: + body = (AUTOMATION_DOCS / relative_path).read_text(encoding="utf-8") + self.assertIsNone(placeholder.search(body), relative_path) + + +if __name__ == "__main__": + unittest.main()