From a7ce5e7ed56499742e6307bce41c19b9c3453bed Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 22:10:01 +0900 Subject: [PATCH 01/11] fix(automation): isolate interactive mention concurrency --- .github/workflows/agent-mention-router.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/agent-mention-router.yml b/.github/workflows/agent-mention-router.yml index f14667a93..b922ba5ab 100644 --- a/.github/workflows/agent-mention-router.yml +++ b/.github/workflows/agent-mention-router.yml @@ -6,10 +6,6 @@ on: schedule: - cron: "*/5 * * * *" -concurrency: - group: review-agent-mention-router-${{ github.repository }} - cancel-in-progress: false - # Organization required-workflow rules do not propagate issue_comment events # into sibling repositories. Keep the workflow default read-only; each bounded # job declares only the writes it actually needs. @@ -28,6 +24,9 @@ jobs: contains(github.event.comment.body, '@cwl-noema-review') || contains(github.event.comment.body, '@opencode-agent') ) + concurrency: + group: review-agent-mention-router-local-${{ github.repository }} + queue: max runs-on: ubuntu-24.04 timeout-minutes: 5 permissions: @@ -70,6 +69,9 @@ jobs: if: >- github.repository == 'ContextualWisdomLab/.github' && github.event_name == 'schedule' + concurrency: + group: review-agent-mention-router-sweep-${{ github.repository }} + cancel-in-progress: false runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: From 455e324dacbdd0b0bca9ae6ad42f165593836bc1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 22:10:33 +0900 Subject: [PATCH 02/11] test(automation): lock independent mention queues --- tests/test_agent_mention_workflow_contract.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_agent_mention_workflow_contract.py b/tests/test_agent_mention_workflow_contract.py index c5fc4cae5..4f9f843ef 100644 --- a/tests/test_agent_mention_workflow_contract.py +++ b/tests/test_agent_mention_workflow_contract.py @@ -10,6 +10,23 @@ CHECKOUT_PIN = "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1" +def _job_block(workflow: str, job_name: str, next_job_name: str | None) -> str: + """Return one top-level job block bounded by the following job.""" + jobs = workflow.split("\njobs:\n", 1)[1] + start = jobs.index(f" {job_name}:\n") + if next_job_name is None: + return jobs[start:] + end = jobs.index(f"\n {next_job_name}:\n", start) + return jobs[start:end] + + +def _concurrency_block(job: str) -> str: + """Return the exact job-scoped concurrency mapping before ``runs-on``.""" + start = job.index(" concurrency:\n") + end = job.index("\n runs-on:", start) + return job[start:end] + + def test_workflow_uses_local_event_and_central_sweep_with_job_scoped_writes() -> None: """The router is central-only, scheduled, and least-privileged.""" @@ -51,6 +68,31 @@ def test_workflow_uses_local_event_and_central_sweep_with_job_scoped_writes() -> assert "agent_mention_sweep.py" in sweep +def test_interactive_mentions_and_sweeps_have_independent_queue_contracts() -> None: + """Scheduled sweeps cannot replace a pending trusted comment invocation.""" + + text = WORKFLOW.read_text(encoding="utf-8") + header = text.split("\njobs:\n", 1)[0] + local_job = _job_block( + text, + "route-local-agent-mention", + "sweep-organization-agent-mentions", + ) + sweep_job = _job_block(text, "sweep-organization-agent-mentions", None) + + assert "\nconcurrency:\n" not in header + assert _concurrency_block(local_job) == ( + " concurrency:\n" + " group: review-agent-mention-router-local-${{ github.repository }}\n" + " queue: max" + ) + assert _concurrency_block(sweep_job) == ( + " concurrency:\n" + " group: review-agent-mention-router-sweep-${{ github.repository }}\n" + " cancel-in-progress: false" + ) + + def test_quality_workflow_measures_exact_files_without_module_name_warnings() -> None: """Coverage includes the two script paths instead of treating paths as modules.""" From ba65538dfee3ca501088890580450475884edaf2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 22:11:07 +0900 Subject: [PATCH 03/11] test(automation): bind concurrency doctoring to ledger contract --- tests/test_agent_mention_artifact_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_agent_mention_artifact_ledger.py b/tests/test_agent_mention_artifact_ledger.py index c527f4a6e..8eb86b65b 100644 --- a/tests/test_agent_mention_artifact_ledger.py +++ b/tests/test_agent_mention_artifact_ledger.py @@ -14,7 +14,7 @@ MODULE_PATH = ROOT / "scripts" / "ci" / "agent_mention_router.py" NOEMA_WORKFLOW = ROOT / ".github" / "workflows" / "agent-mention-noema-dispatch.yml" OPENCODE_WORKFLOW = ROOT / ".github" / "workflows" / "agent-mention-opencode-dispatch.yml" -DOC = ROOT / "docs" / "automation" / "review-agent-comment-invocation.md" +DOC = ROOT / "docs" / "doctoring" / "agent-mention-concurrency-isolation.md" UPLOAD_ARTIFACT_SHA = "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" From b1cf1f25f2e046a9c3bc6e59da93b022fdfaf546 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 22:11:40 +0900 Subject: [PATCH 04/11] docs(automation): record mention concurrency RCA and rollback --- .../agent-mention-concurrency-isolation.md | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/doctoring/agent-mention-concurrency-isolation.md diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md new file mode 100644 index 000000000..6f3a72524 --- /dev/null +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -0,0 +1,124 @@ +# Review-agent mention concurrency isolation + +검토 기준일: **2026-08-07** + +## Incident + +Trusted `@cwl-noema-review` and review-only `@opencode-agent` comments can remain unacknowledged even though the protected-default-branch router is enabled. The failure occurs before model execution: the central workflow mixes two event classes in one workflow-level concurrency group. + +- interactive `issue_comment` routing has a five-minute job timeout; +- the organization-wide sweep is scheduled every five minutes and has a fifteen-minute job timeout. + +GitHub Actions documents that a concurrency group permits one running member. With the default `queue: single`, at most one additional run can be pending; a newer queued run replaces the existing pending run even when `cancel-in-progress` is false. A scheduled sweep can therefore replace a pending trusted comment before exact-head resolution, durable ledger claim, dispatch, or acknowledgement. + +This is a queue-configuration defect, not evidence that the model, credential, allowlist, or review result is invalid. + +## Fail-first evidence + +Direct-main replacement PR #825 starts from protected `main` `1131b1bbafb24e455fc8619cdf316813e8721861`. Exact RED head `a319d513a2f67b707737651a9eb7fdbfe4bc23c4` changed only `tests/test_agent_mention_workflow_contract.py` and required separate job-scoped queue contracts while the inherited workflow still had one shared workflow-level group. + +This replacement does not reuse predecessor PR #815 or stacked development PR #824 checks, reviews, approvals, or mergeability evidence. + +## Decision + +Move concurrency from the workflow to the two jobs and give each event class a separate group. + +```yaml +route-local-agent-mention: + concurrency: + group: review-agent-mention-router-local-${{ github.repository }} + queue: max + +sweep-organization-agent-mentions: + concurrency: + group: review-agent-mention-router-sweep-${{ github.repository }} + cancel-in-progress: false +``` + +GitHub currently documents `queue: max` as allowing up to 100 pending jobs or workflow runs in a concurrency group. Waiting members are processed serially; runs beyond the queue limit are rejected. GitHub also documents that `queue: max` cannot be combined with `cancel-in-progress: true`. + +The interactive route therefore retains every bounded pending trusted comment up to the platform queue limit instead of replacing the previous pending request. The sweep keeps the default single-pending behavior: a running sweep is not interrupted, but obsolete pending sweeps may coalesce. Local routes and scheduled sweeps use different concurrency groups, so scheduled work cannot replace interactive work. + +Concurrency is not the durable idempotency authority. Duplicate forwarding remains governed by the canonical invocation key, exact-key downstream concurrency, and immutable exact-name Actions artifact ledger written before authoritative forwarding. + +## Data and authority flow + +```mermaid +sequenceDiagram + participant M as Trusted maintainer + participant L as Local comment queue + participant S as Scheduled sweep queue + participant R as Central router + participant D as Exact-key downstream dispatcher + participant A as Durable artifact ledger + participant V as Review workflow + + M->>L: issue_comment exact mention + S->>R: bounded organization sweep + L->>R: ordered interactive request + R->>D: canonical invocation payload + SHA-256 key + D->>A: claim exact ledger name + alt first live claim + D->>V: forward once + R-->>M: receipt / acknowledgement + else existing claim + D-->>R: duplicate suppressed + end +``` + +A receipt proves routing/claim processing occurred. It is not an approval and does not weaken exact-head checks, branch protection, or expected-head merge rules. + +## Preserved security and privacy boundaries + +- No model provider, reviewer identity, token name, secret, repository allowlist, dispatch payload, or permission changes. +- `COPILOT_GITHUB_TOKEN` remains unused. +- Workflow-default permissions remain `contents: read`; only existing job-scoped writes remain. +- The local route still accepts only non-bot `OWNER`, `MEMBER`, or `COLLABORATOR` comments on pull requests in the central repository. +- The sweep retains the configured organization-token / OpenCode installation-token credential chain. +- Pull-request number, base branch, base SHA, current head SHA, requesting actor, source comment identifier, and requested agent remain bound into the canonical invocation key. +- The exact-name Actions artifact ledger remains the authority for idempotent forwarding. +- The ledger keeps the existing **30-day artifact retention** and contains bounded invocation metadata, not comment bodies, model output, credentials, or business payloads. + +The privacy alternative to masking is separation and minimization: this automation does not require business PII. It processes bounded GitHub control-plane metadata under repository authorization rather than copying business records into model prompts or artifacts. + +## CSAP / SOC 2 readiness evidence + +This repair improves availability and processing-integrity evidence without claiming certification. + +| Control concern | Evidence | +| --- | --- | +| Change management | Protected pull request, exact-head checks, independent review, immutable commits | +| Availability | Separate local/sweep groups, bounded job timeouts, bounded interactive queue | +| Processing integrity | Canonical invocation key, exact-name artifact claim, duplicate suppression | +| Least privilege | Existing job-scoped permissions and credential separation remain unchanged | +| Monitoring | Queue delay, receipt delay, sweep duration, dispatch count, duplicate-claim outcome | +| Incident response | Fail-first contract, this doctoring record, rollout/rollback criteria | +| Privacy | Metadata-only routing; no business payload or credential retained in ledger | + +## Monitoring and acceptance + +After protected merge: + +1. create a fresh exact-head `@opencode-agent` and/or `@cwl-noema-review` request; +2. require a durable receipt or acknowledgement before relying on downstream review evidence; +3. verify that scheduled sweep runs do not cancel or replace pending interactive routes; +4. monitor local queue delay, sweep duration, dispatch count, duplicate-ledger outcomes, and downstream conclusions; +5. alert when an eligible comment has no receipt within the local five-minute execution timeout plus bounded queue delay; +6. alert when the interactive queue approaches the documented 100-pending limit; +7. keep metrics finite-cardinality and exclude comment text, source diffs, tokens, and model responses. + +A downstream reviewer may still fail closed because credentials, providers, checks, or exact-head evidence are unavailable. That remains distinct from a routing queue failure. + +## Rollback + +Rollback must preserve interactive requests. Restoring the shared workflow-level group is not acceptable. A safe emergency degradation is to suspend the scheduled sweep while retaining the isolated local queue. Removing `queue: max` from the local group requires another independently reviewed durable queue that preserves every eligible invocation. + +## References (APA 7th) + +GitHub. (n.d.). *Concurrency*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency + +GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency + +GitHub. (n.d.). *REST API endpoints for GitHub Actions artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/rest/actions/artifacts + +GitHub. (n.d.). *Store and share data with workflow artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/tutorials/store-and-share-data From 4881c7860a1ed503917e68cc096dbfb1cdb75b36 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 22:12:02 +0900 Subject: [PATCH 05/11] docs(automation): record isolated mention queues in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf30091dd..7901d7655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped. From 88d4315840d1aa0f3195693c3cf0ecd621b1f53f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 11:06:48 +0900 Subject: [PATCH 06/11] test(agent-mention): reject any root concurrency key --- tests/test_agent_mention_workflow_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_agent_mention_workflow_contract.py b/tests/test_agent_mention_workflow_contract.py index 4f9f843ef..7ed9aa71b 100644 --- a/tests/test_agent_mention_workflow_contract.py +++ b/tests/test_agent_mention_workflow_contract.py @@ -80,7 +80,7 @@ def test_interactive_mentions_and_sweeps_have_independent_queue_contracts() -> N ) sweep_job = _job_block(text, "sweep-organization-agent-mentions", None) - assert "\nconcurrency:\n" not in header + assert not any(line.startswith("concurrency:") for line in header.splitlines()) assert _concurrency_block(local_job) == ( " concurrency:\n" " group: review-agent-mention-router-local-${{ github.repository }}\n" From 6a1acb1559755c27d7267e39fd6275bb9db74dc9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 9 Aug 2026 11:07:23 +0900 Subject: [PATCH 07/11] docs(agent-mention): keep review evidence and queue alerts truthful --- docs/doctoring/agent-mention-concurrency-isolation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 6f3a72524..5dbf0a36f 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -87,7 +87,7 @@ This repair improves availability and processing-integrity evidence without clai | Control concern | Evidence | | --- | --- | -| Change management | Protected pull request, exact-head checks, independent review, immutable commits | +| Change management | Protected pull request and exact-head checks; qualifying independent review and post-integration evidence remain pending | | Availability | Separate local/sweep groups, bounded job timeouts, bounded interactive queue | | Processing integrity | Canonical invocation key, exact-name artifact claim, duplicate suppression | | Least privilege | Existing job-scoped permissions and credential separation remain unchanged | @@ -103,8 +103,8 @@ After protected merge: 2. require a durable receipt or acknowledgement before relying on downstream review evidence; 3. verify that scheduled sweep runs do not cancel or replace pending interactive routes; 4. monitor local queue delay, sweep duration, dispatch count, duplicate-ledger outcomes, and downstream conclusions; -5. alert when an eligible comment has no receipt within the local five-minute execution timeout plus bounded queue delay; -6. alert when the interactive queue approaches the documented 100-pending limit; +5. alert when an eligible interactive request has no receipt within **10 minutes** of comment creation (a CWL operational alert threshold, not a GitHub SLA): this permits at most five minutes of queue delay plus the existing five-minute local execution timeout before operator investigation; +6. alert immediately on a queue-limit rejection, unexpected cancellation of an interactive route, or when the interactive queue approaches the documented 100-pending limit; 7. keep metrics finite-cardinality and exclude comment text, source diffs, tokens, and model responses. A downstream reviewer may still fail closed because credentials, providers, checks, or exact-head evidence are unavailable. That remains distinct from a routing queue failure. From d3b22b3ccf05368a90b5da0a03ed14ab6b00119b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 13:46:42 +0900 Subject: [PATCH 08/11] docs(automation): cite AU-6 isolated mention-queue alerts Keep installer tests on the documented linux x86_64 path, record the isolated local/sweep queues, and cite NIST SP 800-53 AU-6/SC-5 so a missing receipt or 100-pending overflow is an availability alert. --- ARCHITECTURE.md | 93 +++++++++++++++++++ CHANGELOG.md | 3 +- CLAUDE.md | 4 +- .../agent-mention-concurrency-isolation.md | 16 ++++ ...st_materialize_base_python_requirements.py | 10 ++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 000000000..9e6e55a4c --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,93 @@ +# Architecture — ContextualWisdomLab `.github` + +This repository is the organization control plane. It is not naruon and it +does not own product data. Sibling products remain standalone modules; this +repo publishes org profile assets, reusable required workflows, and the +review/merge schedulers those products consume. + +## System context + +```mermaid +flowchart LR + Buyer["Commercial buyer / reviewer"] + Agents["Agents on AGENTS.md"] + Project["GitHub Project #1"] + Hub["This repo: org .github"] + Products["Owned products
naruon · orchestrator · engines"] + Runner["Required workflows in each repo context"] + + Buyer --> Hub + Agents --> Project + Agents --> Hub + Project --> Hub + Hub --> Runner + Runner --> Products + Products -->|"standalone or as module"| Buyer +``` + +## Isolated mention queues + +```mermaid +flowchart TD + Comment["Trusted issue_comment"] + Sweep["Five-minute organization sweep"] + Local["review-agent-mention-router-local
queue: max"] + SweepQ["review-agent-mention-router-sweep
cancel-in-progress: false"] + Ledger["Exact-name 30-day artifact ledger"] + + Comment --> Local + Sweep --> SweepQ + Local --> Ledger + SweepQ --> Ledger +``` + +A scheduled sweep cannot replace a pending interactive route. + +## Control-plane data flow + +```mermaid +sequenceDiagram + participant PR as Pull request + participant RW as Required workflows + participant OC as OpenCode reviewer + participant SV as sandboxed_verify / web E2E + participant MS as Merge scheduler + + PR->>RW: pull_request_target on trusted base + RW->>OC: bounded evidence + NVIDIA NIM / OpenCode + OC->>SV: PoC command in isolated copy + SV-->>OC: redacted stdout/stderr + command metadata + OC-->>PR: APPROVE or request changes + MS->>PR: merge only on current-head approval + green checks +``` + +## Trust boundaries + +- Required review workflows execute **base-branch** scripts. A PR that edits + those workflows cannot widen its own `pull_request_target` token. +- Reviewer agents stay `edit: deny`. They judge; they do not implement. +- Sandbox helpers copy the workspace, drop secret environment values unless + explicitly allowlisted by **name**, and run subprocesses with `shell=False`. +- Logs and review receipts redact credential shapes (tokens, bearer values, + known provider prefixes). They do not mask operational PII that the + control plane must process. +- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY` (env may be + `NVIDIA_API_KEY`). They never use `COPILOT_GITHUB_TOKEN`. Existing + review-agent key schemes stay unchanged. + +## Quality gates + +`scripts/ci/` ships with 100% statement/branch coverage and 100% docstrings. +CI installs Python tools only with `pip install --require-hashes`. Contract +tests pin workflow structure and governance prose so drift fails closed. + +## Related durable documents + +- [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) — mission and + ecosystem. +- [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md) + — Project #1 operation. +- [`PR_GOVERNANCE_AUDIT.md`](PR_GOVERNANCE_AUDIT.md) — live review/merge + contract. +- [`docs/doctoring/agent-mention-concurrency-isolation.md`](docs/doctoring/agent-mention-concurrency-isolation.md) + — current increment's queue-isolation decision and APA 7th citations. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7901d7655..40fe0eeb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. +- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. The decision record now cites NIST SP 800-53 AU-6/SC-5 so a 10-minute missing-receipt or 100-pending overflow is an availability alert, not a silent wait. +- Recorded the org control-plane architecture, including isolated mention queues, so agents reconstruct the trust boundary from the repo instead of private memory. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped. diff --git a/CLAUDE.md b/CLAUDE.md index 1c7bdb2f6..0f5dc337e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,9 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`. - `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. + `scorecard-governance.md`, SBOM inventory. Doctoring records live under + `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane + diagram for review, isolated mention queues, and merge trust boundaries. - `.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/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 5dbf0a36f..2ab233fe1 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -107,6 +107,14 @@ After protected merge: 6. alert immediately on a queue-limit rejection, unexpected cancellation of an interactive route, or when the interactive queue approaches the documented 100-pending limit; 7. keep metrics finite-cardinality and exclude comment text, source diffs, tokens, and model responses. +NIST SP 800-53 Rev. 5 AU-6 requires review of audit records and SC-5 requires +protection against resource exhaustion (National Institute of Standards and +Technology, 2020). The 10-minute receipt alert and the 100-pending overflow +signal are those controls: they distinguish a bounded wait from a dropped +trusted mention. GitHub documents that `queue: max` holds at most 100 pending +members and rejects overflow rather than replacing the oldest pending run +(GitHub, n.d.-a). + A downstream reviewer may still fail closed because credentials, providers, checks, or exact-head evidence are unavailable. That remains distinct from a routing queue failure. ## Rollback @@ -122,3 +130,11 @@ GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. Re GitHub. (n.d.). *REST API endpoints for GitHub Actions artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/rest/actions/artifacts GitHub. (n.d.). *Store and share data with workflow artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/tutorials/store-and-share-data + +GitHub. (n.d.-a). *Usage limits, billing, and administration*. GitHub Docs. +Retrieved August 13, 2026, from +https://docs.github.com/en/actions/reference/limits + +National Institute of Standards and Technology. (2020). *Security and +privacy controls for information systems and organizations* (NIST SP +800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5 diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 8a383f0c2..10f682b3e 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -30,6 +30,13 @@ def _created_tool_directory(path: Path) -> str: return str(path) +def _force_linux_x86_64_installer(monkeypatch: pytest.MonkeyPatch) -> None: + """Exercise the installer path that GitHub-hosted linux x86_64 runners use.""" + monkeypatch.setattr(materializer.sys, "platform", "linux") + monkeypatch.setattr(materializer.platform, "machine", lambda: "x86_64") + materializer._install_trusted_uv.cache_clear() + + def test_materializes_only_regular_hash_locks_from_exact_base(tmp_path: Path) -> None: """A PR-modified lock cannot enter the networked coverage image build context.""" repo = tmp_path / "repo" @@ -644,6 +651,7 @@ def test_install_trusted_uv_verifies_version_and_caches_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """The installer writes one executable, verifies its version, and caches it.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -690,6 +698,7 @@ def test_install_trusted_uv_rejects_version_process_failures( failure: OSError | subprocess.TimeoutExpired, ) -> None: """A missing or hung downloaded executable is removed and rejected.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -721,6 +730,7 @@ def test_install_trusted_uv_rejects_wrong_version_or_exit_status( completed: subprocess.CompletedProcess[bytes], ) -> None: """Unexpected version output or a nonzero status cannot satisfy the pin.""" + _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / f"uv-{completed.returncode}-{len(completed.stdout)}" monkeypatch.setattr( materializer.tempfile, From 863f97d96ca1eb8fe3212f8f4bf4232e177f16c1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 15:35:31 +0900 Subject: [PATCH 09/11] docs(automation): cite CWE-362 for isolated mention queues Record that one shared concurrency group is a race: a later sweep can replace a pending trusted comment before dispatch. --- ARCHITECTURE.md | 3 ++- CHANGELOG.md | 2 +- docs/doctoring/agent-mention-concurrency-isolation.md | 9 +++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9e6e55a4c..124cee3e7 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -41,7 +41,8 @@ flowchart TD SweepQ --> Ledger ``` -A scheduled sweep cannot replace a pending interactive route. +A scheduled sweep cannot replace a pending interactive route. CWE-362 +forbids sharing one concurrency group across those event classes. ## Control-plane data flow diff --git a/CHANGELOG.md b/CHANGELOG.md index 40fe0eeb6..b2ebd760a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. The decision record now cites NIST SP 800-53 AU-6/SC-5 so a 10-minute missing-receipt or 100-pending overflow is an availability alert, not a silent wait. +- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. The decision record now cites NIST SP 800-53 AU-6/SC-5 and CWE-362 so a shared concurrency group cannot replace a pending trusted comment. - Recorded the org control-plane architecture, including isolated mention queues, so agents reconstruct the trust boundary from the repo instead of private memory. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 2ab233fe1..4acbee6c3 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -13,6 +13,11 @@ GitHub Actions documents that a concurrency group permits one running member. Wi This is a queue-configuration defect, not evidence that the model, credential, allowlist, or review result is invalid. +CWE-362 classifies concurrent use of a shared resource without proper +synchronization (MITRE, 2026). One workflow-level concurrency group is +that shared resource: a later sweep can replace a pending trusted +comment before dispatch. + ## Fail-first evidence Direct-main replacement PR #825 starts from protected `main` `1131b1bbafb24e455fc8619cdf316813e8721861`. Exact RED head `a319d513a2f67b707737651a9eb7fdbfe4bc23c4` changed only `tests/test_agent_mention_workflow_contract.py` and required separate job-scoped queue contracts while the inherited workflow still had one shared workflow-level group. @@ -123,6 +128,10 @@ Rollback must preserve interactive requests. Restoring the shared workflow-level ## References (APA 7th) +MITRE. (2026). *CWE-362: Concurrent execution using shared resource with +improper synchronization ('race condition')*. +https://cwe.mitre.org/data/definitions/362.html + GitHub. (n.d.). *Concurrency*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency From 75023cdddaf139b5c68a2a01a3152deb6688866e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 01:21:36 +0900 Subject: [PATCH 10/11] fix(coverage): accept only bounded relative requirement includes Materialize a base Python lock only when every package line is an exact SHA-256 pin or a two-token relative -r/--requirement include of a candidate lock path. A lone --require-hashes directive, ./dotted paths, and -r other-hashes.txt no longer enter the trusted build context. --- AGENTS.md | 2 + CHANGELOG.md | 1 + .../agent-mention-concurrency-isolation.md | 2 + .../materialize_base_python_requirements.py | 85 +++++++++++++++---- ...st_materialize_base_python_requirements.py | 19 ++++- 5 files changed, 91 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 688b33035..c01b1f548 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,3 +2,5 @@ > **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. + +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/agent-mention-concurrency-isolation.md`](docs/doctoring/agent-mention-concurrency-isolation.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ebd760a..b30cf68c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. - Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. The decision record now cites NIST SP 800-53 AU-6/SC-5 and CWE-362 so a shared concurrency group cannot replace a pending trusted comment. - Recorded the org control-plane architecture, including isolated mention queues, so agents reconstruct the trust boundary from the repo instead of private memory. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. diff --git a/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index 4acbee6c3..e2effe536 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -26,6 +26,8 @@ This replacement does not reuse predecessor PR #815 or stacked development PR #8 ## Decision +Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. + Move concurrency from the workflow to the two jobs and give each event class a separate group. ```yaml diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 98cdad459..7a9c204b8 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -87,6 +87,58 @@ def _is_candidate_lock_name(name: str) -> bool: ) + +def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool: + """Return whether one safe tracked path can name a pip requirements lock. + + In addition to conventional ``requirements*.txt`` names, repositories often + keep concrete environment closures as direct children such as + ``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct + ``.txt`` children of a directory named ``requirements`` gain this path-based + eligibility; content must still pass the independent complete hash-pin + validation before it reaches the trusted image build context. + """ + return _is_candidate_lock_name(path.name) or ( + path.suffix == ".txt" and path.parent.name == "requirements" + ) + + +def _is_bounded_requirement_include(line: str) -> bool: + """Return whether one requirements include names a bounded relative file. + + Includes are accepted only as a two-token ``-r``/``--requirement`` form + whose target is itself a candidate lock path written as a normalized + relative POSIX path. Absolute paths, ``.`` or ``..`` components, double + slashes, URLs, option-like targets, shell/Windows path separators, + fragments, queries, extra inline options or hashes, and includes of + non-lock files are rejected before a base-owned file can enter the + trusted build context. + The downstream installer still proves that the candidate is an independently + complete hash closure; this predicate grants syntax eligibility only. + """ + fields = line.split() + if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: + return False + target = fields[1] + if ( + target.startswith(("-", "~")) + or "\\" in target + or ":" in target + or "?" in target + or "#" in target + ): + return False + include_path = pathlib.PurePosixPath(target) + return ( + bool(include_path.parts) + and target == include_path.as_posix() + and not include_path.is_absolute() + and "." not in include_path.parts + and ".." not in include_path.parts + and _is_candidate_lock_path(include_path) + ) + + def _requirement_lines(content: bytes) -> list[str]: """Return logical requirement lines, joining backslash line-continuations. @@ -107,26 +159,27 @@ def _requirement_lines(content: bytes) -> list[str]: def _is_hash_pinned(content: bytes) -> bool: - """Return whether content carries hash pins and is safe to preflight. - - Discovery is content-based rather than name-based so hash-pinned locks in any - location (a service subdirectory, ``requirements-dev.txt``, - ``requirements-test.txt``) can be considered for offline coverage, while an - unpinned or PR-mutable requirements file is still excluded from the networked - build context. Hash syntax cannot prove that a file includes every transitive - dependency, so the trusted image installer separately preflights every - candidate as an independent ``--require-hashes`` closure. An empty file - carries no installable dependency and is not materialized. + """Return whether content carries only trusted pins or bounded includes. + + Discovery is content-based rather than name-based so exact hash-pinned locks + in service subdirectories and role-specific requirements files can be + considered for offline coverage. Candidate syntax is deliberately stricter + than a substring search: each package line must be an exact ``==`` pin with + one or more complete SHA-256 hashes, or a bounded relative requirements + include. A global ``--require-hashes`` directive is not trust evidence by + itself. The downstream installer separately preflights every candidate as an + independent ``pip --require-hashes`` closure, so syntax eligibility never + substitutes for dependency-closure proof. """ lines = _requirement_lines(content) - if not lines: + requirement_lines = [line for line in lines if line != "--require-hashes"] + if not requirement_lines: return False - return any(line == "--require-hashes" for line in lines) or all( - "--hash=" in line or line.startswith(("-r ", "--requirement ")) - for line in lines + return all( + _is_fully_hash_pinned_requirement(line) + or _is_bounded_requirement_include(line) + for line in requirement_lines ) - - def _is_fully_hash_pinned_requirement(line: str) -> bool: """Return whether one uv-export line is an exact package pin with SHA-256 hashes.""" fields = re.split(r"\s+(?=--hash=)", line) diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 10f682b3e..317ab5f5c 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -157,9 +157,24 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None: def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None: """Only fully hash-pinned, non-empty lock content is materialized.""" assert not materializer._is_hash_pinned(b"# comment only\n\n") - assert materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") + assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n") - assert materializer._is_hash_pinned(b"-r other-hashes.txt\n") + assert materializer._is_hash_pinned(b"-r requirements-other.txt\n") + assert not materializer._is_hash_pinned(b"-r other-hashes.txt\n") + assert not materializer._is_hash_pinned(b"-r ./requirements-other.txt\n") + assert not materializer._is_hash_pinned(b"-r ../escape.txt\n") + assert materializer._is_bounded_requirement_include( + "--requirement requirements-other.txt" + ) + assert not materializer._is_bounded_requirement_include("-r .") + assert not materializer._is_bounded_requirement_include("-r -evil.txt") + assert not materializer._is_bounded_requirement_include("-r ~evil.txt") + assert not materializer._is_bounded_requirement_include("-r C:foo.txt") + assert not materializer._is_bounded_requirement_include("-r foo?bar.txt") + assert not materializer._is_bounded_requirement_include("-r foo#bar.txt") + assert not materializer._is_bounded_requirement_include(r"-r foo\\bar.txt") + assert not materializer._is_bounded_requirement_include("-r") + assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt") assert not materializer._is_hash_pinned(b"untrusted==1\n") # uv export / pip-compile multi-line continuation format (spec, then --hash= lines). assert materializer._is_hash_pinned( From c9fe4779d0030534a366d6a0fe4ae4252bb794fe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:11:11 +0900 Subject: [PATCH 11/11] fix(automation): restore isolated mention-router boundary Remove unrelated coverage-materializer and architecture changes that entered this branch after the reviewed concurrency repair. Restore protected-main blobs for unrelated files and retain only the five declared mention-router workflow, contract, doctoring, and changelog paths. --- AGENTS.md | 2 - ARCHITECTURE.md | 94 ------------------- CHANGELOG.md | 4 +- CLAUDE.md | 4 +- .../agent-mention-concurrency-isolation.md | 27 ------ .../materialize_base_python_requirements.py | 85 ++++------------- ...st_materialize_base_python_requirements.py | 29 +----- 7 files changed, 20 insertions(+), 225 deletions(-) delete mode 100644 ARCHITECTURE.md diff --git a/AGENTS.md b/AGENTS.md index c01b1f548..688b33035 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,5 +2,3 @@ > **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. - -Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include (no `.`/`..`); a lone `--require-hashes` directive is not trust evidence. See [`docs/doctoring/agent-mention-concurrency-isolation.md`](docs/doctoring/agent-mention-concurrency-isolation.md). diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 124cee3e7..000000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,94 +0,0 @@ -# Architecture — ContextualWisdomLab `.github` - -This repository is the organization control plane. It is not naruon and it -does not own product data. Sibling products remain standalone modules; this -repo publishes org profile assets, reusable required workflows, and the -review/merge schedulers those products consume. - -## System context - -```mermaid -flowchart LR - Buyer["Commercial buyer / reviewer"] - Agents["Agents on AGENTS.md"] - Project["GitHub Project #1"] - Hub["This repo: org .github"] - Products["Owned products
naruon · orchestrator · engines"] - Runner["Required workflows in each repo context"] - - Buyer --> Hub - Agents --> Project - Agents --> Hub - Project --> Hub - Hub --> Runner - Runner --> Products - Products -->|"standalone or as module"| Buyer -``` - -## Isolated mention queues - -```mermaid -flowchart TD - Comment["Trusted issue_comment"] - Sweep["Five-minute organization sweep"] - Local["review-agent-mention-router-local
queue: max"] - SweepQ["review-agent-mention-router-sweep
cancel-in-progress: false"] - Ledger["Exact-name 30-day artifact ledger"] - - Comment --> Local - Sweep --> SweepQ - Local --> Ledger - SweepQ --> Ledger -``` - -A scheduled sweep cannot replace a pending interactive route. CWE-362 -forbids sharing one concurrency group across those event classes. - -## Control-plane data flow - -```mermaid -sequenceDiagram - participant PR as Pull request - participant RW as Required workflows - participant OC as OpenCode reviewer - participant SV as sandboxed_verify / web E2E - participant MS as Merge scheduler - - PR->>RW: pull_request_target on trusted base - RW->>OC: bounded evidence + NVIDIA NIM / OpenCode - OC->>SV: PoC command in isolated copy - SV-->>OC: redacted stdout/stderr + command metadata - OC-->>PR: APPROVE or request changes - MS->>PR: merge only on current-head approval + green checks -``` - -## Trust boundaries - -- Required review workflows execute **base-branch** scripts. A PR that edits - those workflows cannot widen its own `pull_request_target` token. -- Reviewer agents stay `edit: deny`. They judge; they do not implement. -- Sandbox helpers copy the workspace, drop secret environment values unless - explicitly allowlisted by **name**, and run subprocesses with `shell=False`. -- Logs and review receipts redact credential shapes (tokens, bearer values, - known provider prefixes). They do not mask operational PII that the - control plane must process. -- LLM and scheduled agents bind `NVIDIA_NIM_API_KEY` (env may be - `NVIDIA_API_KEY`). They never use `COPILOT_GITHUB_TOKEN`. Existing - review-agent key schemes stay unchanged. - -## Quality gates - -`scripts/ci/` ships with 100% statement/branch coverage and 100% docstrings. -CI installs Python tools only with `pip install --require-hashes`. Contract -tests pin workflow structure and governance prose so drift fails closed. - -## Related durable documents - -- [`docs/CWL-MASTER-CONTEXT.md`](docs/CWL-MASTER-CONTEXT.md) — mission and - ecosystem. -- [`docs/agent-github-project-protocol.md`](docs/agent-github-project-protocol.md) - — Project #1 operation. -- [`PR_GOVERNANCE_AUDIT.md`](PR_GOVERNANCE_AUDIT.md) — live review/merge - contract. -- [`docs/doctoring/agent-mention-concurrency-isolation.md`](docs/doctoring/agent-mention-concurrency-isolation.md) - — current increment's queue-isolation decision and APA 7th citations. diff --git a/CHANGELOG.md b/CHANGELOG.md index b30cf68c3..7901d7655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,7 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context. -- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. The decision record now cites NIST SP 800-53 AU-6/SC-5 and CWE-362 so a shared concurrency group cannot replace a pending trusted comment. -- Recorded the org control-plane architecture, including isolated mention queues, so agents reconstruct the trust boundary from the repo instead of private memory. +- Isolated trusted interactive review-agent mentions from scheduled organization sweeps with separate job-level concurrency groups; interactive requests use the bounded 100-pending `queue: max` contract while sweeps retain non-cancelling single-pending coalescing, leaving the durable exact-name artifact ledger as forwarding authority. - Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics. - Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped. diff --git a/CLAUDE.md b/CLAUDE.md index 0f5dc337e..1c7bdb2f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,9 +64,7 @@ Details: `README.md` and `PR_GOVERNANCE_AUDIT.md`. - `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. Doctoring records live under - `docs/doctoring/`. [`ARCHITECTURE.md`](ARCHITECTURE.md) is the control-plane - diagram for review, isolated mention queues, and merge trust boundaries. + `scorecard-governance.md`, 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/docs/doctoring/agent-mention-concurrency-isolation.md b/docs/doctoring/agent-mention-concurrency-isolation.md index e2effe536..5dbf0a36f 100644 --- a/docs/doctoring/agent-mention-concurrency-isolation.md +++ b/docs/doctoring/agent-mention-concurrency-isolation.md @@ -13,11 +13,6 @@ GitHub Actions documents that a concurrency group permits one running member. Wi This is a queue-configuration defect, not evidence that the model, credential, allowlist, or review result is invalid. -CWE-362 classifies concurrent use of a shared resource without proper -synchronization (MITRE, 2026). One workflow-level concurrency group is -that shared resource: a later sweep can replace a pending trusted -comment before dispatch. - ## Fail-first evidence Direct-main replacement PR #825 starts from protected `main` `1131b1bbafb24e455fc8619cdf316813e8721861`. Exact RED head `a319d513a2f67b707737651a9eb7fdbfe4bc23c4` changed only `tests/test_agent_mention_workflow_contract.py` and required separate job-scoped queue contracts while the inherited workflow still had one shared workflow-level group. @@ -26,8 +21,6 @@ This replacement does not reuse predecessor PR #815 or stacked development PR #8 ## Decision -Materialize accepts only exact SHA-256 pins or a bounded relative `-r` include; a lone `--require-hashes` line is not lock evidence. - Move concurrency from the workflow to the two jobs and give each event class a separate group. ```yaml @@ -114,14 +107,6 @@ After protected merge: 6. alert immediately on a queue-limit rejection, unexpected cancellation of an interactive route, or when the interactive queue approaches the documented 100-pending limit; 7. keep metrics finite-cardinality and exclude comment text, source diffs, tokens, and model responses. -NIST SP 800-53 Rev. 5 AU-6 requires review of audit records and SC-5 requires -protection against resource exhaustion (National Institute of Standards and -Technology, 2020). The 10-minute receipt alert and the 100-pending overflow -signal are those controls: they distinguish a bounded wait from a dropped -trusted mention. GitHub documents that `queue: max` holds at most 100 pending -members and rejects overflow rather than replacing the oldest pending run -(GitHub, n.d.-a). - A downstream reviewer may still fail closed because credentials, providers, checks, or exact-head evidence are unavailable. That remains distinct from a routing queue failure. ## Rollback @@ -130,10 +115,6 @@ Rollback must preserve interactive requests. Restoring the shared workflow-level ## References (APA 7th) -MITRE. (2026). *CWE-362: Concurrent execution using shared resource with -improper synchronization ('race condition')*. -https://cwe.mitre.org/data/definitions/362.html - GitHub. (n.d.). *Concurrency*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency @@ -141,11 +122,3 @@ GitHub. (n.d.). *Control the concurrency of workflows and jobs*. GitHub Docs. Re GitHub. (n.d.). *REST API endpoints for GitHub Actions artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/rest/actions/artifacts GitHub. (n.d.). *Store and share data with workflow artifacts*. GitHub Docs. Retrieved August 7, 2026, from https://docs.github.com/en/actions/tutorials/store-and-share-data - -GitHub. (n.d.-a). *Usage limits, billing, and administration*. GitHub Docs. -Retrieved August 13, 2026, from -https://docs.github.com/en/actions/reference/limits - -National Institute of Standards and Technology. (2020). *Security and -privacy controls for information systems and organizations* (NIST SP -800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5 diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 7a9c204b8..98cdad459 100755 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -87,58 +87,6 @@ def _is_candidate_lock_name(name: str) -> bool: ) - -def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool: - """Return whether one safe tracked path can name a pip requirements lock. - - In addition to conventional ``requirements*.txt`` names, repositories often - keep concrete environment closures as direct children such as - ``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct - ``.txt`` children of a directory named ``requirements`` gain this path-based - eligibility; content must still pass the independent complete hash-pin - validation before it reaches the trusted image build context. - """ - return _is_candidate_lock_name(path.name) or ( - path.suffix == ".txt" and path.parent.name == "requirements" - ) - - -def _is_bounded_requirement_include(line: str) -> bool: - """Return whether one requirements include names a bounded relative file. - - Includes are accepted only as a two-token ``-r``/``--requirement`` form - whose target is itself a candidate lock path written as a normalized - relative POSIX path. Absolute paths, ``.`` or ``..`` components, double - slashes, URLs, option-like targets, shell/Windows path separators, - fragments, queries, extra inline options or hashes, and includes of - non-lock files are rejected before a base-owned file can enter the - trusted build context. - The downstream installer still proves that the candidate is an independently - complete hash closure; this predicate grants syntax eligibility only. - """ - fields = line.split() - if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}: - return False - target = fields[1] - if ( - target.startswith(("-", "~")) - or "\\" in target - or ":" in target - or "?" in target - or "#" in target - ): - return False - include_path = pathlib.PurePosixPath(target) - return ( - bool(include_path.parts) - and target == include_path.as_posix() - and not include_path.is_absolute() - and "." not in include_path.parts - and ".." not in include_path.parts - and _is_candidate_lock_path(include_path) - ) - - def _requirement_lines(content: bytes) -> list[str]: """Return logical requirement lines, joining backslash line-continuations. @@ -159,27 +107,26 @@ def _requirement_lines(content: bytes) -> list[str]: def _is_hash_pinned(content: bytes) -> bool: - """Return whether content carries only trusted pins or bounded includes. - - Discovery is content-based rather than name-based so exact hash-pinned locks - in service subdirectories and role-specific requirements files can be - considered for offline coverage. Candidate syntax is deliberately stricter - than a substring search: each package line must be an exact ``==`` pin with - one or more complete SHA-256 hashes, or a bounded relative requirements - include. A global ``--require-hashes`` directive is not trust evidence by - itself. The downstream installer separately preflights every candidate as an - independent ``pip --require-hashes`` closure, so syntax eligibility never - substitutes for dependency-closure proof. + """Return whether content carries hash pins and is safe to preflight. + + Discovery is content-based rather than name-based so hash-pinned locks in any + location (a service subdirectory, ``requirements-dev.txt``, + ``requirements-test.txt``) can be considered for offline coverage, while an + unpinned or PR-mutable requirements file is still excluded from the networked + build context. Hash syntax cannot prove that a file includes every transitive + dependency, so the trusted image installer separately preflights every + candidate as an independent ``--require-hashes`` closure. An empty file + carries no installable dependency and is not materialized. """ lines = _requirement_lines(content) - requirement_lines = [line for line in lines if line != "--require-hashes"] - if not requirement_lines: + if not lines: return False - return all( - _is_fully_hash_pinned_requirement(line) - or _is_bounded_requirement_include(line) - for line in requirement_lines + return any(line == "--require-hashes" for line in lines) or all( + "--hash=" in line or line.startswith(("-r ", "--requirement ")) + for line in lines ) + + def _is_fully_hash_pinned_requirement(line: str) -> bool: """Return whether one uv-export line is an exact package pin with SHA-256 hashes.""" fields = re.split(r"\s+(?=--hash=)", line) diff --git a/tests/test_materialize_base_python_requirements.py b/tests/test_materialize_base_python_requirements.py index 317ab5f5c..8a383f0c2 100644 --- a/tests/test_materialize_base_python_requirements.py +++ b/tests/test_materialize_base_python_requirements.py @@ -30,13 +30,6 @@ def _created_tool_directory(path: Path) -> str: return str(path) -def _force_linux_x86_64_installer(monkeypatch: pytest.MonkeyPatch) -> None: - """Exercise the installer path that GitHub-hosted linux x86_64 runners use.""" - monkeypatch.setattr(materializer.sys, "platform", "linux") - monkeypatch.setattr(materializer.platform, "machine", lambda: "x86_64") - materializer._install_trusted_uv.cache_clear() - - def test_materializes_only_regular_hash_locks_from_exact_base(tmp_path: Path) -> None: """A PR-modified lock cannot enter the networked coverage image build context.""" repo = tmp_path / "repo" @@ -157,24 +150,9 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None: def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None: """Only fully hash-pinned, non-empty lock content is materialized.""" assert not materializer._is_hash_pinned(b"# comment only\n\n") - assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") + assert materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n") assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n") - assert materializer._is_hash_pinned(b"-r requirements-other.txt\n") - assert not materializer._is_hash_pinned(b"-r other-hashes.txt\n") - assert not materializer._is_hash_pinned(b"-r ./requirements-other.txt\n") - assert not materializer._is_hash_pinned(b"-r ../escape.txt\n") - assert materializer._is_bounded_requirement_include( - "--requirement requirements-other.txt" - ) - assert not materializer._is_bounded_requirement_include("-r .") - assert not materializer._is_bounded_requirement_include("-r -evil.txt") - assert not materializer._is_bounded_requirement_include("-r ~evil.txt") - assert not materializer._is_bounded_requirement_include("-r C:foo.txt") - assert not materializer._is_bounded_requirement_include("-r foo?bar.txt") - assert not materializer._is_bounded_requirement_include("-r foo#bar.txt") - assert not materializer._is_bounded_requirement_include(r"-r foo\\bar.txt") - assert not materializer._is_bounded_requirement_include("-r") - assert not materializer._is_bounded_requirement_include("-r /abs/requirements.txt") + assert materializer._is_hash_pinned(b"-r other-hashes.txt\n") assert not materializer._is_hash_pinned(b"untrusted==1\n") # uv export / pip-compile multi-line continuation format (spec, then --hash= lines). assert materializer._is_hash_pinned( @@ -666,7 +644,6 @@ def test_install_trusted_uv_verifies_version_and_caches_path( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """The installer writes one executable, verifies its version, and caches it.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -713,7 +690,6 @@ def test_install_trusted_uv_rejects_version_process_failures( failure: OSError | subprocess.TimeoutExpired, ) -> None: """A missing or hung downloaded executable is removed and rejected.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / "uv" monkeypatch.setattr( materializer.tempfile, @@ -745,7 +721,6 @@ def test_install_trusted_uv_rejects_wrong_version_or_exit_status( completed: subprocess.CompletedProcess[bytes], ) -> None: """Unexpected version output or a nonzero status cannot satisfy the pin.""" - _force_linux_x86_64_installer(monkeypatch) tool_dir = tmp_path / f"uv-{completed.returncode}-{len(completed.stdout)}" monkeypatch.setattr( materializer.tempfile,