From 035c6e57e5b86c82bf4f56e10526d888ad2d57f9 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Thu, 18 Jun 2026 14:16:31 -0400 Subject: [PATCH 1/2] fix(agent-team): clear 3 non-blocking SAST mediums on main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clarifier_llm: sha1 -> sha256 for the non-security cache-discriminator (CWE-327 false positive). github_adapter + github_intake: inline nosemgrep on the urlopen lines (dynamic-urllib-use-detected) — the URL is built from a fixed https GitHub API base, dynamic part is the path only, no SSRF/file:// surface (extends the existing noqa:S310 trusted-host judgment to semgrep). Scanner now reports 0 mediums on the agent-team scope. --- agent-team/agent_team/nodes/clarifier_llm.py | 4 +++- agent-team/agent_team/transport/github_adapter.py | 4 +++- agent-team/agent_team/transport/github_intake.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/agent-team/agent_team/nodes/clarifier_llm.py b/agent-team/agent_team/nodes/clarifier_llm.py index cd6604d..32459cd 100644 --- a/agent-team/agent_team/nodes/clarifier_llm.py +++ b/agent-team/agent_team/nodes/clarifier_llm.py @@ -94,7 +94,9 @@ def _turn_cache_key( digest_src = json.dumps(list(qa_history), sort_keys=True, default=repr) except (TypeError, ValueError): digest_src = repr(list(qa_history)) - content_hash = hashlib.sha1(digest_src.encode("utf-8")).hexdigest() + # sha256 (not sha1): this is a non-security cache-discriminator, but using a + # modern digest keeps the SAST scanners quiet (CWE-327) with no downside. + content_hash = hashlib.sha256(digest_src.encode("utf-8")).hexdigest() return (thread_id, len(qa_history), content_hash) diff --git a/agent-team/agent_team/transport/github_adapter.py b/agent-team/agent_team/transport/github_adapter.py index 9254a49..669d698 100644 --- a/agent-team/agent_team/transport/github_adapter.py +++ b/agent-team/agent_team/transport/github_adapter.py @@ -173,7 +173,9 @@ def _default_http_post( for key, value in headers.items(): request.add_header(key, value) try: - with _urlrequest.urlopen(request) as response: # noqa: S310 (trusted api host) + # url is built from a fixed https GitHub API base; the dynamic part is the + # path, never the scheme, so there is no SSRF/file:// surface. + with _urlrequest.urlopen(request) as response: # noqa: S310 (trusted api host); nosemgrep status = response.getcode() raw = response.read().decode("utf-8") except _urlerror.HTTPError as exc: # pragma: no cover - network path diff --git a/agent-team/agent_team/transport/github_intake.py b/agent-team/agent_team/transport/github_intake.py index a3db56f..14e0598 100644 --- a/agent-team/agent_team/transport/github_intake.py +++ b/agent-team/agent_team/transport/github_intake.py @@ -283,7 +283,9 @@ def build_default_issue_client( request.add_header("Authorization", f"Bearer {token}") request.add_header("Accept", "application/vnd.github+json") request.add_header("X-GitHub-Api-Version", "2022-11-28") - with _urlrequest.urlopen(request) as response: # noqa: S310 (trusted api host) + # url is built from a fixed https GitHub API base; the dynamic part is + # the path, never the scheme, so there is no SSRF/file:// surface. + with _urlrequest.urlopen(request) as response: # noqa: S310 (trusted api host); nosemgrep raw = response.read().decode("utf-8") data = json.loads(raw) if raw else [] # The issues endpoint can include pull requests (they share the -- 2.50.1 From fdaafac36fe8e4a89b835c9ba058e2094001b369 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Thu, 18 Jun 2026 14:19:00 -0400 Subject: [PATCH 2/2] docs(agent-team): refresh README to current built state (P1-P4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README still described 'FOUNDATION modules only — no coordinator, no transports, no CI workflow'. Updated to reflect the built+merged pipeline (P1 human gate, P2 planner+review, live coordinator+transports+intake, P3-inert build/verify subgraph, P4) with the current layout, the pipeline diagram, key design points, and the deploy-gated/not-built items (P3 live CI, provisioning). --- agent-team/README.md | 122 +++++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/agent-team/README.md b/agent-team/README.md index 12bb29d..bf43434 100644 --- a/agent-team/README.md +++ b/agent-team/README.md @@ -1,65 +1,99 @@ -# agent-team — R720 Plane-2 FOUNDATION +# agent-team — R720 Plane-2 SDLC pipeline -Pre-deployment scaffolding for the R720 agent-team SDLC pipeline (design: -`../docs/r720-agent-team-design.md`). This commit ships the **Plane-2 -FOUNDATION** layer only — the durable, transport-agnostic **contracts** the leaf -builders import verbatim. Nothing here is provisioned, scheduled, or wired to -live infrastructure. +The durable, human-gated agentic SDLC pipeline for the R720 (`sh-secrev` VM), +design: `../docs/r720-agent-team-design.md`. A task flows INTAKE → CLARIFY (the +human gate) → PLAN → REVIEW, and (opt-in, deploy-gated) → BUILD → VERIFY → draft +PR. Every stage is durable and resumable (LangGraph + a SQLite checkpointer); +the human gate suspends on `interrupt()` and resumes on a real answer. -> Status: FOUNDATION modules only. No coordinator, no transports' concrete -> adapters, no CI workflow, no provisioning. Those are later phases (§7). +``` +INTAKE → CLARIFY (Claude, human gate) → PLAN (Claude) → REVIEW (GPT-4.1) + ▲ │ + └── loop-back ───┤ + approve/escalate → END + (P3, opt-in + INERT until the CI gate clears): + approve → BUILD (DeepSeek) → VERIFY (ci_gate) → draft PR +``` + +> **Status (2026-06-18).** P1 (human gate) + P2 (planner + adversarial review +> loop) + the live runtime (coordinator, Slack/GitHub/Claude-Code transports, +> intake) + P3-inert (build/verify subgraph, opt-in) + P4 (more transports + +> GitHub-issue intake) are **built, reviewed, and merged to main** (~795 tests). +> **Nothing is provisioned**: not rsync'd to the box, no live tokens, no +> systemd, no live CI. Production default runs **P2** (no builders). +> **Deploy-gated / not yet built:** the P3 *live* CI apply/verify + OIDC role +> (held behind `/sh-security-review` + the mandatory GPT-4.1 cross-review), and +> all provisioning. See the project memory `project_r720_agent_team` and §7 of +> the design for the phased rollout. ## Layout ``` agent-team/ - agent_team/ # importable package (snake_case) - state_store.py # §6.7 atomic write + integrity-checked read - billing.py # §3.1 claude_invoke billing-mode seam - task_model.py # §3.3 TaskRecord / Phase / PipelineState - db/ - schema.py # §3.3.1/§6.7 SQLite DDL + connect/init/migrate - schema.sql # raw DDL, mirrors schema.py verbatim - transport/ - base.py # §3.3.1 Transport ABC + QuestionSet/NormalizedAnswer - tests/ # pytest unit tests, one module per source module + run-team.py # operator CLI: init-db, list/show/answer/expire/ + # force-resume (ledger), start (intake), serve (daemon), + # intake-github + agent_team/ # importable package (snake_case) + coordinator.py # the live runtime keystone: invoker→graph→ResumeWorker; + # start_task / submit_answer / drain / tick / recover / serve + graph.py # LangGraph wiring: P1 (intake→clarify→plan) + opt-in P2 + # review loop + opt-in P3 build/verify subgraph + invoker.py # §3.1 real Claude path (subscription-OAuth / API / Bedrock) + billing.py # §3.1 claude_invoke billing-mode seam + ci_gate.py # §3.3.2 pure-code authenticated-Checks PASS/FAIL gate + task_model.py / state_store.py + db/{schema.py,schema.sql} # SQLite ledger DDL + BEGIN IMMEDIATE compare-and-set + ledger.py / responder.py / resume_worker.py / deadline_timer.py / recovery.py + operator_cli.py + nodes/ # pipeline stages + their model bindings + clarifier.py + clarifier_llm.py # human gate (Claude) + planner.py # plan (Claude) + review_loop.py + review_loop_llm.py # adversarial review (GPT-4.1 via orchestrator) + builders.py + builders_llm.py # candidate diff (DeepSeek) — INERT, proposes only + verifier.py + verifier_llm.py # ci_gate sole PASS authority; LLM = fix-proposer + build_verify_subgraph.py # P3 BUILD→VERIFY topology (opt-in) + transport/ # one adapter contract + a live impl per channel + base.py # Transport ABC + QuestionSet / NormalizedAnswer + slack_adapter.py + slack_live.py + slack_listener.py # Block Kit + Socket Mode + github_adapter.py + github_live.py + github_intake.py # issue-comment + issue intake + claude_code_adapter.py + claude_code_live.py # file-drop responder + ci/ # §3.3.2 split-job CI apply/verify workflow (DEPLOY-GATED) + systemd/ # agent-team-coordinator.service (not installed) + DEPLOY-R720.md # provisioning runbook (snapshot-first, rsync, tokens, demo) + tests/ # pytest, one module per source module + sim harness ``` The top directory is kebab-case (`agent-team/`); the importable package is snake_case (`agent_team/`), per the engineering handbook. -## Modules (contracts) +## Key design points -| Module | Design ref | What it provides | -|---|---|---| -| `state_store` | §6.7 | `atomic_write(path, data)` (write-temp → fsync → rename), `read_checked(path, *, schema_version)` (schema-version + content-hash integrity check, raises `IntegrityError`), `compute_content_hash(data)`. Pure stdlib; no other `agent_team` deps. | -| `db.schema` | §3.3.1, §6.7 | `SCHEMA_VERSION`, `PENDING_QUESTIONS_DDL`, `BUDGET_LEDGER_DDL`, `connect()` (WAL + foreign_keys + busy_timeout), `init_db()`, `migrate()`, and the `BEGIN IMMEDIATE` compare-and-set helpers (`answer_question`/`expire_question`/`supersede_question`). SQL DDL lives **only** here. | -| `billing` | §3.1 | `BillingMode{SUBSCRIPTION,API,BEDROCK}`, `claude_invoke(prompt, *, mode=None, **kw) -> ClaudeResult`, `resolve_mode(config)`. Single seam; subscription mode pops any stray `ANTHROPIC_API_KEY` so OAuth can't be overridden. | -| `transport.base` | §3.3.1 | `Transport` ABC (`post_question` → `channel_ref`; `parse_answer` → `(question_id, answer, via)`), `QuestionSet`, `NormalizedAnswer`. Transport-independent; Slack/GitHub/Claude-Code adapters subclass in the leaves. | -| `task_model` | §3.3 | `TaskRecord`, `TaskStatus`, `Phase{INTAKE…DONE}`, `new_thread_id()`, `PipelineState` TypedDict (LangGraph state schema), JSON serialization helpers. Pure model, no I/O. | - -### Durable human-in-the-loop (§3.3.1) - -The `pending_questions` ledger is the single durable source of truth for the -question lifecycle. Every race (duplicate answers, transport redelivery, -answer-vs-timeout) resolves via one atomic compare-and-set against the `status` -column, run inside a `BEGIN IMMEDIATE` transaction so concurrent responders are -serialized — first-answer-wins (`rowcount == 1`), late/duplicate ignored -(`rowcount == 0`). The LangGraph `SqliteSaver` checkpointer creates its own -tables against the **same** DB file. +- **Durable human gate (§3.3.1).** The `pending_questions` ledger is the single + source of truth for the question lifecycle. Every race (duplicate answers, + transport redelivery, answer-vs-timeout) resolves via one atomic + compare-and-set against `status`, inside a `BEGIN IMMEDIATE` transaction — + first-answer-wins (`rowcount == 1`), late/duplicate ignored. The LangGraph + `SqliteSaver` checkpointer shares the same DB file. +- **Fail-safe model seams.** Every node treats model output as untrusted and + fails SAFE: garbage never clears the 98% clarifier gate, never auto-approves a + plan, never fabricates a build success, and the verifier's `ci_gate` is the + **sole** PASS authority (the LLM is structurally a fix-proposer only). +- **Inbound auth (§3.3.1).** The Slack Socket Mode listener authorizes the + **sender** against an owner allowlist (`AGENT_TEAM_SLACK_OWNER_IDS`, + fail-closed) on top of the open-status CAS anti-replay. +- **Billing seam (§3.1).** Claude runs under subscription OAuth on the box; + GPT-4.1 (review) and DeepSeek (builders) route through the local orchestrator + `run.py`. Switching Claude billing is a config flip. ## Running the tests ``` cd agent-team -python3 -m pytest tests/ -q +python3 -m pytest -q # conftest puts the package on sys.path; no install needed ``` -`tests/conftest.py` puts the package on `sys.path`, so no install is required. +## Deploy -## Not in this commit (later phases) - -Coordinator/brain, concrete Slack/GitHub/Claude-Code transport adapters, the -CI apply/verify workflow (§3.3.2), step-ca / Roles Anywhere, scheduling, and -the operator CLI (`run-team.py`). See `../docs/r720-agent-team-design.md` §7 -for the phased rollout. Secrets are never committed. +Deploy-gated. See `DEPLOY-R720.md` for the provisioning runbook (VM snapshot +first, rsync, venv deps, `~/secrev.env` tokens, `init-db`, systemd, and the live +P1 exit-criteria demo). Secrets are never committed. -- 2.50.1