M2 delivers the read-only MCP capability broker gateway and four Day-1 infrastructure adapters (Proxmox, SSH/Linux, GitHub, Gitea). 13 REQs (015-027) all pass. 656 tests green. M1 non-regression verified. MCP spec 2025-06-18 conformance verified (PROTOCOL.md + 7 tests). Defense-in-depth SSH (broker layer 1 + Relay Agent layer 2 + no-shell exec). Two-track LLM smoke (Track A mock-path P0 gate passes). CI: Gitea Actions (.gitea/workflows/ci.yml) with Postgres 16 + RLS verification. Phases shipped: P0 pre-execution v0.1.0 P1 Wave F — MCP gateway v0.1.1 P2 Wave G — Proxmox v0.1.2 P3 Wave H — SSH/Linux v0.1.3 P4 Wave I — Git adapters v0.1.4 P5 Wave J — SSE+smoke+UI v0.1.5 P6 Final — review+ship v0.1.6 ← milestone release ---ci--- phase: 6 milestone: v0.2 status: complete phase_role: final milestone_complete: true requirements: covered: [REQ-015, REQ-016, REQ-017, REQ-018, REQ-019, REQ-020, REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026, REQ-027] partial: [] ---/ci---
12 KiB
Clarify — Architectural Decisions (M2)
Spec: CoreCI Chat v0.1 M2 Engineering Specification v1.0 (.ciagent/steer-m2-spec.md, locked 2026-08-25, Sarah Chen).
Autonomy: full (decision threshold 0.6). M2 spec §7 resolved all 9 product-level open questions (Q1-Q9); D-001..D-005 are carried from M1 (still in force); D-006 and D-007 are new M2 architectural decisions recorded here.
Each decision is recorded with: question, options considered, decision, rationale, confidence, status.
D-001 — BYOM endpoint protocol contract (carried from M1)
Question: Which wire protocol should the BYOM routing shim speak, given customers may bring vLLM, TGI, Ollama, OpenAI, Azure OpenAI, Together, or self-hosted endpoints?
Decision: OpenAI-compatible /v1/chat/completions for M1, with a pluggable LlmProvider interface so an Anthropic-native impl can be added in M3 without re-architecting the routing shim.
Rationale: The overwhelming majority of customer-hosted inference endpoints speak OpenAI-compatible. Native Anthropic support is an M3 concern; the interface keeps that door open without paying for it now.
Confidence: 0.85
Status: approved (PO kickoff, M1). Carried forward to M2/M3.
Affects: REQ-006, REQ-007, REQ-008, REQ-009 (M1); M2 LLM smoke uses the same contract (packages/llm-mock speaks OpenAI-compatible).
D-002 — Relay Agent implementation language (carried from M1)
Question: Should the Relay Agent be written in Go, Rust, or TypeScript (Node)?
Decision: Go.
Rationale: Single static binary, zero runtime deps, trivial cross-compile to linux/amd64+arm64, trivial systemd unit. The SSH whitelist hook (M1) and the SSH adapter (M2) both run inside the agent; Go's os/exec + seccomp/pledge-style hardening is well-trodden.
Confidence: 0.90 Status: approved (PO kickoff, M1). Carried forward to M2. Affects: REQ-010, REQ-011, REQ-012, REQ-013, REQ-026; ARCHITECTURE.md § apps/relay-agent. M2 reactivates a go-engineer persona for the SSH adapter integration.
D-003 — Secret manager backend (carried from M1)
Question: Which backend for SecretProvider given spec §5 names "AWS Secrets Manager or equivalent" and us-east-1 is the default region, while CI/local dev must run without AWS access?
Decision: AWS Secrets Manager (prod, KMS-backed, us-east-1) + LocalEncryptedProvider (dev/test, AES-256-GCM) behind a SecretProvider interface.
Rationale: Spec §5 mandates AWS Secrets Manager (or equivalent) for prod. The interface lets CI and local dev run without AWS credentials. No tenant secret is ever in plaintext on disk, in a DB column, in a config file, or in logs — satisfying REQ-040 and the "no env vars for tenant secrets" rule.
Confidence: 0.85
Status: approved (PO kickoff, M1). Carried forward to M2.
Affects: REQ-040 (M1); M2 adapter credentials (Proxmox token, GitHub/Gitea PAT, SSH registration token) all resolve via SecretProvider.get (INV-3).
D-004 — Audit log storage backend (carried from M1)
Question: What is the M1 audit log store, given REQ-038 requires a "write-once store" and the immutability pattern set in M1 propagates to every M2/M3 event?
Decision: Postgres append-only table audit_log with a hash-chain (curr_hash = sha256(prev_hash || canonical_payload)) and REVOKE UPDATE, DELETE from the app role. S3 Object Lock WORM is deferred to M3 hardening.
Rationale: M1 volume is low. A Postgres append-only table with a hash-chain gives cryptographic tamper-evidence, synchronous writes so "write failure halts" (Edge 7) is a single transaction, and REVOKE UPDATE/DELETE makes the app role physically unable to mutate rows. The hash-chain pattern propagates to M2/M3. M2 reuses M1's audit_log for new event types (adapter.configured, adapter.test_connection.{succeeded,failed}, adapter.capability_invoked, adapter.write_rejected).
Confidence: 0.80 Status: approved (PO kickoff, M1). Carried forward to M2. Affects: REQ-038 (M1); M2 adds new event types to the same table (additive, no schema change to existing rows).
D-005 — Web application framework (carried from M1)
Question: Which framework for the browser surface, given M1 ships the admin dashboard and M3 ships the chat UI in the same product?
Decision: Next.js (App Router) + TypeScript, single SPA. M1 ships the admin dashboard as server components; M3 adds the chat UI in the same app.**
Rationale: One app = one deploy, one auth flow, one RBAC map, one RLS-aware API gateway. The dashboard (M1), chat (M3), and M2's Settings → Adapters + Test-Call UI all share packages/auth, packages/db, packages/audit cleanly. App Router server components read via the API gateway (never bypassing RLS); M2's SSE streaming uses Route Handlers.
Confidence: 0.85
Status: approved (PO kickoff, M1). Carried forward to M2.
Affects: ARCHITECTURE.md § apps/control-plane, apps/dashboard. M2 adds /api/mcp/* routes and the Settings → Adapters + Test-Call UI in the same Next.js app.
D-006 — GitHub fine-grained PAT scope minimum (NEW in M2)
Question: The M2 spec §7 Q5 recommended contents:read + metadata:read as the GitHub PAT minimum. But M2's GitHub tools (github.list_repos, github.get_recent_ci_runs, github.get_workflow_run) do not read repo contents — they list repos (metadata) and read Actions runs (Actions scope). Should contents:read be required?
Options considered:
contents:read+metadata:read(spec recommendation) — broadens token scope to repo contents, which no M2 tool usesmetadata:read+actions:read(proposed) — scopes exactly match M2 tool requirements; no over-privilegemetadata:readonly (minimum viable) — would fail at runtime forget_recent_ci_runsandget_workflow_run(need Actions scope)
Decision: Fine-grained PAT with metadata:read + actions:read minimum (no contents:read).
Rationale: M2's three GitHub tools require only Metadata (read) — required for all fine-grained PATs — and Actions (read). contents:read grants repo file contents access, which no M2 tool uses; including it broadens the attack surface for no benefit. Principle of least privilege: scope the token to exactly what the tools need. This is a deviation from the spec's Q5 recommendation, recorded here and applied to REQ-022/REQ-027 acceptance criteria during SPECIFY.
Confidence: 0.80
Status: approved (PO, M2 spec lock 2026-08-25). Deviation from spec §7 Q5 recommendation.
Affects: REQ-022, REQ-027 acceptance criteria (updated in REQUIREMENTS.md). The broker validates metadata:read + actions:read at adapter config submit time; per-tool scope validation at invocation time. Classic PATs (coarse repo scope) are rejected — fine-grained PATs only.
D-007 — MCP transport architecture for M2 adapters (NEW in M2)
Question: The M2 spec §7 Q1 established that the broker implements MCP 2025-06-18 with three transport layers. For the broker ↔ adapter layer specifically, should adapters be MCP servers communicating over stdio (subprocess per adapter), or in-process modules using a custom MCP transport?
Options considered:
- stdio subprocess per adapter (strict MCP server model) — each adapter is a spawned process communicating over stdin/stdout JSON-RPC; clean isolation but heavy overhead for same-process TS modules
- in-process custom transport (proposed) — adapters are TS modules in the same process; broker emits
tools/listandtools/callJSON-RPC messages in-process; MCP2025-06-18allows custom transports provided they preserve JSON-RPC format + lifecycle - HTTP-based adapter microservices — overkill for M2; adds network hop + deployment complexity
Decision: In-process custom MCP transport for Proxmox/GitHub/Gitea adapters. The SSH adapter's MCP layer is also in-process, with downstream WebSocket transport to the M1 Relay Agent (Go binary) — the MCP tools/call JSON-RPC sits between broker and TS SSH adapter module; the TS module then calls the Relay Agent over M1's WebSocket.
Rationale: MCP 2025-06-18 §Transports explicitly states: "Clients and servers MAY implement additional custom transports... Implementers who choose to support custom transports MUST ensure they preserve the JSON-RPC message format and lifecycle requirements." Spawning subprocesses for same-process TypeScript modules is unnecessary overhead — the adapters share the broker's withTenant transaction, SecretProvider access, and audit writer. The in-process custom transport preserves JSON-RPC 2.0 message format (tools/list, tools/call requests; content[] + isError results). For SSH, the in-process MCP layer wraps the existing M1 Relay Agent WebSocket — the MCP conformance is at the JSON-RPC layer (broker ↔ TS SSH module), and the WebSocket to the Relay Agent is downstream transport that does not affect MCP conformance. The Relay Agent's CheckCommand (M1 G-004 contract) is the execution-layer enforcement.
Confidence: 0.80 Status: approved (PO, M2 spec lock 2026-08-25). Affects: REQ-015, REQ-016, REQ-021, REQ-026 implementation; ARCHITECTURE.md § MCP broker transport architecture. The broker ↔ UI uses REST facade + SSE (not MCP Streamable HTTP — a browser-friendly facade with MCP-compliant tool schemas/results inside). The broker ↔ CI/LLM smoke uses stdio transport.
Spec-derived constraints (no decision needed — locked by M2 spec §7)
These are recorded for traceability; they are NOT clarify decisions, just restated spec locks that constrain the M2 architecture.
- MCP spec version
2025-06-18(Q1) — latest stable with complete published documentation. Conformance verified against modelcontextprotocol.io (artifact at M2 gate). - 9-tool closed starter set (Q2) —
proxmox.list_vms,proxmox.get_vm_status,proxmox.get_node_metrics,ssh.run_whitelisted_command,github.list_repos,github.get_recent_ci_runs,github.get_workflow_run,gitea.list_repos,gitea.get_recent_ci_runs. Additions require spec amendment (v1.2+). - 6-command SSH whitelist subset (Q3) —
uptime,df -h,free -m,systemctl status <svc>,journalctl -n <N>(1-500),systemctl list-units --type=service. Broker validates before dispatch (layer 1); RelayCheckCommandvalidates at execution (layer 2). - In-memory token-bucket rate limiting (Q4) — per user (60/min) + per tenant (300/min), capacity = rate, refill 1/sec (user) / 5/sec (tenant). Redis migration path for M3.
- Version-aware Gitea scope validation (Q6) — ≥1.22:
read:repository; <1.22: any token with broker-side write-method blocklist. - Per-call SSE streams with ULID correlation IDs (Q7) — one stream per capability invocation; client disconnect cancels in-flight call, no audit event for client-side cancellation.
packages/llm-mockas devDependency (Q8) — CI-only, import-guarded against prod bundle.- M2→M3 contract freeze (Q9) — 5-endpoint REST+SSE contract frozen at M2 acceptance gate; M3 treats as stable API.
Clarify summary
| ID | Decision | Confidence | Status |
|---|---|---|---|
| D-001 | OpenAI-compatible BYOM contract for M1 (carried to M2) | 0.85 | approved |
| D-002 | Relay Agent in Go (carried to M2) | 0.90 | approved |
| D-003 | AWS SM (prod) + local-encrypted (dev) behind interface (carried to M2) | 0.85 | approved |
| D-004 | Postgres append-only + hash-chain for M1 audit; S3 WORM in M3 (carried to M2) | 0.80 | approved |
| D-005 | Next.js (App Router) + TypeScript single SPA (carried to M2) | 0.85 | approved |
| D-006 | GitHub fine-grained PAT: metadata:read + actions:read minimum (no contents:read) |
0.80 | approved (deviation from spec Q5) |
| D-007 | In-process custom MCP transport for TS adapters; SSH downstream WebSocket to M1 Relay | 0.80 | approved |
All above-threshold (≥0.6). No escalations. Pipeline proceeds to RESEARCH.