Merge pull request #15 from Sea-Haven-Industries/feature/agent-team-medium-cleanup
cleanup(agent-team): clear 3 SAST mediums + refresh README to built state
This commit is contained in:
commit
beb5ad0807
4 changed files with 87 additions and 47 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Reference in a new issue