Merge phase/00-pre-execution into milestone/v0.1-bootstrap
Phase 0 (pre-execution) complete. All .ciagent/ reference files committed. Proceeding to execution phases (Wave A: Foundations). ---ci--- phase: 0 milestone: v0.1 status: complete ---/ci---
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
CoreCI Chat v0.1 is a multi-tenant SaaS with a TypeScript control plane, a Go Relay Agent distributed to customer Linux hosts, and an OpenAI-compatible BYOM routing layer. The control plane runs in a single AWS region (us-east-1) and enforces tenant isolation via Postgres Row-Level Security. Every request flows through an API gateway that authenticates the session, resolves the tenant, enforces RBAC, and writes an immutable audit entry. The Relay Agent is an outbound-only WebSocket client that registers as a target and heartbeats to the control plane; in M2 it will receive MCP tool calls (SSH) and enforce a fixed command whitelist. All credentials live in AWS Secrets Manager (prod) or a local-encrypted fallback (dev) behind a `SecretProvider` interface — never env vars, config files, or DB columns.
|
||||
|
||||
The v0.1 wedge is read-only diagnostic: no write actions, no hosted inference, no remediation. Async durable execution is provided by Trigger.dev (instrumented in M3 for chat workflows; the runtime bootstrap lands in M1 Wave A so M3 plugs in cleanly).
|
||||
|
||||
### Init-time invariants (preserved by the architecture-drift guard)
|
||||
- Single git repository at `~/coreci-chat`, branch hierarchy `main → milestone/v0.1-bootstrap → phase/NN-*`.
|
||||
- `.ciagent/` reference files at repo root (single-project mode).
|
||||
- Release target: Gitea `coreci/coreci-chat` at `https://git.cloudinit.dev`.
|
||||
- Autonomy: `full` (no HITL after clarify; decision threshold 0.6).
|
||||
|
||||
### Architecture invariants (M1 → v0.1)
|
||||
- Every HTTP request hits the API gateway first: auth → tenant resolve → RBAC → audit.
|
||||
- Every DB query runs under `SET app.tenant_id = ?` via a session-scoped transaction; RLS policies enforce scoping. Cross-tenant queries return empty.
|
||||
- Every credential is resolved via `SecretProvider.get(tenantId, key)`; the provider abstracts AWS Secrets Manager (prod) and local-encrypted (dev/test).
|
||||
- Every auditable event (prompt, tool call, SSH command, response) is appended to `audit_log` with a `prev_hash → curr_hash` chain; `UPDATE`/`DELETE` are REVOKE'd from the app role; a write failure halts the enclosing operation (REQ-038 Edge 7).
|
||||
- Every LLM inference call is routed to the tenant's configured BYOM endpoint via an OpenAI-compatible `/v1/chat/completions` contract; unconfigured/unreachable → reject with actionable error (REQ-009).
|
||||
- The Relay Agent makes only outbound connections (WebSocket to SaaS). No inbound firewall rules on customer hosts.
|
||||
|
||||
## Components
|
||||
|
||||
### apps/control-plane (TypeScript, Next.js App Router API routes + standalone services)
|
||||
- **Description**: API gateway, tenant resolution, RBAC enforcement, BYOM registry/validation/routing, Relay Agent WebSocket server, audit writer, secret provider bootstrap, dashboard (Next.js). Hosts the admin dashboard in M1; the chat UI lands here in M3.
|
||||
- **Boundaries**: All inbound HTTP/WebSocket enters here. Owns the Postgres connection and the `SET app.tenant_id` discipline. Owns the Trigger.dev runtime bootstrap.
|
||||
- **Depends on**: `packages/db`, `packages/auth`, `packages/audit`, `packages/secrets`, `packages/config`, Trigger.dev, WorkOS, AWS Secrets Manager.
|
||||
|
||||
### apps/relay-agent (Go)
|
||||
- **Description**: Single static binary. Outbound WebSocket client to the control plane; registration (tenant/target/hostname/OS/IP/version); heartbeat + exponential-backoff reconnect (max 5 → alert); systemd service; SSH whitelist file format + enforcement hook (the SSH adapter plugs into the hook in M2 — no SSH execution in M1).
|
||||
- **Boundaries**: No inbound ports. Reads its tenant registration token from a local config written by the install script (token resolves to a secret-manager reference, never the raw secret). All state is in-memory or on the control plane.
|
||||
- **Depends on**: control-plane WebSocket endpoint; systemd; the fixed SSH whitelist file shipped alongside the binary.
|
||||
|
||||
### apps/dashboard (Next.js, part of apps/control-plane in M1)
|
||||
- **Description**: Tenant admin dashboard surfaced in the same Next.js app. M1 surfaces Relay Agent status (green/yellow/red), target hostname, last 100 log lines, BYOM config + validation, RBAC user/role management. Per-tenant view under RLS.
|
||||
- **Boundaries**: Server components read via the same API gateway (no DB access bypassing RLS). Client components subscribe to a status WebSocket fan-out.
|
||||
- **Depends on**: `apps/control-plane` API, `packages/auth`.
|
||||
|
||||
### packages/db (TypeScript)
|
||||
- **Description**: Postgres schema, migrations, RLS policies, the `withTenant(tenantId, fn)` helper that sets `app.tenant_id` and runs `fn` in a transaction. Tables: `tenants`, `users`, `tenant_memberships` (user × tenant × role), `targets`, `byom_endpoints` (URL in DB, key ref to secret manager), `audit_log` (append-only, hash-chain), `invitations`. All tenant-scoped tables carry `tenant_id` and an RLS policy.
|
||||
- **Boundaries**: The ONLY module that opens a Postgres connection. App role has INSERT/SELECT on `audit_log` only; UPDATE/DELETE REVOKE'd. Schema migrations run via a gated migrator.
|
||||
- **Depends on**: Postgres 16.
|
||||
|
||||
### packages/auth (TypeScript)
|
||||
- **Description**: WorkOS SSO/SAML integration, session management, tenant resolution middleware, RBAC role → route permission map (Admin/Operator/Viewer). Enforces RBAC at the API gateway from the first endpoint (REQ-005 pattern).
|
||||
- **Boundaries**: Sessions are httpOnly cookies; tokens never logged. WorkOS is the only IdP in v0.1.
|
||||
- **Depends on**: WorkOS SDK, `packages/db`.
|
||||
|
||||
### packages/audit (TypeScript)
|
||||
- **Description**: Append-only audit writer. Computes `curr_hash = sha256(prev_hash || canonical_payload)`. Payload includes tenant_id, user_id, target_id (for SSH), timestamp, correlation_id, event_type, event_body. On write failure: throws `AuditWriteHaltError` → enclosing operation halts + admin alert (REQ-038 Edge 7).
|
||||
- **Boundaries**: Never exposes UPDATE/DELETE. Read path is admin-export only (no query UI in MVP — spec §2.2).
|
||||
- **Depends on**: `packages/db`.
|
||||
|
||||
### packages/secrets (TypeScript)
|
||||
- **Description**: `SecretProvider` interface with two impls: `AwsSecretsManagerProvider` (prod, us-east-1, KMS-backed) and `LocalEncryptedProvider` (dev/test, AES-256-GCM with a key from an env var only for the master key — the master key is the ONE allowed env var, everything else is provider-resolved). Every tenant credential (BYOM key, Proxmox token, SSH key, Git token) is stored/retrieved via `provider.put(tenantId, name, value)` / `provider.get(tenantId, name)`.
|
||||
- **Boundaries**: Secrets never logged, never written to DB columns, never in config files. The DB stores only a reference (e.g. `aws-sm:coreci/<tenantId>/byom`).
|
||||
- **Depends on**: AWS SDK (prod), Node crypto (dev).
|
||||
|
||||
### packages/config (TypeScript)
|
||||
- **Description**: Typed config loading (env-aware: prod/staging/dev/test). Loads non-secret config only. The only env vars consumed here are infra-level (DATABASE_URL, WORKOS_API_KEY, AWS_REGION, SECRET_MASTER_KEY_DEV, TRIGGER_API_KEY). All tenant/customer secrets go through `packages/secrets`.
|
||||
- **Boundaries**: Never reads tenant secrets directly.
|
||||
- **Depends on**: nothing.
|
||||
|
||||
## Data Flow
|
||||
|
||||
### M1 happy path — Relay Agent registration (J2 steps 3–4)
|
||||
```
|
||||
Platform Lead ──install script──▶ customer Linux host
|
||||
detect-OS → install-binary → write-systemd-unit → register-target (writes tenant reg token to local config)
|
||||
systemd ──start──▶ relay-agent (Go binary)
|
||||
relay-agent ──outbound WebSocket (wss, tenant reg token)──▶ control-plane WS server
|
||||
control-plane: auth token → resolve tenant (RLS) → INSERT target (hostname/OS/IP/version) → audit append
|
||||
control-plane ──ack + target_id──▶ relay-agent
|
||||
relay-agent ──heartbeat every Ns──▶ control-plane (updates last_seen → green status)
|
||||
control-plane ──status fan-out──▶ dashboard (server-sent events / WS)
|
||||
```
|
||||
|
||||
### M1 happy path — BYOM configure + validate (J2 step 2)
|
||||
```
|
||||
Admin ──POST /api/byom (url, key)──▶ API gateway
|
||||
auth → tenant resolve (RLS) → RBAC (Admin only) → audit append (config event)
|
||||
secrets.put(tenantId, "byom", key) → returns ref
|
||||
INSERT byom_endpoints (tenant_id, url, secret_ref) — under RLS
|
||||
BYOM validator: fetch OpenAI-compatible /v1/chat/completions with a trivial test prompt
|
||||
on success → mark validated=true, audit append (validation ok), return green
|
||||
on failure → rollback insert, audit append (validation fail), return error details (Edge 11)
|
||||
```
|
||||
|
||||
### M3 (forward reference) — chat diagnostic (J1)
|
||||
```
|
||||
Operator ──prompt──▶ API gateway → auth → tenant → RBAC (Operator) → audit append (prompt)
|
||||
orchestrator (Trigger.dev task) → BYOM routing shim → POST tenant's BYOM /v1/chat/completions
|
||||
if unconfigured/unreachable → reject (REQ-009, Edge 1), audit append, halt
|
||||
LLM returns tool selection → MCP gateway → route to adapter (M2) → Relay Agent (SSH, M2)
|
||||
read-only enforced at gateway (403 on write) and Relay Agent (whitelist)
|
||||
tool output streams via SSE → UI trace panel → audit append (tool call + SSH command + output)
|
||||
LLM synthesizes cited answer → stream to UI → audit append (response)
|
||||
conversation persisted (tenant-scoped) → audit append
|
||||
```
|
||||
|
||||
## Build Order (M1 waves — each a vertical slice, testable + shippable)
|
||||
|
||||
1. **Wave A — Foundations.** Monorepo (pnpm): `apps/control-plane`, `apps/relay-agent` (Go module), `apps/dashboard`, `packages/{db,auth,audit,secrets,config}`. Postgres schema + RLS + `withTenant`. `audit_log` hash-chain + REVOKE UPDATE/DELETE. `SecretProvider` interface + AWS SM impl + local-encrypted impl. Trigger.dev bootstrap (runtime wired, no tasks yet). Covers REQ-038, REQ-039, REQ-040.
|
||||
2. **Wave B — Identity & RBAC.** WorkOS SSO; session; tenant resolution middleware (`SET app.tenant_id`); RBAC at API gateway (Admin/Operator/Viewer → route permission map). First endpoint protected on day one. Covers REQ-001, REQ-002, REQ-003, REQ-004, REQ-005.
|
||||
3. **Wave C — BYOM.** Tenant-scoped BYOM endpoint registry (URL in DB, key in secret manager); validate-on-save test inference call (OpenAI-compatible); routing shim (no inference in M1 — proxy contract + REQ-009 reject path). Covers REQ-006, REQ-007, REQ-008, REQ-009.
|
||||
4. **Wave D — Relay Agent.** Modular install script (detect-OS/install-binary/write-systemd-unit/register-target; clean abort on unsupported OS). Go binary: outbound wss + tenant reg token, registration (tenant/target/hostname/OS/IP/version), heartbeat + exp-backoff (max 5 → alert), auto-reconnect, systemd unit w/ auto-restart. SSH whitelist file format + enforcement hook (no adapter yet — M2 plugs in). Covers REQ-010, REQ-011, REQ-012, REQ-013, (REQ-026 whitelist hook).
|
||||
5. **Wave E — Dashboard surfacing.** WebSocket fan-out of agent status to dashboard; green/yellow/red; target hostname; last 100 log lines; per-tenant view under RLS. M1 gate demo: SSO → BYOM green → install → register → green dashboard. Covers REQ-014.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"phase": 0,
|
||||
"stage": "mvp_ux_check",
|
||||
"milestone": "v0.1",
|
||||
"phase_role": "pre_execution",
|
||||
"attempts": 1,
|
||||
"updated_at": "2026-08-24T23:15:00Z"
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# Clarify — Architectural Decisions
|
||||
|
||||
Spec: CoreCI Chat v0.1 Engineering Specification v1.1 (locked 2026-08-24, Sarah Chen).
|
||||
Autonomy: `full` (decision threshold 0.6). All 5 decisions below were surfaced during kickoff and approved by the Product Owner before EXECUTE. Spec §7 resolved all 8 product-level open questions; the 5 decisions here are the remaining **architectural** choices the spec left to Engineering.
|
||||
|
||||
Each decision is recorded with: question, options considered, decision, rationale, confidence, status.
|
||||
|
||||
---
|
||||
|
||||
## D-001 — BYOM endpoint protocol contract
|
||||
|
||||
**Question:** Which wire protocol should the BYOM routing shim speak for M1, given customers may bring vLLM, TGI, Ollama, OpenAI, Azure OpenAI, Together, or self-hosted endpoints?
|
||||
|
||||
**Options considered:**
|
||||
- OpenAI-compatible `/v1/chat/completions` (universal interoperability)
|
||||
- Anthropic Messages API native
|
||||
- Pluggable provider interface with multiple impls from day one
|
||||
|
||||
**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 (vLLM, TGI, Ollama, OpenAI, Azure OpenAI, Together, LiteLLM) speak OpenAI-compatible. Native Anthropic support is a M3 concern; the interface keeps that door open without paying for it now. REQ-006/007/008/009 all reference a "test inference call" and "outbound traffic log" — OpenAI-compatible gives the cheapest validation path (one POST, JSON body, `choices[0].delta.content`).
|
||||
|
||||
**Confidence:** 0.85
|
||||
**Status:** approved (PO kickoff)
|
||||
**Affects:** REQ-006, REQ-007, REQ-008, REQ-009; ARCHITECTURE.md § apps/control-plane BYOM validator.
|
||||
|
||||
---
|
||||
|
||||
## D-002 — Relay Agent implementation language
|
||||
|
||||
**Question:** Should the Relay Agent be written in Go, Rust, or TypeScript (Node) given it is distributed via `curl|bash` and runs as a systemd service on Ubuntu 24.04 / Debian 12+?
|
||||
|
||||
**Options considered:**
|
||||
- Go — single static binary, zero runtime deps, tiny image, cross-compile trivial
|
||||
- Rust — single static binary, stronger safety, slower compile/iterate
|
||||
- Node/TypeScript — same language as control plane, but requires Node runtime on every customer host
|
||||
|
||||
**Decision:** **Go.**
|
||||
|
||||
**Rationale:** The install script ships a single static binary via `curl|bash`. Go gives that with zero customer-side runtime (no Node, no Python). Cross-compile to linux/amd64 + linux/arm64 is one command. systemd unit stays trivial (`ExecStart=/usr/local/bin/coreci-relay-agent`). The control plane stays TypeScript (Trigger.dev DX rationale from spec §7 Q1 is about the orchestration layer, not the agent). 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.9
|
||||
**Status:** approved (PO kickoff)
|
||||
**Affects:** REQ-010, REQ-011, REQ-012, REQ-013, REQ-026 (whitelist hook); ARCHITECTURE.md § apps/relay-agent.
|
||||
|
||||
---
|
||||
|
||||
## D-003 — Secret manager backend
|
||||
|
||||
**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?
|
||||
|
||||
**Options considered:**
|
||||
- AWS Secrets Manager only — simplest, but blocks CI/local
|
||||
- AWS Secrets Manager (prod) + local file (dev) — fast, but weak dev hygiene
|
||||
- AWS Secrets Manager (prod) + local-encrypted (dev) behind a `SecretProvider` interface — clean
|
||||
|
||||
**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 — `LocalEncryptedProvider` reads its master key from the ONE allowed env var (`SECRET_MASTER_KEY_DEV`), encrypts every tenant secret at rest with AES-256-GCM, and stores ciphertext in a gitignored local file. Prod swaps the impl via config. 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. The DB stores only a reference (e.g. `aws-sm:coreci/<tenantId>/byom`).
|
||||
|
||||
**Confidence:** 0.85
|
||||
**Status:** approved (PO kickoff)
|
||||
**Affects:** REQ-040; ARCHITECTURE.md § packages/secrets.
|
||||
|
||||
---
|
||||
|
||||
## D-004 — Audit log storage backend for 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?
|
||||
|
||||
**Options considered:**
|
||||
- S3 Object Lock WORM from day one — strongest immutability, but adds infra + an async write path that complicates "write failure halts the operation" (Edge 7)
|
||||
- Postgres append-only table with hash-chain + REVOKE UPDATE/DELETE — cheap, synchronous, halt-on-fail is trivial
|
||||
- Dedicated append-only service (e.g. QuestDB, ClickHouse) — overkill for M1 volume
|
||||
|
||||
**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 (onboarding + dashboard events, no chat yet). 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 is what propagates to M2/M3 — when we add S3 Object Lock in M3, the Postgres table stays as the hot path and S3 is the WORM cold store. Refactoring later is additive, not a rewrite. This matches the spec's "critical-path: append-only from day one" directive.
|
||||
|
||||
**Confidence:** 0.8
|
||||
**Status:** approved (PO kickoff)
|
||||
**Affects:** REQ-038; ARCHITECTURE.md § packages/audit, packages/db.
|
||||
|
||||
---
|
||||
|
||||
## D-005 — Web application framework
|
||||
|
||||
**Question:** Which framework for the browser surface, given M1 ships the admin dashboard and M3 ships the chat UI in the same product?
|
||||
|
||||
**Options considered:**
|
||||
- Next.js (App Router) + TypeScript, single SPA — one app for dashboard (M1) + chat (M3)
|
||||
- Separate Next.js apps (dashboard, chat) — clearer M1/M3 boundary, duplicate infra
|
||||
- Remix + TypeScript — similar DX, smaller ecosystem for SSE/streaming
|
||||
|
||||
**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) and chat (M3) share `packages/auth`, `packages/db`, `packages/audit` cleanly. App Router server components read via the API gateway (never bypassing RLS); M3's SSE streaming uses Route Handlers. TS-first aligns with the Trigger.dev rationale (spec §7 Q1).
|
||||
|
||||
**Confidence:** 0.85
|
||||
**Status:** approved (PO kickoff)
|
||||
**Affects:** ARCHITECTURE.md § apps/control-plane, apps/dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Spec-derived constraints (no decision needed — locked by spec)
|
||||
|
||||
These are recorded for traceability; they are NOT clarify decisions, just restated spec locks that constrain the architecture.
|
||||
|
||||
- **Trigger.dev** for durable execution (spec §7 Q1) — bootstrapped in Wave A, tasks added in M3.
|
||||
- **WorkOS** for SSO/SAML + SCIM (spec §7 Q2) — Wave B.
|
||||
- **Vanta** for GRC (spec §7 Q3) — instrumentation in M3 only.
|
||||
- **Install script (curl|bash), apt fallback** (spec §7 Q4) — Wave D, modular functions.
|
||||
- **Fixed SSH whitelist, no customer extension in v0.1** (spec §7 Q5) — file + hook in Wave D, adapter in M2.
|
||||
- **PVEAuditor built-in role** (spec §7 Q6) — M2 (adapter), documented now.
|
||||
- **Gitea via SaaS-to-API exposure** (spec §7 Q7) — M2 (adapter).
|
||||
- **pgvector for v1.1 RAG** (spec §7 Q8) — not v0.1.
|
||||
|
||||
---
|
||||
|
||||
## Clarify summary
|
||||
|
||||
| ID | Decision | Confidence | Status |
|
||||
|----|----------|-----------|--------|
|
||||
| D-001 | OpenAI-compatible BYOM contract for M1 | 0.85 | approved |
|
||||
| D-002 | Relay Agent in Go | 0.90 | approved |
|
||||
| D-003 | AWS SM (prod) + local-encrypted (dev) behind interface | 0.85 | approved |
|
||||
| D-004 | Postgres append-only + hash-chain for M1 audit; S3 WORM in M3 | 0.80 | approved |
|
||||
| D-005 | Next.js (App Router) + TypeScript single SPA | 0.85 | approved |
|
||||
|
||||
All above-threshold (≥0.6). No escalations. Pipeline proceeds to RESEARCH.
|
||||
@@ -0,0 +1,220 @@
|
||||
# GRILL.md — M1 Plan Adversarial Review
|
||||
|
||||
**Reviewer:** CIAgent griller (red-team persona)
|
||||
**Subject:** `.ciagent/PLAN.md` — M1 plan (5 waves A–E + final phase)
|
||||
**Scope:** 17 M1 REQs (001–014, 038, 039, 040) + 4 PO high-stakes claims
|
||||
**Date:** 2026-08-24
|
||||
**Method:** 9-axis adversarial review with binding verdicts. Confidence ≥ 0.60 = binding; < 0.60 = escalate.
|
||||
|
||||
---
|
||||
|
||||
## Verdict Summary
|
||||
|
||||
| Axis | Verdict | One-line rationale |
|
||||
|------|---------|-------------------|
|
||||
| 1. Feasibility | **PASS** | Each wave is a coherent vertical slice; Go binary + install script + WS server are well-trodden territory; no wave implies unsolved tech. |
|
||||
| 2. Scope | **PASS-WITH-FIXES** | Plan stays within the 17 M1 REQs, but the `/api/byom/test-inference` endpoint and the runtime health-check audit task are un-spec'd scope additions that need explicit PO acknowledgment. |
|
||||
| 3. Cost | **PASS-WITH-FIXES** | Decomposition is efficient and rework-minimizing, but Wave A ships Trigger.dev bootstrap (M3 infra) and Wave D ships an SSH whitelist hook with no caller in M1 — both are deliberate pre-investments that must be explicitly logged as debt-for-future-value, not hidden as "M1 work." |
|
||||
| 4. Dependencies | **PASS-WITH-FIXES** | Ordering is correct, but Wave D's WS server depends on Wave B's `/api/relay/issue-token` (auth token issuance) and the plan admits B and D must "coordinate the contract in the plan" — that contract is not specified here, creating a cross-wave coupling risk. |
|
||||
| 5. Testability | **PASS** | Every M1 REQ maps to ≥1 must-have pass/fail item; the 4 review deliverables are producible; coverage gate ≥80% is explicit. |
|
||||
| 6. Security | **PASS-WITH-FIXES** | RLS, audit REVOKE, secret provider, RBAC-from-first-endpoint, and SSH whitelist are sound patterns, but the per-tenant hash-chain has a chain-verification gap for concurrent writers, and the "no SSH execution in M1" whitelist hook can't be integration-tested against a real exec path — only unit-tested. |
|
||||
| 7. Architecture drift | **PASS** | Plan is faithful to ARCHITECTURE.md + all 5 CLARIFY decisions; no contradictions found. |
|
||||
| 8. Requirements coverage | **PASS-WITH-FIXES** | All 17 REQs have tasks + must-haves, but REQ-026 is listed as "whitelist hook only" in Wave D while its acceptance criteria (spec §4) describe full SSH-key auth + whitelist execution — the M1/M2 split is underspecified and the plan leans on a parenthetical, not a contract. |
|
||||
| 9. Operational readiness | **PASS-WITH-FIXES** | The M1 gate (spec §2.3) will pass and all 4 review deliverables are producible, but the install logs deliverable requires 3 OSes (Ubuntu 24.04, Debian 12+, unsupported) and the test strategy only lists 3 CI containers — Fedora as the "unsupported" case is an assumption, not a spec mandate; an unsupported-OS matrix needs explicit sign-off. |
|
||||
|
||||
**Final Verdict: PASS-WITH-FIXES** — The plan is sound and shippable. The fixes below are binding; none warrant a FAIL (escalation), but each must be resolved before the wave it touches ships.
|
||||
|
||||
---
|
||||
|
||||
## Axis 1: Feasibility
|
||||
|
||||
**Verdict: PASS**
|
||||
|
||||
Each wave is a vertical slice of known-complexity work. Wave A (Postgres RLS + append-only audit + secret provider + Trigger.dev bootstrap) is the densest but is well-researched (RESEARCH.md R-001/003/004) with concrete patterns. Wave D (Go binary + install script + WebSocket + whitelist) is the most heterogeneous but the Go persona is correctly scoped (PERSONAS.md) and `gorilla/websocket` + systemd is commodity. No wave implies an unsolved technical problem or a "learn as we go" risk on the delivery path. The one feasibility flag — Trigger.dev bootstrap with no tasks in M1 — is explicitly a no-op health check, which is the right de-risking choice.
|
||||
|
||||
---
|
||||
|
||||
## Axis 2: Scope
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
The plan maps cleanly to the 17 M1 REQs; no M2 (REQ-015–027) or M3 (REQ-028–037, 041–044) work is silently included. The out-of-scope list (REQUIREMENTS.md §Out of Scope) is respected. However, two un-spec'd scope additions exist in the plan and should be made explicit rather than smuggled in:
|
||||
|
||||
1. `POST /api/byom/test-inference` (Wave C, Task 3) is not in spec §4. Spec REQ-008 says "100% of LLM inference calls are sent to the configured BYOM endpoint (verified via outbound traffic log)" — in M1 there is no chat/orchestration to drive inference. The plan invents a test endpoint to *prove* REQ-008 without M3. This is a reasonable proxy, but it is a new surface and should be flagged as a plan-time scope addition, not implied by REQ-008.
|
||||
2. The Trigger.dev `runtimeHealthCheck` task that "appends an audit entry every 5 min" (Wave A, Task 7; R-001) writes synthetic audit entries with no business event behind them. REQ-038 lists "prompt, tool call, SSH command, response" as auditable events — a health-check tick is none of those. This pollutes the audit store with non-spec'd events and sets a precedent that "anything can append to audit_log."
|
||||
|
||||
**Required fixes:**
|
||||
1. Add a one-line note to Wave C Task 3 that `/api/byom/test-inference` is a plan-time proxy endpoint to satisfy REQ-008 in the absence of M3 orchestration; mark it for removal/deprecation when M3 lands. Get PO acknowledgment (non-blocking, but recorded).
|
||||
2. Change the Wave A Trigger.dev health task to write to a separate `runtime_health` table or log, NOT `audit_log`. REQ-038's audit store is for business events only. If a health tick must be auditable, define a new `event_type: "system.health"` and add it to the spec's auditable-event list via a follow-up — do not silently widen REQ-038's scope in Wave A.
|
||||
|
||||
---
|
||||
|
||||
## Axis 3: Cost
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
The decomposition is efficient: A→B/C parallel→D parallel→E is a near-critical path with real parallelism, and each wave ships a patch (releasable), avoiding a big-bang. The pre-investments are sound: shipping the whitelist hook in M1 (D-002 rationale) and the Trigger.dev runtime in M1 (R-001) are explicitly to avoid M2/M3 rewrites — this is the right trade. But the plan presents these as M1 deliverables without quantifying the cost-vs-future-value, which is exactly how "we'll add it later" debt gets hidden:
|
||||
|
||||
1. Wave A's Trigger.dev bootstrap (Task 7) is M3 infrastructure shipped in M1. It has no M1 caller. Its only M1 value is "proves the runtime works." That's a spike, not a deliverable — and spikes belong on a spike line, not the M1 acceptance gate.
|
||||
2. Wave D's SSH whitelist hook (Task 6) ships `CheckCommand` + whitelist JSON + unit tests with **no SSH execution path** in M1. This is correctly per the PO claim ("M2 plugs the adapter into the existing hook"), but it means M1 pays the cost of designing a hook against an imaginary caller. The cost is justified *if and only if* M2 actually uses the hook as-shipped. The plan provides no contract guaranteeing that.
|
||||
|
||||
**Required fixes:**
|
||||
3. Annotate Wave A Task 7 and Wave D Task 6 in PLAN.md as "pre-investment for M2/M3" with a one-line expected-payoff (avoids rewrite of X). This makes the cost visible in the plan rather than buried in a task list. Non-blocking, but required for audit traceability.
|
||||
4. Add a binding note to Wave D Task 6: "The `CheckCommand(cmd) error` signature and whitelist JSON schema are the M2 SSH adapter contract. M2 must consume them as-shipped; any signature change requires a documented migration." This locks the future-value claim the plan is spending M1 cost on.
|
||||
|
||||
---
|
||||
|
||||
## Axis 4: Dependencies
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
The wave ordering is correct: A is the true foundation (withTenant/audit/secrets), B and C depend only on A, D depends on A + a piece of B (auth token issuance), E depends on B + D. The parallelism diagram (PLAN.md §Wave ordering) is accurate. The gap is the one the plan itself flags but does not resolve:
|
||||
|
||||
- PLAN.md line 235: "D can run in parallel after A (independent of B/C; the WS server in D needs B's auth token-issuance endpoint — coordinate the contract in the plan, then D's WS server + B's token endpoint can land in the same wave window)."
|
||||
|
||||
This is an admission that D is **not** independent of B — it depends on `POST /api/relay/issue-token` (Wave B Task 1, wait — actually this endpoint is listed in Wave D Task 1, owned by backend-engineer). There's a territorial ambiguity: the token-issuance endpoint is in Wave D's task list (D Task 1) but the plan's parallelism note says it lives in B's window. Which wave owns the token contract? If D's go-engineer is blocked waiting on B's auth middleware to issue tokens, D does not truly parallelize.
|
||||
|
||||
**Required fixes:**
|
||||
5. Resolve the Wave B / Wave D token-issuance ownership in PLAN.md: explicitly state that `POST /api/relay/issue-token` (currently Wave D Task 1) is owned by **backend-engineer** and lands in whichever wave ships first, but that the *contract* (token format, scope, rotation) is defined in Wave A's secrets package so neither B nor D blocks on the other's implementation. Add the contract spec (token format: JWT? opaque? lifetime?) to Wave A or Wave B as a must-have.
|
||||
|
||||
---
|
||||
|
||||
## Axis 5: Testability
|
||||
|
||||
**Verdict: PASS**
|
||||
|
||||
Every M1 REQ has ≥1 pass/fail must-have. REQ-001/002/003/004/005 → Wave B must-haves (SSO round-trip, 403 test, role-change-enforced-next-call). REQ-006/007/008/009 → Wave C must-haves (secret-ref scan, validation green/red, 400/503 reject paths). REQ-010/011/012/013 → Wave D must-haves (3 OS install matrix, registration metadata, reconnect-backoff). REQ-014 → Wave E must-haves (green-within-90s, yellow→red aging, T1≠T2 RLS). REQ-038/039/040 → Wave A must-haves (chain verification, UPDATE/DELETE rejected, cross-tenant zero rows, secrets-not-in-DB scan). The 4 review deliverables (per-REQ report, demo, pen test, install logs) are explicitly produced in the Final Phase. Coverage gate ≥80% (spec §6) is enforced. The one soft spot — the SSH whitelist hook has no integration test against a real exec path in M1 — is captured under Axis 6, not here, because the *unit* testability is complete.
|
||||
|
||||
---
|
||||
|
||||
## Axis 6: Security
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
The patterns are sound and match RESEARCH.md R-003/004 and CLARIFY D-003/004:
|
||||
- RLS: `SET LOCAL app.tenant_id` per-transaction, app role non-superuser, migrator role BYPASSRLS-gated, `withTenant` wrapper, query-outside-wrapper throws. Correct.
|
||||
- Audit: append-only `audit_log`, `REVOKE UPDATE/DELETE` from `coreci_app`, hash-chain `curr_hash = sha256(prev_hash || canonical(payload))`, constraint trigger rejects forged `prev_hash`, write-failure rolls back the enclosing transaction (Edge 7). Correct.
|
||||
- Secrets: `SecretProvider` interface, AWS SM (prod) + local-encrypted (dev), DB stores only `secret_ref`, `SecretValue.toString()` returns `[REDACTED]`, lint rule bans `console.log(secret)`. Correct.
|
||||
- RBAC: enforced at API gateway from first endpoint (`GET /api/me`), role→route map, 403 test for Viewer→Admin route. Correct.
|
||||
- SSH whitelist: fixed file, `CheckCommand` parses base + args, deny list catches `-exec`/redirection/operators, unit tests for `rm -rf`/`find -exec`/pipe-to-nc. Correct *as far as it goes*.
|
||||
|
||||
Two gaps:
|
||||
|
||||
1. **Per-tenant hash-chain concurrency.** R-003 notes "Per-tenant chain is simpler... avoids cross-tenant ordering contention" and recommends partitioning by `tenant_id` or heavy indexing on `(tenant_id, id)`. But the constraint trigger that enforces `prev_hash = (last row's curr_hash for that tenant)` requires reading "the last row for this tenant" — under concurrent writers in the same tenant (two simultaneous audit appends), both read the same `prev_hash`, both INSERT, and one's `prev_hash` will fail the constraint. That's correct (no corruption), but it means concurrent audit writes in one tenant will *serialize-fail* and roll back. For M1 volume (onboarding, dashboard) this is fine. For M3 (chat with parallel tool calls) it's a bottleneck. The plan should state this is a known M1-acceptable limitation with a documented M3 mitigation (advisory lock per tenant, or sequence-per-tenant, or accept the rollback-retry).
|
||||
|
||||
2. **Whitelist hook has no integration test path in M1.** `CheckCommand` is unit-tested, but the claim "Retrofit later = rewrite" (PO claim #1) rests on the hook being *correct in the shape M2 will consume*. With no exec path, M1 cannot prove the hook actually intercepts a real SSH command — only that it parses strings. An M2 discovery that `exec.Command` needs the command pre-split differently, or that the deny list misses a real-world escape (e.g., `systemctl status; rm -rf /` where `;` is in the arg not the base), would force a rework *despite* the M1 pre-investment.
|
||||
|
||||
**Required fixes:**
|
||||
6. Add to Wave A audit task (or RESEARCH.md R-003) an explicit note: "Per-tenant hash-chain serializes concurrent audit writes within one tenant via constraint-trigger rollback. Acceptable for M1 volume. M3 mitigation: per-tenant advisory lock (`pg_advisory_xact_lock(hashtext(tenantId))`) before the INSERT, or sequence-per-tenant." This documents the known limit so M3 isn't surprised.
|
||||
7. Add to Wave D Task 6 a **shadow integration test**: a Go test that constructs an `exec.Cmd` from a parsed whitelist command (e.g., `exec.Command("systemctl", "status", "nginx")`) and asserts `CheckCommand` accepts it, plus a negative test that `exec.Command("rm", "-rf", "/")` is rejected *before* the Cmd would be started. This proves the hook composes with `os/exec` without needing a live SSH server. Closes the "M2 rework" risk the PO claim is hedging against.
|
||||
|
||||
---
|
||||
|
||||
## Axis 7: Architecture drift
|
||||
|
||||
**Verdict: PASS**
|
||||
|
||||
Line-for-line, PLAN.md is faithful to ARCHITECTURE.md and all 5 CLARIFY decisions:
|
||||
- D-001 (OpenAI-compatible BYOM): Wave C uses `/v1/chat/completions`. ✓
|
||||
- D-002 (Go Relay Agent): Wave D ships a Go binary + systemd. ✓
|
||||
- D-003 (AWS SM + local-encrypted behind interface): Wave A Task 6 ships both impls. ✓
|
||||
- D-004 (Postgres append-only + hash-chain, S3 WORM deferred to M3): Wave A Task 5 matches exactly. ✓
|
||||
- D-005 (Next.js App Router single SPA): Wave B/E ship dashboard in `apps/control-plane`/`apps/dashboard`. ✓
|
||||
- The API-gateway-first invariant (ARCHITECTURE.md §invariants) is enforced by Wave B Task 4. ✓
|
||||
- The `withTenant` discipline is owned by data-engineer (territory alignment matches PERSONAS.md). ✓
|
||||
- No contradictions between PLAN.md, ARCHITECTURE.md, and CLARIFY.md found.
|
||||
|
||||
---
|
||||
|
||||
## Axis 8: Requirements coverage
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
All 17 M1 REQs have explicit tasks and must-haves:
|
||||
- REQ-001→005: Wave B. ✓
|
||||
- REQ-006→009: Wave C. ✓
|
||||
- REQ-010→013: Wave D. ✓
|
||||
- REQ-014: Wave E. ✓
|
||||
- REQ-038/039/040: Wave A. ✓
|
||||
|
||||
The single coverage problem is **REQ-026**. The plan lists "REQ-026 (whitelist hook only)" in Wave D (PLAN.md line 17, 154, 163). But spec §4 REQ-026's acceptance criteria reads: "*Given a customer generates an SSH keypair... when the Relay Agent receives a tool call, then only commands on the approved whitelist are executed; non-whitelisted commands are rejected and audited.*" That is full SSH-key-auth + whitelist **execution** — which is M2 work (REQUIREMENTS.md traceability line 98 confirms "deferred (whitelist format + hook ships M1 Wave D)"). The plan's parenthetical "(whitelist hook only)" is doing a lot of load-bearing work and is the single most likely place for a scope dispute at the M1 review.
|
||||
|
||||
The REQUIREMENTS.md traceability table (line 98) correctly defers REQ-026 to M2 with the M1-hook note. The plan is *consistent* with REQUIREMENTS.md. But the spec §4 acceptance criteria for REQ-026 are NOT M1-eligible as written. This is a spec-vs-plan wording gap, not a plan defect — yet the plan inherits the ambiguity.
|
||||
|
||||
**Required fixes:**
|
||||
8. Add to PLAN.md Wave D a one-line scope statement: "M1 ships REQ-026 *partially*: the whitelist file format + `CheckCommand` enforcement hook + unit tests. The spec §4 REQ-026 acceptance criteria (SSH keypair auth + tool-call-driven execution) are M2. M1's must-have is the hook + whitelist, NOT end-to-end SSH execution." This makes the partial-REQ-026 coverage explicit so the M1 review doesn't dispute it. (This is a clarification, not a spec change — the REQUIREMENTS.md traceability already says this.)
|
||||
|
||||
---
|
||||
|
||||
## Axis 9: Operational readiness
|
||||
|
||||
**Verdict: PASS-WITH-FIXES**
|
||||
|
||||
The M1 acceptance gate (spec §2.3) will pass: the Happy Path (PLAN.md §Happy Path) walks SSO → BYOM green → install → register → green dashboard, and each step has a must-have. The 4 review deliverables (Final Phase §M1 review deliverables) are all producible:
|
||||
1. Per-REQ report: each REQ has must-haves → pass/fail. ✓
|
||||
2. Demo recording: Happy Path is walkable end-to-end (Wave E must-have line 197). ✓
|
||||
3. Pen test: `tests/pen/cross-tenant.test.ts` (Wave A scaffold, Final full run). ✓
|
||||
4. Install logs on 3 OSes: Wave D must-haves + CI matrix. ✓ (with the caveat below)
|
||||
|
||||
The gap: deliverable #4 requires install logs on "Ubuntu 24.04 (pass), Debian 12+ (pass), unsupported OS (clean abort)" (Final Phase line 217). The test strategy (PLAN.md line 244) says "CI matrix runs `scripts/install.sh` on Ubuntu 24.04, Debian 12, Fedora (expects abort) containers." Fedora as the *representative* unsupported OS is a plan choice, not a spec mandate. Spec §5 says "Install script aborts on unsupported OS" — it does not name Fedora. If the pen-test reviewer asks "did you test CentOS? RHEL? Alpine?" the plan has no answer. The "unsupported OS" deliverable is under-specified.
|
||||
|
||||
**Required fixes:**
|
||||
9. Define the unsupported-OS test matrix explicitly in PLAN.md Wave D must-haves: at minimum 2 unsupported cases (e.g., Fedora + Alpine, or CentOS Stream + Arch) to prove `detect_os` aborts on a non-Debian-family OS and a wrong-version Debian-family OS. Single-OS "unsupported" is not sufficient evidence for the M1 review deliverable.
|
||||
|
||||
---
|
||||
|
||||
## High-Stakes Claim Stress-Tests
|
||||
|
||||
### Claim 1 — "SSH command whitelist (REQ-026) — ship the whitelist file format and the enforcement hook in the Relay Agent now. M2 plugs the adapter into the existing hook. Retrofit later = rewrite."
|
||||
|
||||
**Verdict: CLAIM SUBSTANTIVELY MET, WITH ONE GAP.**
|
||||
|
||||
Wave D Task 6 ships `/etc/coreci/ssh-whitelist.json` (versioned, with commands + arg-deny list) and `apps/relay-agent/whitelist/check.go` (`CheckCommand(cmd) error`) with unit tests. This *is* the file format + enforcement hook the PO demanded. The retrofit-later-rewrite risk is genuinely mitigated. **The gap**: with no SSH execution path in M1, the hook is only string-tested, not exec-composition-tested. If M2's `exec.Command` integration reveals the hook needs the command pre-split or the deny list misses real-world escapes, M2 will rework *despite* the M1 investment. The PO's "rewrite" framing assumes the hook is correct as-shipped; M1 cannot prove that without an exec path. **Fix #7 (shadow `exec.Cmd` integration test) closes this.** With that fix, the claim holds.
|
||||
|
||||
### Claim 2 — "Audit log immutability (REQ-038) — append-only store from day one. The pattern you set in M1 propagates to every event in M2/M3."
|
||||
|
||||
**Verdict: CLAIM MET, WITH ONE DOCUMENTED LIMIT.**
|
||||
|
||||
Wave A Task 5 ships the Postgres append-only `audit_log` with hash-chain + `REVOKE UPDATE/DELETE` + constraint trigger on forged `prev_hash` + halt-on-write-failure. This is the right day-one pattern (CLARIFY D-004 rationale is correct: additive refactor to S3 WORM in M3, not a rewrite). The hash-chain pattern *does* propagate cleanly to M2/M3 because the `append(event)` contract is event-type-agnostic. **The limit**: per-tenant concurrent-write serialization (Axis 6 gap #1) is acceptable for M1 but will surface in M3 under parallel tool calls. **Fix #6** documents this so M3 isn't a surprise. With that documentation, the claim holds for M1 and the propagation story is sound.
|
||||
|
||||
### Claim 3 — "RBAC enforcement (REQ-005) — at the API gateway from the first endpoint. No 'we'll add auth later' stubs."
|
||||
|
||||
**Verdict: CLAIM MET — NO STUB PERIOD.**
|
||||
|
||||
Wave B Task 4 ships `packages/auth/rbac.ts` (role→route map) applied at the API gateway, with `GET /api/me` as the first protected endpoint and a `Viewer → 403 on POST /api/byom` test. There is no "auth-later" stub: the middleware runs on every request, the role map is enforced before the handler, and the test proves denial. Wave C's BYOM routes inherit this. The claim is satisfied as literally stated. No fix needed.
|
||||
|
||||
### Claim 4 — "Secret manager (REQ-040) — every credential via the secret manager from the very first secret. No env vars, no config files, no DB columns. Ever."
|
||||
|
||||
**Verdict: CLAIM MET IN LETTER, WITH A SEMANTIC GAP THAT MUST BE NAMED.**
|
||||
|
||||
Wave A Task 6 ships `SecretProvider` + AWS SM + local-encrypted; Wave C stores BYOM keys via `secrets.put`; the DB holds only `secret_ref`. No tenant credential is in an env var, config file, or DB column. **But** ARCHITECTURE.md (line 61) and RESEARCH.md (R-001 line 15, R-004 line 54) explicitly carve out *infra-level* env vars: `DATABASE_URL`, `WORKOS_API_KEY`, `AWS_REGION`, `SECRET_MASTER_KEY_DEV`, `TRIGGER_API_KEY`. These are not tenant secrets — they are platform bootstrap credentials. The PO's "No env vars... Ever" is, read literally, false; read sensibly, it means "no *tenant* secret in env vars." This distinction is load-bearing and currently lives only in ARCHITECTURE.md §packages/config and RESEARCH.md footnotes — it is not surfaced in PLAN.md. A reviewer applying the PO's claim verbatim would flag `WORKOS_API_KEY` and `TRIGGER_API_KEY` as violations.
|
||||
|
||||
**Required fix:**
|
||||
10. Add to PLAN.md Wave A (or a new "Credential taxonomy" note) an explicit two-tier model: **(a) Infra/bootstrap credentials** (`DATABASE_URL`, `WORKOS_API_KEY`, `TRIGGER_API_KEY`, `AWS_REGION`, `SECRET_MASTER_KEY_DEV`) loaded via `packages/config` from env vars — these are platform-level, not tenant-scoped. **(b) Tenant credentials** (BYOM key, Proxmox token, SSH key, Git token, tenant reg token) via `SecretProvider` only — never env/config/DB. State that the PO's "no env vars" claim applies to tier (b), and that tier (a) is the documented exception. Without this, the M1 security review will spend cycles re-litigating `WORKOS_API_KEY`.
|
||||
|
||||
---
|
||||
|
||||
## Binding Fixes (consolidated)
|
||||
|
||||
Only items required by PASS-WITH-FIXES verdicts. Numbered G-001..G-010 for this grill session.
|
||||
|
||||
| ID | Axis | Fix | Blocking wave |
|
||||
|----|------|-----|---------------|
|
||||
| G-001 | 2 | Flag `/api/byom/test-inference` as a plan-time proxy endpoint for REQ-008, marked for M3 deprecation; get PO acknowledgment. | Wave C |
|
||||
| G-002 | 2 | Stop writing Trigger.dev health-check ticks to `audit_log`; use a separate `runtime_health` table or define a new `event_type: "system.health"` via spec follow-up. Do not widen REQ-038's auditable-event list silently. | Wave A |
|
||||
| G-003 | 3 | Annotate Wave A Task 7 (Trigger.dev) and Wave D Task 6 (whitelist hook) as "pre-investment for M2/M3" with one-line expected payoff, so the cost is visible in the plan. | Wave A, D |
|
||||
| G-004 | 3 | Lock `CheckCommand(cmd) error` signature + whitelist JSON schema as the M2 SSH adapter contract; any signature change requires a documented migration. | Wave D |
|
||||
| G-005 | 4 | Resolve Wave B/D token-issuance ownership: `POST /api/relay/issue-token` owned by backend-engineer, contract (token format, scope, lifetime) defined in Wave A secrets package so B and D parallelize without blocking. | Wave A/B/D |
|
||||
| G-006 | 6 | Document per-tenant hash-chain concurrent-write serialization as a known M1-acceptable limit; record M3 mitigation (advisory lock or sequence-per-tenant). | Wave A |
|
||||
| G-007 | 6 | Add a shadow `exec.Cmd` integration test in Wave D proving `CheckCommand` composes with `os/exec` (positive: `systemctl status nginx`; negative: `rm -rf /` rejected pre-start). | Wave D |
|
||||
| G-008 | 8 | Add explicit Wave D scope statement: M1 ships REQ-026 *partially* (whitelist file + hook + tests); spec §4 SSH-key-auth + tool-call execution are M2. | Wave D |
|
||||
| G-009 | 9 | Define unsupported-OS test matrix as ≥2 cases (e.g., Fedora + Alpine) in Wave D must-haves, not a single Fedora container. | Wave D |
|
||||
| G-010 | 6/claim4 | Add a two-tier credential taxonomy to PLAN.md: infra/bootstrap creds (env vars, listed) vs tenant creds (SecretProvider only). State PO's "no env vars" applies to tenant creds only. | Wave A |
|
||||
|
||||
**No escalations.** All 9 axes resolved at confidence ≥ 0.60. All 4 PO claims either met or met-with-named-fix. No axis FAILED.
|
||||
|
||||
---
|
||||
|
||||
## Final Verdict
|
||||
|
||||
**PASS-WITH-FIXES** — The M1 plan is sound, faithful to spec and architecture, covers all 17 REQs, and will pass the M1 acceptance gate. The 10 binding fixes (G-001..G-010) are required before the wave each touches ships; none block the plan from proceeding to Wave A. The plan's central bet — pre-shipping the Trigger.dev runtime and the SSH whitelist hook to avoid M2/M3 rewrites — is the right call, provided fixes G-002, G-004, G-007, and G-010 lock those pre-investments into actual contracts rather than aspirations.
|
||||
@@ -0,0 +1,24 @@
|
||||
# MVP/UX Check (Phase 0 gate)
|
||||
|
||||
Per the run workflow, the MVP/UX checkpoint (REQ-MVP-UX-001) runs between GRILL and EXECUTE. At `full` autonomy, the orchestrator verifies the 3 sections are present in PLAN.md and auto-generates any missing sections.
|
||||
|
||||
## Verification
|
||||
|
||||
PLAN.md (`.ciagent/PLAN.md`) is checked for the 3 mandatory sections:
|
||||
|
||||
| Section | Required | Present | Location | Content |
|
||||
|---------|----------|---------|----------|---------|
|
||||
| `## User-Facing Surface` | yes | ✓ | PLAN.md line 49 | Names the M1 user-facing surface: Platform Lead admin dashboard (browser, Next.js, at `/dashboard`). Lists 8 specific dashboard surfaces (SSO entry, onboarding checklist, BYOM config form, relay install instructions, targets list, target detail, team/RBAC, audit export) + 3 non-UI operator surfaces (install script, Go binary, systemd unit). Explicitly states chat UI is M3, not M1. |
|
||||
| `## Happy Path` | yes | ✓ | PLAN.md line 66 | End-to-end M1 scenario written BEFORE execute. Maps to spec Journey 2 (steps 1–4 + 7 + 9). 8 BDD steps (Given/When/Then) covering: SSO signup + tenant provision, BYOM validate-and-save, install script on Ubuntu 24.04, Relay Agent registration + heartbeat, dashboard green, team invite + RBAC enforcement, cross-tenant isolation, unsupported-OS abort. States this Happy Path IS the M1 demo recording required by the M1 review. |
|
||||
| `## UX Acceptance Criteria` | yes | ✓ | PLAN.md line 86 | 9 explicit pass/fail criteria: SSO <3 clicks, BYOM feedback synchronous <10s, dashboard green within 90s of first heartbeat, install command copy-pasteable, RBAC enforced on next call, cross-tenant isolation verifiable, audit append-only (UPDATE/DELETE fails), secrets never in DB (scan returns zero), unsupported OS aborts cleanly. Each maps to a REQ. |
|
||||
|
||||
All 3 sections present and substantive. No auto-generation needed.
|
||||
|
||||
## Verdict
|
||||
|
||||
**PASS** — PLAN.md satisfies REQ-MVP-UX-001. EXECUTE is unblocked.
|
||||
|
||||
## Post-check actions
|
||||
|
||||
- Update CHECKPOINT.json: `stage: "mvp_ux_check"` → next: PHASE 0 SHIP.
|
||||
- Proceed to Phase 0 ship (tag `v0.0.1`, merge `phase/00-pre-execution` → `milestone/v0.1-bootstrap`).
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
# PERSONAS.md — CoreCI Chat v0.1 M1 persona configuration
|
||||
# Produced at end of RESEARCH (Phase 0). Lead-developer assessment.
|
||||
---
|
||||
|
||||
## Persona Roster
|
||||
|
||||
Active personas for CoreCI Chat v0.1 M1:
|
||||
|
||||
| Persona | Active | Phase-Specific | Reason |
|
||||
|---------|--------|-----------------|--------|
|
||||
| backend-engineer | yes | no | Owns API gateway, RBAC, audit, secret manager, BYOM routing, Trigger.dev bootstrap, Postgres RLS — the bulk of M1. |
|
||||
| data-engineer | yes | no | Owns Postgres schema, migrations, RLS policies, audit hash-chain. RLS + append-only audit are data-engineering territory. |
|
||||
| frontend-engineer | yes | no | Owns the Next.js dashboard (Wave E): agent status, green/yellow/red, last 100 log lines, BYOM config form, RBAC user/role management UI. |
|
||||
| lead-developer | yes | no | Coordinates wave decomposition, resolves territory disputes (e.g., who owns the `withTenant` helper — data vs backend), makes final architectural calls. |
|
||||
| general | no | — | Not needed; the four specialized personas cover M1. |
|
||||
| security-engineer | yes | no (custom) | M1 has heavy security surface: RBAC enforcement, RLS, audit immutability, secret manager, SSH whitelist hook, cross-tenant pen test. The default four personas lack a dedicated security lens; this custom persona owns the security review for Waves A/D specifically. |
|
||||
| go-engineer | yes | yes (Wave D only) | Custom persona for the Go Relay Agent (Wave D). The default four personas are TS/web-oriented; Go systemd+WebSocket+whitelist work needs a Go-specific territory. Removed after Wave D ships. |
|
||||
|
||||
## Framework Alignment (overrides from package.json — to be set when the monorepo is created)
|
||||
|
||||
These will be finalized at Wave A start once `package.json` + `go.mod` exist. Preliminary:
|
||||
|
||||
- backend-engineer: `frameworks: [next, node, typescript, trigger.dev, workos-sdk, aws-sdk]`
|
||||
- data-engineer: `frameworks: [postgres, knex|prisma, node, typescript]`
|
||||
- frontend-engineer: `frameworks: [next, react, typescript, tailwind]`
|
||||
- security-engineer: `frameworks: [node, typescript, postgres-rls, aws-kms, go-seccomp]`
|
||||
- go-engineer: `frameworks: [go, gorilla-websocket, systemd]`
|
||||
|
||||
## Territory Alignment (overrides to match actual file structure)
|
||||
|
||||
Preliminary globs, to be refined after Wave A scaffolds the monorepo:
|
||||
|
||||
- backend-engineer:
|
||||
- `apps/control-plane/**`
|
||||
- `packages/auth/**`
|
||||
- `packages/audit/**`
|
||||
- `packages/secrets/**`
|
||||
- `packages/config/**`
|
||||
- `packages/runtime/**`
|
||||
- data-engineer:
|
||||
- `packages/db/**`
|
||||
- `apps/control-plane/lib/db/**`
|
||||
- migrations: `packages/db/migrations/**`
|
||||
- frontend-engineer:
|
||||
- `apps/dashboard/**`
|
||||
- `apps/control-plane/app/(dashboard)/**`
|
||||
- security-engineer:
|
||||
- `packages/auth/rbac/**`
|
||||
- `packages/audit/**`
|
||||
- `packages/secrets/**`
|
||||
- `packages/db/rls/**`
|
||||
- tests: `tests/security/**`, `tests/pen/**`
|
||||
- go-engineer (Wave D only):
|
||||
- `apps/relay-agent/**`
|
||||
- `scripts/install.sh`, `scripts/install/*`
|
||||
- `apps/relay-agent/whitelist/**`
|
||||
|
||||
## Constraint Alignment
|
||||
|
||||
Shared across all personas (from PROJECT.md Constraints + spec §5):
|
||||
|
||||
- Read-only by default; 100% of write-action requests rejected at MCP gateway (M2) and Relay Agent (M1 whitelist hook).
|
||||
- BYOM mandatory; 100% of inference outbound to customer endpoint.
|
||||
- Multi-tenancy isolation via Postgres RLS; cross-tenant queries return empty.
|
||||
- Audit immutability; append-only; write failure halts.
|
||||
- Secret handling; every credential via `SecretProvider`; no env vars, config files, or DB columns for tenant secrets.
|
||||
- RBAC enforced at API gateway from the first endpoint.
|
||||
- Branch discipline: writes only on `phase/NN-*`; `---ci---` blocks in every commit.
|
||||
|
||||
Persona-specific:
|
||||
|
||||
- security-engineer: must sign off on Wave A (RLS + audit + secrets) and Wave D (whitelist hook) before those waves ship. Blocks the wave ship on a P0/P1 finding.
|
||||
- go-engineer: the install script must be modular (detect-OS/install-binary/write-systemd-unit/register-target as separate functions) per PO kickoff note. A single monolithic `install.sh` is a P0 finding.
|
||||
- frontend-engineer: server components read via the API gateway (never bypass RLS); client components subscribe to the status WebSocket fan-out.
|
||||
|
||||
## Phase-Specific Personas
|
||||
|
||||
- `go-engineer`: active for Wave D only. After Wave D ships, the persona is removed from the roster and the `apps/relay-agent/**` territory reverts to `backend-engineer` for any M1 follow-up. M2 reactivates a Go persona for the SSH adapter.
|
||||
|
||||
## Notes
|
||||
|
||||
- This file is consumed by the EXECUTE workflow for persona assignment and territory enforcement.
|
||||
- Framework + territory globs will be re-validated at Wave A start against the actual `package.json` / `go.mod` and committed as a follow-up to the Wave A plan commit.
|
||||
@@ -0,0 +1,279 @@
|
||||
# Plan — Milestone 1 (v0.1)
|
||||
|
||||
Source spec: `.ciagent/steer-v0.1-spec.md` (v1.1, locked 2026-08-24).
|
||||
M1 scope: REQ-001..014, REQ-038, REQ-039, REQ-040 (17 REQs). M1 acceptance gate: spec §2.3.
|
||||
Architecture: `.ciagent/ARCHITECTURE.md`. Decisions: `.ciagent/CLARIFY.md`. Research: `.ciagent/RESEARCH.md`. Personas: `.ciagent/PERSONAS.md`.
|
||||
|
||||
M1 is decomposed into **5 execution phases** (Waves A–E). Each wave is a vertical slice: scaffolds + implements + tests + ships a patch on the v0.0.x line. Each wave maps to explicit REQ IDs and has must-have verification items. Waves are ordered by dependency; A → B → C may overlap (B starts once A's `withTenant` + `audit` are green); D is independent of B/C and can run in parallel; E depends on B + D.
|
||||
|
||||
## Phase mapping (CIAgent phase model)
|
||||
|
||||
| CIAgent phase | Wave | Branch | Patch tag | REQs |
|
||||
|---------------|------|--------|-----------|------|
|
||||
| Phase 0 | pre-execution | `phase/00-pre-execution` | v0.0.1 | (this plan) |
|
||||
| Phase 1 | Wave A — Foundations | `phase/01-foundations` | v0.0.2 | REQ-038, REQ-039, REQ-040 |
|
||||
| Phase 2 | Wave B — Identity & RBAC | `phase/02-identity-rbac` | v0.0.3 | REQ-001, REQ-002, REQ-003, REQ-004, REQ-005 |
|
||||
| Phase 3 | Wave C — BYOM | `phase/03-byom` | v0.0.4 | REQ-006, REQ-007, REQ-008, REQ-009 |
|
||||
| Phase 4 | Wave D — Relay Agent | `phase/04-relay-agent` | v0.0.5 | REQ-010, REQ-011, REQ-012, REQ-013, REQ-026 (whitelist hook) |
|
||||
| Phase 5 | Wave E — Dashboard surfacing | `phase/05-dashboard` | v0.0.6 | REQ-014 |
|
||||
| Phase 6 | Final — Review + Ship | `phase/06-final-review-ship` | v0.0.7 ← milestone release | all M1 |
|
||||
|
||||
Tags run on the v0.0.x patch line (no prior minor). The final phase's patch (v0.0.7) IS the v0.1 milestone release. The milestone merge to `main` happens at the final phase.
|
||||
|
||||
### Grill fixes applied (G-001..G-010)
|
||||
|
||||
This plan was grilled (`.ciagent/GRILL.md`, verdict PASS-WITH-FIXES). The 10 binding fixes are integrated below and flagged inline as `[G-NNN]`. Summary:
|
||||
|
||||
- **G-001** (Wave C): `/api/byom/test-inference` is a plan-time proxy for REQ-008 (no M3 orchestration to drive inference); marked for M3 deprecation; PO-acknowledged.
|
||||
- **G-002** (Wave A): Trigger.dev health-check ticks write to a `runtime_health` table, NOT `audit_log`. REQ-038's auditable events are business events only.
|
||||
- **G-003** (Wave A + D): Trigger.dev bootstrap + SSH whitelist hook are pre-investments for M2/M3, with one-line expected payoff.
|
||||
- **G-004** (Wave D): `CheckCommand(cmd) error` signature + whitelist JSON schema are the M2 SSH adapter contract; changes require a documented migration.
|
||||
- **G-005** (Wave A + B + D): `POST /api/relay/issue-token` owned by backend-engineer; token contract (format/scope/lifetime) defined in Wave A secrets package so B and D parallelize without blocking.
|
||||
- **G-006** (Wave A): per-tenant hash-chain serializes concurrent audit writes via constraint-trigger rollback — known M1-acceptable limit; M3 mitigation documented.
|
||||
- **G-007** (Wave D): shadow `exec.Cmd` integration test proves `CheckCommand` composes with `os/exec` without a live SSH server.
|
||||
- **G-008** (Wave D): M1 ships REQ-026 *partially* (whitelist file + hook + tests); spec §4 SSH-key-auth + tool-call execution are M2.
|
||||
- **G-009** (Wave D): unsupported-OS test matrix is ≥2 cases (Fedora + Alpine), not a single container.
|
||||
- **G-010** (Wave A): two-tier credential taxonomy — infra/bootstrap creds (env vars, listed) vs tenant creds (SecretProvider only). PO's "no env vars" applies to tenant creds.
|
||||
|
||||
### Credential taxonomy [G-010]
|
||||
|
||||
The PO's "no env vars, no config files, no DB columns. Ever." (REQ-040) applies to **tenant credentials**. The platform has a two-tier model:
|
||||
|
||||
- **Tier (a) — Infra/bootstrap credentials** (platform-level, NOT tenant-scoped): loaded via `packages/config` from environment variables. These are: `DATABASE_URL`, `WORKOS_API_KEY`, `TRIGGER_API_KEY`, `TRIGGER_API_URL`, `AWS_REGION`, `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` (or IAM role), `SECRET_MASTER_KEY_DEV` (dev/test only). They are never tenant secrets.
|
||||
- **Tier (b) — Tenant credentials** (BYOM key, Proxmox token, SSH key, Git token, tenant registration token): via `SecretProvider` ONLY. Never env vars, never config files, never DB columns. The DB stores only a `SecretRef`.
|
||||
|
||||
This two-tier model is the documented exception to the PO's verbatim "no env vars" claim and will be cited in the M1 security review.
|
||||
|
||||
---
|
||||
|
||||
## User-Facing Surface (MVP/UX §1)
|
||||
|
||||
M1 ships ONE user-facing surface: the **Platform Lead admin dashboard** (browser, Next.js, at `/dashboard`). The chat UI is M3 — explicitly not in M1.
|
||||
|
||||
M1 dashboard surfaces (each is a pass/fail QA surface):
|
||||
|
||||
1. **SSO entry** (`/login`): "Sign in with SSO" button → WorkOS redirect → session → `/dashboard` redirect. (REQ-001)
|
||||
2. **Onboarding checklist** (`/dashboard`): first-tenant view shows the 5 onboarding steps with status badges (grey/green): Configure BYOM, Install Relay Agent, Register Target, Verify Green Status, Invite Team. (REQ-002)
|
||||
3. **BYOM config form** (`/dashboard/byom`): URL input + API key input + "Validate & Save" button. On save: green "Validated" badge with the test inference result, or red error panel with details. (REQ-006, REQ-007)
|
||||
4. **Relay Agent install instructions** (`/dashboard/relay`): per-tenant `curl|bash` install command with the tenant registration token embedded; supported OS list shown; "unsupported OS" callout. (REQ-010)
|
||||
5. **Targets list** (`/dashboard/targets`): one row per registered Relay Agent — hostname, OS, IP, agent version, health (green/yellow/red), last-seen, "View logs" link. (REQ-012, REQ-014)
|
||||
6. **Target detail** (`/dashboard/targets/<id>`): health badge, last 100 log lines (streamed via WebSocket fan-out), registration metadata. (REQ-014)
|
||||
7. **Team / RBAC** (`/dashboard/team`): list members + role; "Invite" form (email → single-use link); role change dropdown (Admin/Operator/Viewer). (REQ-003, REQ-004)
|
||||
8. **Audit export** (`/dashboard/audit`): admin-only "Download audit log (CSV)" button — no query UI in MVP (spec §2.2). (REQ-038)
|
||||
|
||||
Non-UI surfaces: the Relay Agent install script (`curl|bash`), the Go binary, the systemd unit. These are operator-facing artifacts, documented in the install guide.
|
||||
|
||||
## Happy Path (MVP/UX §2)
|
||||
|
||||
End-to-end M1 scenario (maps to spec Journey 2 steps 1–4 + 7 + 9), written before EXECUTE:
|
||||
|
||||
> **Given** a fresh CoreCI Chat deployment with Postgres + WorkOS + AWS Secrets Manager configured,
|
||||
> **when** a Platform Lead completes the M1 onboarding,
|
||||
> **then** the M1 acceptance gate (spec §2.3) passes: SSO signup → BYOM green validation → Relay Agent install on a target Linux host → target registered → green status in the admin dashboard. Audit logging, RLS, and secret manager are operational.
|
||||
|
||||
BDD steps:
|
||||
1. **Given** an unauthenticated visitor, **when** they hit `/login` and complete WorkOS SSO, **then** a tenant is provisioned (first signup), they are assigned Admin, and `/dashboard` loads with the 5-step onboarding checklist (all grey). (REQ-001, REQ-002)
|
||||
2. **Given** the Admin on `/dashboard/byom`, **when** they submit a valid BYOM URL + API key and click "Validate & Save", **then** the API key is stored in the secret manager (not the DB), a test inference call to `/v1/chat/completions` returns 200, the endpoint row is inserted under RLS, the checklist step turns green, and an audit entry is appended. (REQ-006, REQ-007, REQ-038, REQ-039, REQ-040)
|
||||
3. **Given** the Admin on `/dashboard/relay`, **when** they copy the `curl|bash` command and run it on an Ubuntu 24.04 host, **then** the install script detects Ubuntu, downloads the Go binary, verifies the checksum, writes the systemd unit, writes the tenant token to `/etc/coreci/relay.env`, enables + starts the service, and exits 0. (REQ-010)
|
||||
4. **Given** the systemd service is running, **when** the Relay Agent starts, **then** it opens an outbound WebSocket to the SaaS within 60s, registers (tenant/target/hostname/Ubuntu 24.04/IP/version), the control plane inserts a `targets` row under RLS + appends an audit entry, and the dashboard `/targets` list shows the new row. (REQ-011, REQ-012, REQ-038, REQ-039)
|
||||
5. **Given** the Relay Agent is registered, **when** it sends a heartbeat every 30s, **then** `last_seen` updates and the dashboard health badge is **green**. (REQ-013, REQ-014)
|
||||
6. **Given** the Admin on `/dashboard/team`, **when** they invite `ops@example.com` as Operator, **then** WorkOS sends a single-use acceptance link; when the invitee accepts and hits the API, RBAC enforces Operator permissions (e.g., cannot edit BYOM). (REQ-003, REQ-004, REQ-005)
|
||||
7. **Given** a second tenant T2 exists, **when** T1's user issues any DB query, **then** RLS returns zero T2 rows (cross-tenant pen test passes). (REQ-039)
|
||||
8. **Given** the unsupported-OS case, **when** the install script runs on Fedora, **then** it exits non-zero with a message listing Ubuntu 24.04 LTS and Debian 12+. (REQ-010, Edge 16)
|
||||
|
||||
This Happy Path is the M1 demo recording required by the M1 review.
|
||||
|
||||
## UX Acceptance Criteria (MVP/UX §3)
|
||||
|
||||
1. **SSO works in <3 clicks** from `/login` to `/dashboard` for a returning user (REQ-001).
|
||||
2. **BYOM validation feedback is synchronous** — the "Validate & Save" button shows a spinner and resolves in <10s with a green/red result (REQ-007).
|
||||
3. **Dashboard health badge turns green within 90s** of the Relay Agent's first heartbeat (REQ-013, REQ-014).
|
||||
4. **Install command is copy-pasteable** — the `/dashboard/relay` page shows one `curl -fsSL <url> | sh` line with the tenant token already embedded; no manual editing required (REQ-010).
|
||||
5. **RBAC is enforced on the very next API call** after a role change — the dashboard re-fetches and the new permissions apply immediately (REQ-004, REQ-005).
|
||||
6. **Cross-tenant isolation is verifiable** — the M1 pen-test script demonstrates zero leakage across two tenants (REQ-039).
|
||||
7. **Audit log is append-only** — a test that attempts `UPDATE`/`DELETE` on `audit_log` as the app role fails with a permission error (REQ-038).
|
||||
8. **Secrets are never in the DB** — a test that scans `byom_endpoints` and all tenant-scoped tables for plaintext credentials passes (returns zero matches); secrets are resolvable only via `SecretProvider.get` (REQ-040).
|
||||
9. **Unsupported OS aborts cleanly** — running the install script on an unsupported OS exits non-zero with an actionable message (REQ-010, Edge 16).
|
||||
|
||||
---
|
||||
|
||||
## Wave A — Foundations (Phase 1)
|
||||
|
||||
**Goal:** Monorepo scaffold + Postgres schema with RLS + append-only hash-chain audit + SecretProvider interface + Trigger.dev bootstrap. No HTTP routes yet.
|
||||
**Depends on:** Phase 0.
|
||||
**REQs covered:** REQ-038, REQ-039, REQ-040.
|
||||
**Personas:** backend-engineer, data-engineer, security-engineer (sign-off).
|
||||
|
||||
### Tasks
|
||||
1. **Scaffold monorepo** (backend-engineer): pnpm workspace; `apps/control-plane` (Next.js App Router + TS), `apps/relay-agent` (Go module, empty for now), `apps/dashboard` (part of control-plane for M1), `packages/{db,auth,audit,secrets,config,runtime}`. `tsconfig.json` base + per-package extends. `package.json` scripts: `lint`, `typecheck`, `test` (vitest), `migrate`. `turbo.json` or pnpm `--filter` orchestration. `.gitignore` additions (`.secrets/`, `node_modules`, `dist`, `.next`).
|
||||
2. **Postgres schema + migrations** (data-engineer): `packages/db/migrations/0001_init.sql` — `tenants`, `users`, `tenant_memberships (user_id, tenant_id, role)`, `targets`, `byom_endpoints (tenant_id, url, secret_ref, validated)`, `invitations`, `audit_log (id BIGSERIAL, tenant_id, prev_hash, curr_hash, payload JSONB, created_at, user_id, target_id, correlation_id, event_type)`, `runtime_health (id BIGSERIAL, component, status, payload JSONB, created_at)` (NOT tenant-scoped; NOT an audit table — see G-002). All tenant-scoped tables carry `tenant_id UUID NOT NULL`.
|
||||
3. **RLS policies** (data-engineer + security-engineer): per-table policy `USING (tenant_id = current_setting('app.tenant_id')::uuid)`. `packages/db/rls.sql` run by the migrator. App role `coreci_app` with INSERT/SELECT only; `REVOKE UPDATE, DELETE ON audit_log FROM coreci_app`. `migrator` role with BYPASSRLS for migrations only.
|
||||
4. **`withTenant` helper** (data-engineer): `packages/db/withTenant.ts` — opens a transaction, `SET LOCAL app.tenant_id = $1`, runs the callback, commits. Throws if called outside a transaction. Unit test: a query outside `withTenant` returns zero tenant-scoped rows.
|
||||
5. **Audit writer** (backend-engineer + security-engineer): `packages/audit/writer.ts` — `append(event)` computes `curr_hash = sha256(prev_hash || canonical(payload))`, INSERTs inside the caller's transaction. Constraint trigger rejects a forged `prev_hash`. `AuditWriteHaltError` thrown on failure → caller's transaction rolls back. Unit test: append 3 entries, verify the chain; attempt UPDATE/DELETE → permission denied. **[G-006] Known M1-acceptable limit:** the per-tenant hash-chain serializes concurrent audit writes within one tenant (two simultaneous appends read the same `prev_hash`; the second INSERT fails the constraint and rolls back). Acceptable for M1 volume (onboarding + dashboard). **M3 mitigation:** `pg_advisory_xact_lock(hashtext(tenantId))` before the INSERT, or a per-tenant sequence for `prev_hash` ordering. Documented here so M3 is not a surprise.
|
||||
6. **`SecretProvider` interface + impls** (backend-engineer + security-engineer): `packages/secrets/provider.ts` (interface), `packages/secrets/aws-sm.ts` (`@aws-sdk/client-secrets-manager`), `packages/secrets/local-encrypted.ts` (AES-256-GCM, master key from `SECRET_MASTER_KEY_DEV`). `SecretValue` type with `[REDACTED]` toString. Unit tests for both impls.
|
||||
7. **Trigger.dev bootstrap** (backend-engineer): `packages/runtime/index.ts` — initializes the Trigger.dev client from config; registers a `runtimeHealthCheck` task that runs every 5 min and writes a row to `runtime_health` (NOT `audit_log` — see G-002; REQ-038's audit store is for business events only: prompts, tool calls, SSH commands, responses). No chat tasks. **[G-003] Pre-investment for M3:** shipping the runtime now means M3 chat orchestration plugs in without a runtime bootstrap rewrite; the 5-min health tick proves the runtime is wired without polluting the audit store.
|
||||
8. **Cross-tenant pen-test scaffold** (security-engineer): `tests/pen/cross-tenant.test.ts` — two tenants, attempt to read T2 as T1, assert zero rows. (Full pen test runs at M1 review.)
|
||||
9. **Tenant registration token contract** (backend-engineer + security-engineer): `packages/secrets/relay-token.ts` — defines the token issued by `POST /api/relay/issue-token` (Wave D Task 1) and consumed by the Go Relay Agent (Wave D Task 3). **[G-005] Contract (locked here so Wave B's auth middleware and Wave D's WS server + agent parallelize without blocking):** token is a signed JWT (HS256, key from `SECRET_MASTER_KEY_DEV` in dev / KMS-derived in prod — NOT a tenant secret, it's a platform bootstrap signing key in tier (a) of the credential taxonomy), claims `{tenantId, scope: "relay.register", iat, exp}`, lifetime 24h, refreshable. Stored as a `SecretRef` for re-issuance. The endpoint itself is built in Wave D; the contract + signing helper live here so neither wave blocks.
|
||||
|
||||
### Must-haves (verify before ship)
|
||||
- [ ] `pnpm typecheck` + `pnpm lint` + `pnpm test` green.
|
||||
- [ ] Migrations run clean against a fresh Postgres 16.
|
||||
- [ ] `withTenant` test: query outside wrapper returns zero tenant rows.
|
||||
- [ ] Audit chain test: 3 appends verify; UPDATE/DELETE rejected.
|
||||
- [ ] `SecretProvider` test: put/get round-trip for both impls; `toString()` returns `[REDACTED]`.
|
||||
- [ ] Cross-tenant pen-test scaffold compiles + runs (T1 sees zero T2 rows).
|
||||
- [ ] Trigger.dev health task writes a `runtime_health` row on a 5-min tick (NOT `audit_log`). [G-002]
|
||||
- [ ] `runtime_health` table is NOT tenant-scoped (no RLS); `audit_log` IS tenant-scoped.
|
||||
- [ ] Relay token contract: JWT signed, claims `{tenantId, scope: "relay.register"}`, 24h lifetime; signing helper + verify helper unit-tested. [G-005]
|
||||
- [ ] Audit concurrent-write limit documented in code comments (per-tenant serialization; M3 mitigation noted). [G-006]
|
||||
- [ ] Code coverage ≥ 80% on `packages/db`, `packages/audit`, `packages/secrets`, `packages/runtime`.
|
||||
|
||||
## Wave B — Identity & RBAC (Phase 2)
|
||||
|
||||
**Goal:** WorkOS SSO + session + tenant resolution + RBAC at the API gateway from the first endpoint. First HTTP routes.
|
||||
**Depends on:** Wave A (withTenant, audit). Wave B does NOT own the relay token-issuance endpoint (that's Wave D Task 1); the token contract is defined in Wave A Task 9 so B and D parallelize. [G-005]
|
||||
**REQs covered:** REQ-001, REQ-002, REQ-003, REQ-004, REQ-005.
|
||||
**Personas:** backend-engineer, frontend-engineer, security-engineer (sign-off).
|
||||
|
||||
### Tasks
|
||||
1. **WorkOS SSO route** (backend-engineer): `/api/auth/login` → WorkOS hosted SSO redirect; `/api/auth/callback` → code exchange → session. Session stored httpOnly cookie + `sessions` table row (tenant_id, user_id, role).
|
||||
2. **Tenant provisioning** (backend-engineer): on first signup, if the WorkOS `organizationId` has no tenant, INSERT tenant + tenant_membership(role=Admin) inside `withTenant`. Audit append (provision event).
|
||||
3. **Tenant resolution middleware** (backend-engineer): `packages/auth/middleware.ts` — reads cookie, loads session, `SET app.tenant_id` via `withTenant`, attaches `req.user = {id, tenantId, role}`.
|
||||
4. **RBAC enforcement** (backend-engineer + security-engineer): `packages/auth/rbac.ts` — role → route permission map (Admin: all; Operator: read + chat; Viewer: read). Applied at the API gateway. First protected endpoint: `GET /api/me` (returns user + tenant). Test: Viewer calling `POST /api/byom` → 403.
|
||||
5. **Invitations** (backend-engineer): `POST /api/invitations` (Admin only) → WorkOS invitation API → single-use link emailed. `POST /api/invitations/accept` → creates tenant_membership. Edge 15: bounce webhook → mark invalid.
|
||||
6. **Role assignment** (backend-engineer): `PATCH /api/team/<userId>` (Admin only) → updates `tenant_memberships.role`. Enforced on next API call.
|
||||
7. **Login + dashboard shell** (frontend-engineer): `/login` page (SSO button), `/dashboard` shell with the 5-step onboarding checklist (all grey — steps light up as later waves ship). `/dashboard/team` page (invite + role UI).
|
||||
|
||||
### Must-haves
|
||||
- [ ] SSO round-trip works (test with WorkOS sandbox).
|
||||
- [ ] First signup provisions tenant + Admin role; audit entry written.
|
||||
- [ ] RBAC: Viewer → 403 on Admin-only route; Operator → 200 on read.
|
||||
- [ ] Role change enforced on the next API call (test).
|
||||
- [ ] Invitation email sent (WorkOS); acceptance creates membership; bounce marks invalid.
|
||||
- [ ] Audit entry appended for every auth event (login, provision, invite, role change).
|
||||
- [ ] Coverage ≥ 80% on `packages/auth`.
|
||||
|
||||
## Wave C — BYOM (Phase 3)
|
||||
|
||||
**Goal:** BYOM endpoint registry + validate-on-save + routing shim (OpenAI-compatible). No chat/orchestration in M1 — the routing shim is a proxy contract + REQ-009 reject path.
|
||||
**Depends on:** Wave A (secrets, audit, withTenant), Wave B (RBAC).
|
||||
**REQs covered:** REQ-006, REQ-007, REQ-008, REQ-009.
|
||||
**Personas:** backend-engineer, frontend-engineer, security-engineer (sign-off).
|
||||
|
||||
### Tasks
|
||||
1. **BYOM endpoints table + routes** (backend-engineer): `POST /api/byom` (Admin only) — takes `{url, apiKey}`; `secrets.put(tenantId, "byom", apiKey)` → `secret_ref`; INSERT `byom_endpoints (url, secret_ref, validated=false)` under `withTenant`; audit append (config event).
|
||||
2. **Validate-on-save** (backend-engineer): after INSERT, call the BYOM validator: `POST <url>/v1/chat/completions` with a trivial test prompt; on 200 → `UPDATE ... validated=true`, audit append (validation ok), return green; on failure → DELETE the row (or mark invalid), audit append (validation fail), return error details (Edge 11). All inside one transaction per the audit-halt rule.
|
||||
3. **Routing shim** (backend-engineer): `packages/byom/router.ts` — `routeInference(tenantId, payload)` resolves the tenant's validated endpoint via `withTenant`, fetches the key via `secrets.get`, POSTs to `/v1/chat/completions`. M1 exposes a test endpoint `POST /api/byom/test-inference` (Admin only) that calls the shim and returns the raw response. **[G-001] Scope note:** this endpoint is a plan-time proxy to satisfy REQ-008 ("100% of LLM inference calls routed to BYOM, verified via outbound traffic log") in the absence of M3 chat orchestration — there is no other driver of inference in M1. It is marked for M3 deprecation once the chat orchestrator (REQ-033) drives real inference. PO-acknowledged (non-blocking). The outbound traffic log test for REQ-008 runs against this endpoint's egress.
|
||||
4. **REQ-009 reject path** (backend-engineer + security-engineer): if no validated BYOM endpoint exists or the endpoint is unreachable, `routeInference` throws `ByomUnconfiguredError` / `ByomUnreachableError` → API returns a clear actionable error. Test: with no endpoint configured, calling `/api/byom/test-inference` → 400 with actionable message; with an unreachable URL → 503 with actionable message.
|
||||
5. **BYOM dashboard page** (frontend-engineer): `/dashboard/byom` — URL + API key form, "Validate & Save" button, green/red result panel, current endpoint status. `/dashboard` checklist step 1 turns green on validated save.
|
||||
|
||||
### Must-haves
|
||||
- [ ] Save stores key in secret manager; DB holds only `secret_ref` (test: scan tables for plaintext keys → zero).
|
||||
- [ ] Validation POST hits the configured endpoint; green/red result accurate.
|
||||
- [ ] Routing shim sends 100% of test-inference calls to the configured endpoint (outbound traffic log test).
|
||||
- [ ] REQ-009: unconfigured → 400 actionable; unreachable → 503 actionable. No inference attempted.
|
||||
- [ ] Audit entries for config + validation events.
|
||||
- [ ] Coverage ≥ 80% on `packages/byom` + BYOM routes.
|
||||
|
||||
## Wave D — Relay Agent (Phase 4)
|
||||
|
||||
**Goal:** Modular install script + Go binary + WebSocket registration + heartbeat + auto-reconnect + SSH whitelist hook (no SSH adapter yet).
|
||||
**Depends on:** Wave A (audit, withTenant, relay-token contract from Wave A Task 9 [G-005]), Wave B (auth middleware).
|
||||
**REQs covered:** REQ-010, REQ-011, REQ-012, REQ-013, REQ-026 (partial — whitelist file + hook only; see G-008).
|
||||
**Personas:** go-engineer (phase-specific), backend-engineer (control-plane WS server), security-engineer (sign-off on whitelist hook).
|
||||
|
||||
### Scope note — REQ-026 partial coverage in M1 [G-008]
|
||||
M1 ships REQ-026 **partially**: the whitelist file format + the `CheckCommand` enforcement hook + unit + shadow-exec tests. The spec §4 REQ-026 acceptance criteria (customer generates an SSH keypair; the Relay Agent receives a tool call; only whitelisted commands execute; non-whitelisted rejected + audited) describe **end-to-end SSH-key-auth + tool-call-driven execution**, which is **M2** work (the SSH adapter plugs into the hook shipped here). M1's must-have is the hook + whitelist + tests, NOT end-to-end SSH execution. This matches the REQUIREMENTS.md traceability (REQ-026 deferred to M2; whitelist format + hook ship M1 Wave D).
|
||||
|
||||
### Tasks
|
||||
1. **Tenant registration token endpoint** (backend-engineer): `POST /api/relay/issue-token` (Admin only) — issues the per-tenant registration token defined in Wave A Task 9 [G-005] (signed JWT, `scope: "relay.register"`, 24h). Stores a re-issuance ref via `secrets.put`. The dashboard `/dashboard/relay` page shows the `curl|bash` command with the token embedded.
|
||||
2. **WS server** (backend-engineer): `apps/control-plane/api/relay/ws` — accepts outbound WebSocket, authenticates the tenant token (verify JWT from Wave A Task 9), on `register` message INSERTs `targets (tenant_id, hostname, os, os_version, ip, agent_version, last_seen)` under `withTenant`, audit append. Responds `{registered, targetId}`. Handles `ping` → updates `last_seen` → `pong`.
|
||||
3. **Go binary — WebSocket client** (go-engineer): `apps/relay-agent/main.go` — reads `CORECI_TENANT_TOKEN` + `CORECI_SAAS_URL` from `/etc/coreci/relay.env`, connects `wss://<saas>/api/relay/ws`, sends `register`, then `ping` every 30s. On disconnect: exponential backoff (1/2/4/8/16s), max 5 attempts → alert + keep trying every 60s. systemd `Restart=on-failure` for hard crashes.
|
||||
4. **Install script (modular)** (go-engineer): `scripts/install.sh` with separate functions `detect_os`, `install_binary`, `write_systemd_unit`, `register_target`, `main`. `detect_os` parses `/etc/os-release` (Ubuntu ≥ 24.04, Debian ≥ 12); else exit non-zero with the supported-OS list (Edge 16). `install_binary` downloads the Go binary for the detected arch, verifies SHA256, installs to `/usr/local/bin/coreci-relay-agent`. `write_systemd_unit` writes the unit + `daemon-reload` + `enable --now`. `register_target` writes `/etc/coreci/relay.env` with the tenant token. Idempotent (re-run upgrades).
|
||||
5. **apt fallback** (go-engineer): documented apt package path (same script, `--method=apt` flag). The apt package ships the same binary + unit. (Documented; primary path is curl|bash.)
|
||||
6. **SSH whitelist file + enforcement hook** (go-engineer + security-engineer): `/etc/coreci/ssh-whitelist.json` (versioned schema `{"version": 1, "commands": [...], "arguments": {"deny": [...]}}`; fixed list: cat, ls, systemctl status, journalctl, df, du, ps, top, ss, netstat, ip, uptime, uname, free, who, w, last, dmesg, lscpu, lspci, lsblk, mount, findmnt, hostname, "ip addr", "ip route", "ss -tlnp"; argument deny list: -exec, -execdir, --exec, |, >, >>, &, ;, &&, ||). `apps/relay-agent/whitelist/check.go` — `CheckCommand(cmd string) error` parses the command, checks base + args, returns error if rejected. Unit tests: every whitelist command passes; `rm -rf`, `find -exec`, `cat /etc/shadow | nc` all rejected. **[G-004] Contract lock:** the `CheckCommand(cmd string) error` signature + the whitelist JSON schema are the **M2 SSH adapter contract**. M2 must consume them as-shipped; any signature change requires a documented migration with a compatibility shim. **[G-003] Pre-investment for M2:** shipping the hook now means M2's SSH adapter plugs in without reworking the enforcement boundary; the cost is justified by avoiding the "retrofit = rewrite" risk the PO flagged. **[G-007] Shadow `exec.Cmd` integration test:** a Go test that constructs `exec.Command("systemctl", "status", "nginx")` from a parsed whitelist command and asserts `CheckCommand` accepts it (positive), plus a negative test that `exec.Command("rm", "-rf", "/")` is rejected by `CheckCommand` *before* the Cmd would be started — proving the hook composes with `os/exec` without a live SSH server. No SSH execution path in M1 — M2 plugs the adapter into `CheckCommand`.
|
||||
|
||||
### Must-haves
|
||||
- [ ] Install script on Ubuntu 24.04 succeeds (exit 0, systemd service running, agent connected within 60s).
|
||||
- [ ] Install script on Debian 12+ succeeds (same).
|
||||
- [ ] **[G-009] Install script on ≥2 unsupported OSes aborts cleanly** with the supported-OS list (Edge 16): at minimum one non-Debian-family (e.g., Fedora or Alpine) AND one wrong-version Debian-family (e.g., Ubuntu 22.04 or Debian 11). Single-OS "unsupported" is not sufficient evidence.
|
||||
- [ ] Re-running the script upgrades, does not fail.
|
||||
- [ ] Relay Agent registers with full metadata (tenant/target/hostname/OS/IP/version); audit entry written.
|
||||
- [ ] Heartbeat updates `last_seen`; dashboard can read it (Wave E surfaces this).
|
||||
- [ ] Auto-reconnect: kill the WS server, agent retries with backoff, max 5 → alert; restart server → agent reconnects.
|
||||
- [ ] `CheckCommand`: every whitelist command passes; every deny-list case rejected. Coverage 100% on the whitelist module.
|
||||
- [ ] **[G-007] Shadow `exec.Cmd` test:** positive (`systemctl status nginx` accepted, composes to `exec.Command`) + negative (`rm -rf /` rejected pre-start) both pass.
|
||||
- [ ] Whitelist file format is versioned (JSON `{"version": 1, ...}`). [G-004]
|
||||
- [ ] `CheckCommand(cmd string) error` signature + whitelist JSON schema documented as the M2 contract. [G-004]
|
||||
|
||||
## Wave E — Dashboard surfacing (Phase 5)
|
||||
|
||||
**Goal:** Dashboard shows Relay Agent health (green/yellow/red), target hostname, last 100 log lines, per-tenant view under RLS. M1 gate demo path complete.
|
||||
**Depends on:** Wave B (auth, dashboard shell), Wave D (Relay Agent + WS server).
|
||||
**REQs covered:** REQ-014.
|
||||
**Personas:** frontend-engineer, backend-engineer.
|
||||
|
||||
### Tasks
|
||||
1. **Status fan-out WebSocket** (backend-engineer): `apps/control-plane/api/relay/status` — Admin-authenticated WS that pushes target status changes (health, last_seen, log lines) to connected dashboard clients. Status computed from `targets.last_seen` (green < 60s, yellow < 5min, red > 5min).
|
||||
2. **Targets list page** (frontend-engineer): `/dashboard/targets` — server component fetches targets via the API (under RLS), renders rows (hostname, OS, IP, version, health badge, last-seen, "View logs"). Client component subscribes to the status WS for live updates.
|
||||
3. **Target detail page** (frontend-engineer): `/dashboard/targets/<id>` — health badge, registration metadata, last 100 log lines (streamed). Under RLS (T1 admin cannot view T2's target — pen-test asserts).
|
||||
4. **Onboarding checklist completion** (frontend-engineer): `/dashboard` checklist step "Verify Green Status" turns green when at least one target is green. The M1 demo path (SSO → BYOM green → install → register → green dashboard) is end-to-end walkable.
|
||||
5. **Audit export** (backend-engineer + frontend-engineer): `/dashboard/audit` — admin-only "Download CSV" button. No query UI (spec §2.2). CSV scoped to the tenant under RLS.
|
||||
|
||||
### Must-haves
|
||||
- [ ] Dashboard shows a registered target as green within 90s of first heartbeat.
|
||||
- [ ] Killing the agent → badge turns yellow then red as `last_seen` ages.
|
||||
- [ ] Restarting the agent → badge turns green again.
|
||||
- [ ] T1 admin cannot view T2's target (RLS pen-test asserts).
|
||||
- [ ] Last 100 log lines render on the target detail page.
|
||||
- [ ] Audit CSV export is tenant-scoped (no cross-tenant rows).
|
||||
- [ ] The full M1 Happy Path (UX §2) is walkable end-to-end.
|
||||
|
||||
---
|
||||
|
||||
## Final Phase — Review + Ship (Phase 6)
|
||||
|
||||
**Goal:** Multi-persona code review + project health audit + milestone ship (v0.1.0 release, merge to main).
|
||||
**REQs covered:** all M1 (sign-off).
|
||||
|
||||
### Tasks
|
||||
1. **Code review** (lead-developer + all personas): review all changes in `milestone/v0.1-bootstrap` since `main`. Auto-apply P0 fixes; flag P1+ for post-hoc.
|
||||
2. **Audit** (lead-developer): reconstruction test (git log matches `.ciagent/` files); file/branch/commit discipline; cross-tenant pen test runs green; install script runs on the 3 OS cases (Ubuntu 24.04 pass, Debian 12+ pass, unsupported abort).
|
||||
3. **M1 acceptance gate verification** (lead-developer): spec §2.3 — Platform Lead can SSO → BYOM green → install → register → green dashboard. Audit/RLS/secrets operational.
|
||||
4. **Milestone ship**: tag `v0.0.7` (final phase patch = v0.1 milestone release); merge `phase/06` → `milestone/v0.1-bootstrap` → `main`; create Gitea release with full milestone summary; build + upload Relay Agent binaries (linux amd64/arm64) + install script + apt package.
|
||||
5. **Complete**: mark all M1 REQs complete in REQUIREMENTS.md; mark milestone complete in ROADMAP.md; clear checkpoint.
|
||||
|
||||
### M1 review deliverables (per Sarah's kickoff)
|
||||
1. Per-REQ pass/fail test report with evidence (17 REQs).
|
||||
2. Demo recording: fresh tenant → SSO → BYOM green → install → register → green dashboard.
|
||||
3. Cross-tenant isolation pen test result (zero leakage).
|
||||
4. Install script logs: Ubuntu 24.04 (pass), Debian 12+ (pass), unsupported OS (clean abort).
|
||||
|
||||
---
|
||||
|
||||
## Wave ordering & parallelism
|
||||
|
||||
```
|
||||
Phase 0 (this plan) ──▶ Wave A (foundations)
|
||||
│
|
||||
├──▶ Wave B (identity/RBAC) ──▶ Wave E (dashboard) ──▶ Final
|
||||
│ ▲
|
||||
└──▶ Wave C (BYOM) ─────────────────┤
|
||||
│
|
||||
Wave D (relay agent) ─────────────┘
|
||||
```
|
||||
|
||||
- A must complete first (B, C, D all depend on `withTenant` + `audit` + `secrets`).
|
||||
- B and C can run in parallel after A (different territories; both depend on A only).
|
||||
- D can run in parallel after A (independent of B/C; the WS server in D needs B's auth token-issuance endpoint — coordinate the contract in the plan, then D's WS server + B's token endpoint can land in the same wave window).
|
||||
- E depends on B (dashboard shell + auth) and D (agent + WS server).
|
||||
- Final depends on all.
|
||||
|
||||
## Test strategy
|
||||
|
||||
- Unit: vitest in `packages/*` + `apps/control-plane`; Go `testing` in `apps/relay-agent`.
|
||||
- Integration: a Postgres 16 container in CI; `withTenant` + audit + RLS + secrets tests against it.
|
||||
- Pen test: `tests/pen/cross-tenant.test.ts` runs at Wave A (scaffold) and Final (full).
|
||||
- Install test: CI matrix runs `scripts/install.sh` on Ubuntu 24.04, Debian 12, Fedora (expects abort) containers.
|
||||
- Coverage gate: ≥ 80% on new modules (spec §6).
|
||||
- Lint + typecheck: `pnpm lint` + `pnpm typecheck` must be green before any wave ships.
|
||||
@@ -0,0 +1,77 @@
|
||||
# coreci-chat
|
||||
|
||||
## What This Is
|
||||
|
||||
A browser-based chat interface where mid-market enterprise IT operators ask natural-language questions about their infrastructure (Proxmox VE 7.x/8.x, SSH/Linux servers on Ubuntu 24.04 LTS / Debian 12+, GitHub, self-hosted Gitea) and receive coherent, evidence-backed diagnostic answers within 5 minutes (p95). All LLM inference is routed through a customer-provided model endpoint (BYOM) — CoreCI Chat never hosts inference. CoreCI Chat is a companion product to CoreCI (the owner's existing CI/CD platform). The v0.1 wedge is deliberately conservative: investigate and diagnose; remediation is deferred to v1.1.
|
||||
|
||||
The Relay Agent is a lightweight systemd service installed on customer Linux hosts, communicating to the CoreCI Chat SaaS over an outbound-only WebSocket. Read-only enforcement is applied at both the MCP gateway (control plane) and the Relay Agent (SSH command whitelist). Every prompt, tool call, SSH command, and response is written to an immutable audit log. All tenant-scoped data is protected by Postgres Row-Level Security. All credentials live in a centralized secret manager (AWS Secrets Manager in prod, encrypted-local fallback in dev).
|
||||
|
||||
**One-sentence goal:** A browser-based chat interface where mid-market enterprise IT operators ask natural-language questions about their infrastructure (Proxmox, SSH/Linux servers, GitHub, Gitea) and receive evidence-backed diagnostic answers in under 5 minutes — with all LLM inference routed through customer-provided model endpoints (BYOM).
|
||||
|
||||
## Milestone
|
||||
|
||||
**v0.1 — Read-Only Diagnostic MVP.** Milestone branch: `milestone/v0.1-bootstrap`. Tags run on the v0.0.x patch line (no prior minor exists); phase 0 seeds `v0.0.1`, each execution phase ships a progressive patch, and the final phase's patch (`v0.0.(N+1)`) IS the milestone release. Milestone type: **Feature** (at least one `feat:` phase).
|
||||
|
||||
## Authoritative Spec
|
||||
|
||||
`/home/opencode/coreci-chat/.ciagent/steer-v0.1-spec.md` — CoreCI Chat v0.1 Engineering Specification v1.1 (FINAL), authored by Sarah Chen (Product Owner), locked 2026-08-24. Anchor docs: CoreCI Chat Vision v1.0, locked Phase 2 scope decisions. All 44 REQs locked; Section 7 decisions locked.
|
||||
|
||||
## M1 Scope (current milestone)
|
||||
|
||||
REQ-001 → REQ-014, REQ-038, REQ-039, REQ-040 (17 REQs total). M1 acceptance gate (spec §2.3): Platform Lead can sign up via SSO, configure a BYOM endpoint with green validation, deploy Relay Agent via install script on at least one target Linux host, register the target, and see green status in the admin dashboard. Audit logging, RLS, and secret manager are operational.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Validated (Phase 0 init)
|
||||
- ✓ Initialize a git repository at `~/coreci-chat` with branch hierarchy `main → milestone/v0.1-bootstrap → phase/00-pre-execution`
|
||||
- ✓ Write `.ciagent/config.json` with autonomy `full`, release target Gitea `coreci/coreci-chat`, secrets hygiene
|
||||
- ✓ Persist `GITEA_TOKEN` to `.ciagent/.env.secrets` (mode 0600); `.env*` in `.gitignore`
|
||||
|
||||
### Active (Phase 0 — this run)
|
||||
- [ ] SPECIFY: parse spec, rewrite PROJECT.md / REQUIREMENTS.md / ARCHITECTURE.md with real content
|
||||
- [ ] CLARIFY: resolve 5 architectural decisions (approved by PO during kickoff)
|
||||
- [ ] RESEARCH: produce research artifacts + PERSONAS.md under `.ciagent/`
|
||||
- [ ] PLAN: vertical-slice wave plans referencing REQ IDs, MVP/UX sections
|
||||
- [ ] GRILL: adversarial plan review, binding verdicts
|
||||
|
||||
### Out of Scope (v0.1 — do not build)
|
||||
See `.ciagent/REQUIREMENTS.md` § Out of Scope and spec §2.2. Highlights: write actions, hosted LLM inference, Kubernetes/ArgoCD/Helm, Slack/Teams/CLI/mobile, approval-gated remediation, RAG, SOC 2 Type 1 final cert, custom RBAC roles, BYOK, multi-region, Windows. Anything not in the spec is a spec-time scope question — never silently added.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Branch discipline:** all writes on `phase/NN-*` branches, never `main`. `---ci---` blocks in every commit.
|
||||
- **Read-only by default:** 100% of write-action requests rejected at MCP gateway (HTTP 403) and at Relay Agent (SSH whitelist). No state mutation is possible in v0.1.
|
||||
- **BYOM mandatory:** 100% of LLM inference outbound to the customer-configured endpoint; zero inference from CoreCI Chat infra.
|
||||
- **Multi-tenancy isolation:** Postgres RLS on all tenant-scoped tables; cross-tenant queries return empty. Quarterly pen test, zero leakage.
|
||||
- **Audit immutability:** write-once store, 100% capture, zero deletes; write failure halts the operation (no silent drops).
|
||||
- **Secret handling:** every credential via the centralized secret manager from the first secret. No env vars, no config files, no DB columns. Never logged in plaintext.
|
||||
- **RBAC enforcement:** at the API gateway from the first endpoint. No "auth later" stubs.
|
||||
- **SSH whitelist:** fixed whitelist shipped with the Relay Agent in M1; M2 plugs the SSH adapter into the existing enforcement hook. Customer extension deferred to v1.1 (signed config).
|
||||
- **Supported targets:** Ubuntu 24.04 LTS, Debian 12+ (install script aborts cleanly on unsupported OS with actionable error). Proxmox VE 7.x and 8.x.
|
||||
- **Relay Agent distribution:** install script (`curl|bash`) primary, apt package fallback. systemd service, outbound-only WebSocket, per-target registration.
|
||||
- **Async durable execution:** Trigger.dev.
|
||||
- **Identity:** WorkOS (SSO/SAML + SCIM).
|
||||
- **GRC:** Vanta (instrumentation in M3, not M1).
|
||||
- **Single region:** us-east-1 MVP. TLS 1.2+ in transit, AES-256 at rest.
|
||||
- **Performance NFRs:** <3s p95 time-to-first-token; <5 min p95 diagnostic completion (≤10 tool calls); ≥5 concurrent workflows per tenant; ≤20 tool calls per workflow; 60 req/min per user, 300 req/min per tenant (token bucket).
|
||||
- **Autonomy:** `full` — no HITL after clarify. Decision threshold 0.6. Escalation hooks: `[deploy, delete_data, merge_to_main]`.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
| Decision | Rationale | Source |
|
||||
|----------|-----------|--------|
|
||||
| Spec v1.1 locked, 44 REQs | Sarah Chen (PO) locked 2026-08-24 | spec §8 |
|
||||
| M1 = REQ-001..014 + 038/039/040 | spec §2.3 | spec §2.3 |
|
||||
| Trigger.dev durable runtime | Best TS DX, long-running workflows, MVP cost | spec §7 Q1 |
|
||||
| WorkOS IdP | Enterprise SAML/SSO/SCIM at mid-market price | spec §7 Q2 |
|
||||
| Vanta GRC | AWS-native ecosystem, M3 instrumentation | spec §7 Q3 |
|
||||
| Install script primary, apt fallback | Fastest to ship, flexible | spec §7 Q4 |
|
||||
| Fixed SSH whitelist, no customer extension in v0.1 | Security; signed-config extension in v1.1 | spec §7 Q5 |
|
||||
| PVEAuditor built-in role for Proxmox | Simpler setup, well-understood | spec §7 Q6 |
|
||||
| Gitea via SaaS-to-API exposure | Customer opens firewall; Relay-Agent variant deferred | spec §7 Q7 |
|
||||
| pgvector for v1.1 RAG | Co-located with primary DB (not v0.1) | spec §7 Q8 |
|
||||
| OpenAI-compatible BYOM contract for M1 | Most customer endpoints speak it; pluggable provider iface for Anthropic-native in M3 | CLARIFY (PO-approved default) |
|
||||
| Relay Agent in Go | Single static binary, ideal for curl\|bash + systemd + zero-runtime on Ubuntu/Debian | CLARIFY (PO-approved default) |
|
||||
| AWS Secrets Manager (prod) + local-encrypted (dev) behind `SecretProvider` interface | Spec §5 default us-east-1; interface enables CI/local without AWS | CLARIFY (PO-approved default) |
|
||||
| Postgres append-only table + hash-chain + REVOKE UPDATE/DELETE for audit (M1); S3 Object Lock WORM in M3 | Append-only from day one; cheap refactor to WORM later | CLARIFY (PO-approved default) |
|
||||
| Next.js (App Router) + TypeScript single SPA | Dashboard in M1, chat UI in M3, same app | CLARIFY (PO-approved default) |
|
||||
@@ -0,0 +1,116 @@
|
||||
# Requirements
|
||||
|
||||
Source: CoreCI Chat v0.1 Engineering Specification v1.1 (`.ciagent/steer-v0.1-spec.md`), Sarah Chen (PO), locked 2026-08-24.
|
||||
Milestone type: **Feature** (at least one `feat:` phase). Tags run on the v0.0.x patch line (no prior minor exists; phase 0 seeds `v0.0.1`).
|
||||
|
||||
## M1 Requirements (this milestone — REQ-001..014, 038, 039, 040)
|
||||
|
||||
All acceptance criteria verbatim from spec §4. Every REQ maps to Journey J1 and/or J2 (spec §3.2) and to at least one failure/edge path (spec §3.3).
|
||||
|
||||
### Identity & Access
|
||||
|
||||
- [ ] **REQ-001** (J2, High) Establish SSO session via identity provider — **Given** an unauthenticated user navigates to CoreCI Chat, **when** they complete SSO flow via the configured IdP, **then** a session is established and they are redirected to the dashboard. _(Edge 10: SSO provider down → error w/ retry, tenant creation blocked)_
|
||||
- [ ] **REQ-002** (J2, High) Provision tenant on first signup — **Given** a user completes signup for the first time, **when** tenant creation runs, **then** a new tenant is created, the user is assigned Admin role, and the admin dashboard loads.
|
||||
- [ ] **REQ-003** (J2, High) Invite users to tenant via email — **Given** an Admin submits an invitation, **when** the system processes the invite, **then** an email is sent to the invitee containing a single-use acceptance link. _(Edge 15: bounce → admin notified, invite invalidated)_
|
||||
- [ ] **REQ-004** (J2, High) Apply RBAC role to user — **Given** an Admin assigns a role (Admin/Operator/Viewer), **when** the assignment is saved, **then** the user's role is updated and enforced on the next API call.
|
||||
- [ ] **REQ-005** (J1, J2, High) Enforce RBAC at API gateway — **Given** a user with role X calls endpoint Y, **when** the role check runs, **then** the request is allowed iff X has permission for Y. _(Critical-path pattern: set at API gateway from first endpoint, no auth-later stubs.)_
|
||||
|
||||
### BYOM (Bring Your Own Model)
|
||||
|
||||
- [ ] **REQ-006** (J2, High) Configure BYOM endpoint (URL + API key) — **Given** an Admin submits endpoint URL and API key, **when** the form is saved, **then** the API key is stored in the secret manager and the URL is validated.
|
||||
- [ ] **REQ-007** (J2, High) Validate BYOM endpoint connectivity on save — **Given** an Admin submits a BYOM endpoint, **when** validation runs, **then** a test inference call is sent and the result is displayed as success or failure with error details. _(Edge 11: test fails → save blocked, errors surfaced)_
|
||||
- [ ] **REQ-008** (J1, J2, High) Route all LLM inference to configured BYOM endpoint — **Given** a user submits a prompt, **when** orchestration runs, **then** 100% of LLM inference calls are sent to the configured BYOM endpoint (verified via outbound traffic log).
|
||||
- [ ] **REQ-009** (J1, J2, High) Reject LLM request when BYOM is unconfigured or unreachable — **Given** no BYOM endpoint is configured or it is unreachable, **when** an Operator submits a prompt, **then** the request is rejected with a clear actionable error and no inference is attempted. _(Edge 1: unreachable mid-workflow → actionable error, halt.)_
|
||||
|
||||
### Relay Agent
|
||||
|
||||
- [ ] **REQ-010** (J2, High) Distribute Relay Agent as systemd service via install script — **Given** a Platform Lead runs the install script on a supported host (Ubuntu 24.04 LTS or Debian 12+), **when** execution completes, **then** a systemd service is installed, started, and configured for auto-start on boot; install aborts with clear error on unsupported OS. _(Edge 16: unsupported OS → clean abort, list supported versions. Critical-path: modular install — separate functions detect-OS/install-binary/write-systemd-unit/register-target.)_
|
||||
- [ ] **REQ-011** (J2, High) Establish outbound WebSocket from Relay Agent to SaaS — **Given** the Relay Agent systemd service is running with valid tenant credentials, **when** the service starts, **then** it establishes an outbound WebSocket to CoreCI Chat SaaS within 60 seconds.
|
||||
- [ ] **REQ-012** (J2, High) Register Relay Agent with tenant + target metadata — **Given** a Relay Agent connects, **when** registration completes, **then** tenant ID, target ID, hostname, OS name and version, IP address, and agent version are recorded. _(Edge 12: registration fails → error + troubleshooting link, dashboard red.)_
|
||||
- [ ] **REQ-013** (J2, High) Maintain heartbeat and auto-reconnect on WebSocket drop — **Given** the WebSocket drops, **when** 30 seconds elapse without reconnect, **then** the Relay Agent initiates reconnection with exponential backoff (max 5 attempts before alerting); systemd auto-restarts on hard failure. _(Edge 4: drops mid-investigation → auto-reconnect, resume from durable state.)_
|
||||
- [ ] **REQ-014** (J2, Med) Surface Relay Agent health and logs in admin dashboard — **Given** a Relay Agent is registered, **when** an Admin views the dashboard, **then** health status (green/yellow/red), target hostname, and the last 100 log lines are visible.
|
||||
|
||||
### Security & Compliance (M1)
|
||||
|
||||
- [ ] **REQ-038** (J1, J2, High) Log every prompt, tool call, SSH command, and response to immutable audit store — **Given** any of these events occur, **when** the audit log write runs, **then** the entry is written to a write-once store with tenant ID, user ID, target ID (for SSH), timestamp, and correlation ID; write failures halt the operation. _(Edge 7: write fails → halt + alert, no silent drops. Critical-path: append-only from day one; hash-chain pattern propagates to M2/M3.)_
|
||||
- [ ] **REQ-039** (J1, J2, High) Implement Row-Level Security on all tenant-scoped data — **Given** any database query is executed, **when** the query runs, **then** RLS policies enforce tenant scoping and cross-tenant queries return empty results. _(Critical-path: zero leakage verified by pen test.)_
|
||||
- [ ] **REQ-040** (J2, High) Store tenant credentials in centralized secret manager — **Given** any tenant credential (BYOM API key, Proxmox token, SSH key, Git token) is stored, **when** stored, **then** it resides in the centralized secret manager and never in plaintext in application logs or DB rows. _(Critical-path: every credential via secret manager from the first secret. No env vars, no config files, no DB columns. Ever.)_
|
||||
|
||||
## Deferred to M2 (REQ-015 → REQ-027 — MCP Layer & Day 1 Adapters)
|
||||
|
||||
Listed for traceability; NOT in M1 scope. M2 acceptance gate: all four Day 1 integrations respond to a test call, multi-target scoping functional, read-only enforcement verified at both gateway and Relay Agent layers (including SSH whitelist). See spec §2.3.
|
||||
|
||||
REQ-015 (abstract MCP tool schema), REQ-016 (route to adapter), REQ-017 (SSE stream), REQ-018 (read-only at gateway), REQ-019 (rate limit), REQ-020 (Proxmox adapter), REQ-021 (SSH/Linux adapter), REQ-022 (GitHub adapter), REQ-023 (Gitea adapter), REQ-024 (multi-target scope), REQ-025 (Proxmox auth PVEAuditor), REQ-026 (SSH key + whitelist enforcement at Relay Agent — **whitelist file format + enforcement hook ship in M1, adapter plugs in M2**), REQ-027 (Git scoped tokens).
|
||||
|
||||
## Deferred to M3 (REQ-028 → REQ-037, REQ-041 → REQ-044 — Chat, Orchestration, Hardening)
|
||||
|
||||
Listed for traceability; NOT in M1 scope. M3 acceptance gate: Operator asks a diagnostic question, sees streamed tool execution, receives a cited evidence-backed answer within 5 min p95; async workflows persist/rejoin; usage metered; SOC 2 controls instrumented; all v0.1 release gates pass.
|
||||
|
||||
REQ-028 (chat UI), REQ-029 (NL input), REQ-030 (streaming response + citations), REQ-031 (tool traces), REQ-032 (conversation history), REQ-033 (reason about tools), REQ-034 (multi-step workflows), REQ-035 (≤20 step limit), REQ-036 (durable execution), REQ-037 (rejoin workflow), REQ-041 (SOC2 posture page), REQ-042 (Vanta instrumentation), REQ-043 (usage metering), REQ-044 (usage dashboard).
|
||||
|
||||
## Out of Scope (v0.1 — do not build)
|
||||
|
||||
| Feature | Reason |
|
||||
|---------|--------|
|
||||
| Write actions (apply/delete/scale/restart/VM start-stop) | v1.1 (spec §2.2) |
|
||||
| Hosted LLM inference | Never — BYOM permanent (spec §2.2) |
|
||||
| Kubernetes / ArgoCD / Helm | Not planned (spec §2.2) |
|
||||
| Slack / Teams / Discord / CLI / mobile chat surfaces | v1.1+ / not planned (spec §2.2) |
|
||||
| Approval-gated remediation, Senior Approver persona | v1.1 (spec §2.2) |
|
||||
| RAG over historical incidents | v1.1 (spec §2.2) |
|
||||
| SOC 2 Type 1 final certification | Audit-in-progress posture only (spec §2.2) |
|
||||
| Custom RBAC roles beyond Admin/Operator/Viewer | v1.2+ (spec §2.2) |
|
||||
| BYOK / customer-managed encryption keys | v1.2+ (spec §2.2) |
|
||||
| Multi-region deployment | Single region MVP (spec §5) |
|
||||
| Windows server management | Not planned v1.x (spec §2.2) |
|
||||
| Fine-tuning, custom model deployments | Not planned (spec §2.2) |
|
||||
| Anything not in the spec | Flag as spec-time scope question; never silently add |
|
||||
|
||||
## Traceability
|
||||
|
||||
| Requirement | Milestone | Phase | Status |
|
||||
|-------------|-----------|-------|--------|
|
||||
| REQ-001 | M1 | Wave B | pending |
|
||||
| REQ-002 | M1 | Wave B | pending |
|
||||
| REQ-003 | M1 | Wave B | pending |
|
||||
| REQ-004 | M1 | Wave B | pending |
|
||||
| REQ-005 | M1 | Wave B | pending |
|
||||
| REQ-006 | M1 | Wave C | pending |
|
||||
| REQ-007 | M1 | Wave C | pending |
|
||||
| REQ-008 | M1 | Wave C | pending |
|
||||
| REQ-009 | M1 | Wave C | pending |
|
||||
| REQ-010 | M1 | Wave D | pending |
|
||||
| REQ-011 | M1 | Wave D | pending |
|
||||
| REQ-012 | M1 | Wave D | pending |
|
||||
| REQ-013 | M1 | Wave D | pending |
|
||||
| REQ-014 | M1 | Wave E | pending |
|
||||
| REQ-015 | M2 | — | deferred |
|
||||
| REQ-016 | M2 | — | deferred |
|
||||
| REQ-017 | M2 | — | deferred |
|
||||
| REQ-018 | M2 | — | deferred |
|
||||
| REQ-019 | M2 | — | deferred |
|
||||
| REQ-020 | M2 | — | deferred |
|
||||
| REQ-021 | M2 | — | deferred (whitelist hook ships M1 Wave D) |
|
||||
| REQ-022 | M2 | — | deferred |
|
||||
| REQ-023 | M2 | — | deferred |
|
||||
| REQ-024 | M2 | — | deferred |
|
||||
| REQ-025 | M2 | — | deferred |
|
||||
| REQ-026 | M2 | — | deferred (whitelist format + hook ships M1 Wave D) |
|
||||
| REQ-027 | M2 | — | deferred |
|
||||
| REQ-028 | M3 | — | deferred |
|
||||
| REQ-029 | M3 | — | deferred |
|
||||
| REQ-030 | M3 | — | deferred |
|
||||
| REQ-031 | M3 | — | deferred |
|
||||
| REQ-032 | M3 | — | deferred |
|
||||
| REQ-033 | M3 | — | deferred |
|
||||
| REQ-034 | M3 | — | deferred |
|
||||
| REQ-035 | M3 | — | deferred |
|
||||
| REQ-036 | M3 | — | deferred |
|
||||
| REQ-037 | M3 | — | deferred |
|
||||
| REQ-038 | M1 | Wave A | pending |
|
||||
| REQ-039 | M1 | Wave A | pending |
|
||||
| REQ-040 | M1 | Wave A | pending |
|
||||
| REQ-041 | M3 | — | deferred |
|
||||
| REQ-042 | M3 | — | deferred |
|
||||
| REQ-043 | M3 | — | deferred |
|
||||
| REQ-044 | M3 | — | deferred |
|
||||
@@ -0,0 +1,104 @@
|
||||
# Research Findings (Phase 0)
|
||||
|
||||
Spec: CoreCI Chat v0.1 v1.1 (locked 2026-08-24). All architectural decisions locked in CLARIFY.md. This document records implementation patterns, pitfalls, and references for each M1 technology choice so the EXECUTE waves have a grounded baseline. Research is scoped to M1 (REQ-001..014, 038, 039, 040); M2/M3 technologies are noted where they touch M1 foundations.
|
||||
|
||||
---
|
||||
|
||||
## R-001 — Trigger.dev bootstrap & durable execution pattern
|
||||
|
||||
**Scope:** M1 Wave A bootstraps the runtime; M3 adds chat orchestration tasks. M1 must not couple to Trigger.dev in a way that forces an M3 rewrite.
|
||||
|
||||
**Findings:**
|
||||
- Trigger.dev v3 runs tasks as idempotent functions decorated with `task()`; long-running workflows use `runTask()` checkpoints. The runtime connects to a Trigger.dev server (cloud or self-hosted) via `TRIGGER_API_KEY` + `TRIGGER_API_URL`.
|
||||
- Bootstrap pattern: a single `packages/runtime` (or inside `apps/control-plane/lib/runtime`) that initializes the Trigger.dev client at process start and exports a `registerTask` helper. M1 wires the client + a no-op health task; M3 registers the chat orchestration task.
|
||||
- **Pitfall:** Trigger.dev cloud requires outbound HTTPS to `https://api.trigger.dev`. Since the control plane is the only thing that talks to Trigger.dev (not the Relay Agent, not the browser), this is fine for the SaaS deployment. Document the firewall egress.
|
||||
- **Pitfall:** `TRIGGER_API_KEY` is infra-level config — goes through `packages/config` env loading, NOT through `packages/secrets`. It is not a tenant secret.
|
||||
- **M1 decision:** Bootstrap the client + a `runtimeHealthCheck` task that runs every 5 min and appends an audit entry. Proves the runtime works end-to-end without coupling to chat logic.
|
||||
- **Reference:** Trigger.dev v3 docs — `trigger.dev/docs`.
|
||||
|
||||
## R-002 — WorkOS SSO + RBAC role mapping + SCIM
|
||||
|
||||
**Scope:** M1 Wave B (REQ-001..005). WorkOS is the only IdP in v0.1.
|
||||
|
||||
**Findings:**
|
||||
- WorkOS provides `authenticateWithCode()` (OAuth/OIDC code flow) and a hosted SSO portal. The control plane exchanges the auth code for a session, then resolves the user to a tenant.
|
||||
- User ↔ tenant mapping is owned by CoreCI Chat, not WorkOS. WorkOS gives us `userId`, `email`, `organizationId` (optional). We map `organizationId` → tenant at first signup (REQ-002): if no tenant exists for this org, create one and assign the user Admin.
|
||||
- RBAC roles (Admin/Operator/Viewer) are CoreCI Chat's, stored in `tenant_memberships.role`. WorkOS role/ group claims are advisory only — we do not trust them for authorization (REQ-005 enforces at our API gateway, not at the IdP).
|
||||
- Session: httpOnly cookie + a server-side session row carrying `tenantId` + `role`. The API gateway reads the cookie, loads the session, and runs RBAC.
|
||||
- SCIM (REQ-003 invitations): WorkOS exposes a SCIM endpoint and an invitation API. For M1, use the WorkOS `Invitation` resource (single-use acceptance link emailed via WorkOS) — simpler than building email ourselves. Edge 15 (bounce) handled by WorkOS webhook → we mark the invite invalid.
|
||||
- **Pitfall:** WorkOS SSO provider down (Edge 10) — surface a retry screen, block tenant creation. Do not fall back to local auth.
|
||||
- **Pitfall:** Multi-tenant users (same email in two orgs) — resolve the active tenant from the session, allow tenant switching via an explicit endpoint (out of M1 scope to build the switcher UI; the API supports it).
|
||||
- **Reference:** WorkOS Node SDK — `workos.com/docs`.
|
||||
|
||||
## R-003 — Postgres RLS + audit hash-chain
|
||||
|
||||
**Scope:** M1 Wave A (REQ-038, REQ-039). The pattern propagates to every M2/M3 table.
|
||||
|
||||
**Findings:**
|
||||
- **RLS pattern:** every tenant-scoped table has `tenant_id UUID NOT NULL` and a policy `USING (tenant_id = current_setting('app.tenant_id')::uuid)`. The app connects as a role with `app.tenant_id` set per-transaction via `SET LOCAL app.tenant_id = $1` inside a transaction. `packages/db` exposes `withTenant(tenantId, async fn)` that opens a transaction, `SET LOCAL`, runs `fn`, commits. No query outside `withTenant` touches tenant-scoped tables.
|
||||
- **Pitfall:** `current_setting('app.tenant_id')` returns NULL if unset → policy `tenant_id = NULL` is false → rows invisible (safe default, but throws if a query runs outside `withTenant`). Enforce in code: a lint rule or a wrapper that rejects queries without a tenant context.
|
||||
- **Pitfall:** Superuser bypasses RLS. The app role must NOT be superuser. Migrations run as a separate `migrator` role (BYPASSRLS) gated by CI, not the app role.
|
||||
- **Audit hash-chain:** `audit_log (id BIGSERIAL, tenant_id UUID, prev_hash BYTEA, curr_hash BYTEA, payload JSONB, created_at TIMESTAMPTZ, ...)`. `curr_hash = sha256(prev_hash || canonical_jsonb(payload))`. The first row's `prev_hash` is a fixed genesis constant. `id` is monotonic; the chain is verifiable by walking `ORDER BY id`.
|
||||
- **Immutability:** `REVOKE UPDATE, DELETE ON audit_log FROM app_role`. Add a trigger that raises if anyone tries INSERT with a forged `prev_hash` (the app computes `curr_hash` in-app, but `prev_hash` must equal the last row's `curr_hash` for that tenant — a constraint trigger enforces this).
|
||||
- **Edge 7 (write failure halts):** the audit write runs inside the same transaction as the business operation. If the INSERT fails, the transaction rolls back and the operation never happened. Admin alert fires from the error handler.
|
||||
- **Pitfall:** Per-tenant hash-chain vs global hash-chain. Per-tenant chain is simpler to verify and avoids cross-tenant ordering contention. Use per-tenant chains (partition `audit_log` by `tenant_id` or index heavily on `(tenant_id, id)`).
|
||||
- **Reference:** Postgres RLS docs — `postgresql.org/docs/16/ddl-rowsecurity.html`.
|
||||
|
||||
## R-004 — AWS Secrets Manager + KMS + local-encrypted dev fallback
|
||||
|
||||
**Scope:** M1 Wave A (REQ-040). `SecretProvider` interface, two impls.
|
||||
|
||||
**Findings:**
|
||||
- `SecretProvider` interface: `get(tenantId, name): Promise<SecretValue>`, `put(tenantId, name, value): Promise<SecretRef>`, `delete(tenantId, name): Promise<void>`. `SecretRef` is a string like `aws-sm:coreci/<tenantId>/<name>` or `local:<tenantId>/<name>`.
|
||||
- `AwsSecretsManagerProvider` (prod): uses `@aws-sdk/client-secrets-manager`. Secret name convention `coreci/<tenantId>/<name>`. KMS key per tenant (or a shared CMK with encryption context `{tenantId}`). `put` creates or updates; `get` fetches + decrypts. IAM role scoped to the `coreci/*` prefix.
|
||||
- `LocalEncryptedProvider` (dev/test): AES-256-GCM. Master key from `SECRET_MASTER_KEY_DEV` env var (the ONE allowed env var for secrets — everything else is provider-resolved). Ciphertext stored in `.secrets/local-encrypted.json` (gitignored). Each entry: `{ciphertext, iv, authTag, salt}`. Key derived via PBKDF2 from the master key + per-entry salt.
|
||||
- **Pitfall:** The DB never stores the secret — only the `SecretRef`. `byom_endpoints.secret_ref TEXT` holds `aws-sm:coreci/<tenantId>/byom`. The app calls `secrets.get(tenantId, 'byom')` to resolve at use time.
|
||||
- **Pitfall:** Logging — never `console.log` a resolved secret. The `SecretValue` type should have a custom `toString()` that returns `[REDACTED]`. Add a lint rule banning `console.log(secret)`.
|
||||
- **Pitfall:** Rotation — out of M1 scope. The interface supports it (`put` overwrites); a rotation job is M3.
|
||||
- **Reference:** AWS Secrets Manager Node SDK — `docs.aws.amazon.com/secretsmanager`.
|
||||
|
||||
## R-005 — Go Relay Agent: install script, systemd, WebSocket, SSH whitelist hook
|
||||
|
||||
**Scope:** M1 Wave D (REQ-010..013, REQ-026 whitelist hook). The SSH adapter itself is M2.
|
||||
|
||||
**Install script (modular):**
|
||||
- Separate functions: `detect_os`, `install_binary`, `write_systemd_unit`, `register_target`, `main`.
|
||||
- `detect_os`: reads `/etc/os-release`, parses `ID` + `VERSION_ID`. Supported: `ubuntu` ≥ 24.04, `debian` ≥ 12. Anything else → exit non-zero with a clear message listing supported OS + versions (Edge 16).
|
||||
- `install_binary`: downloads the static Go binary for the detected arch (`uname -m` → amd64/arm64) from the control plane's release URL. Verifies SHA256 checksum. Installs to `/usr/local/bin/coreci-relay-agent`. Fallback: apt package from a configured repo (documented; same script path, different binary source).
|
||||
- `write_systemd_unit`: writes `/etc/systemd/system/coreci-relay-agent.service` with `ExecStart`, `Restart=on-failure`, `RestartSec=5`, `WantedBy=multi-user.target`, `Environment=CORECI_CONFIG=/etc/coreci/relay.env`. `systemctl daemon-reload && systemctl enable --now coreci-relay-agent`.
|
||||
- `register_target`: writes `/etc/coreci/relay.env` with `CORECI_TENANT_TOKEN=<token>` (the tenant registration token issued by the dashboard), `CORECI_SAAS_URL=https://...`. The token is a secret-manager reference bootstrap — the agent uses it to authenticate the first WebSocket; long-lived credentials are issued by the control plane post-registration.
|
||||
- **Pitfall:** `curl|bash` anti-patterns — always download to a temp file, verify checksum before executing, never pipe to a shell that runs as root without a checksum gate. The install script is `curl -fsSL https://.../install.sh | sh` but the script itself verifies the binary checksum before install.
|
||||
- **Pitfall:** Idempotency — re-running the script must upgrade, not fail. `install_binary` overwrites; `write_systemd_unit` overwrites + reloads; `register_target` preserves an existing token.
|
||||
|
||||
**Go binary:**
|
||||
- WebSocket client: `gorilla/websocket` or `nhooyr.io/websocket`. Outbound `wss://<saas>/api/relay/ws`. Auth: `Authorization: Bearer <tenant_token>` on the initial handshake.
|
||||
- Registration: first message after connect is `{type: "register", tenantId, hostname, os, osVersion, ip, agentVersion}`. Control plane responds `{type: "registered", targetId}`.
|
||||
- Heartbeat: send `{type: "ping", ts}` every 30s; control plane echoes `{type: "pong", ts}`. If no pong within 60s, drop + reconnect. `last_seen` updated on every ping → dashboard green/yellow/red.
|
||||
- Reconnect: exponential backoff (1s, 2s, 4s, 8s, 16s), max 5 attempts → log alert + keep trying every 60s. systemd `Restart=on-failure` handles hard crashes.
|
||||
- **Pitfall:** Clock skew — use server time for `last_seen`, not agent time.
|
||||
- **Pitfall:** TLS — pin the SaaS cert via the system trust store; never allow self-signed in prod (dev only flag).
|
||||
|
||||
**SSH whitelist hook (M1 ships format + hook, M2 plugs adapter):**
|
||||
- Whitelist file: `/etc/coreci/ssh-whitelist.json`, shipped with the binary. Format: `{"commands": ["cat", "ls", "systemctl status", "journalctl", "df", "du", "ps", "top", "ss", "netstat", "ip", "uptime", "uname", "free", "who", "w", "last", "dmesg", "lscpu", "lspci", "lsblk", "mount", "findmnt", "hostname", "ip addr", "ip route", "ss -tlnp"], "arguments": {"deny": ["-exec", "-execdir", "--exec", "|", ">", ">>", "&", ";", "&&", "||"]}}`.
|
||||
- Enforcement hook: a Go function `CheckCommand(cmd string) error` that parses the command, checks the base command against the whitelist, checks arguments against the deny list, and returns an error if rejected. M2's SSH adapter calls `CheckCommand` before `exec.Command`. M1 ships the function + a unit test + the whitelist file; no SSH execution path yet.
|
||||
- **Pitfall:** `find -exec` is the classic whitelist escape — deny `-exec`/`-execdir`. Argument deny list catches redirections and shell operators.
|
||||
- **Reference:** systemd unit docs — `systemd.io` ; gorilla/websocket — `github.com/gorilla/websocket`.
|
||||
|
||||
## R-006 — Vanta evidence collection (M3 — noted, not built in M1)
|
||||
|
||||
**Scope:** M3 only (REQ-042). Recorded here so M1 foundations don't block M3 instrumentation.
|
||||
|
||||
**Findings:**
|
||||
- Vanta collects evidence via integrations (AWS, GitHub, HR systems) and via custom controls that call Vanta's API. The control plane exposes a `/vanta/evidence` endpoint that Vanta polls for control evidence (access logs, change management, vendor risk, incident response).
|
||||
- M1 action: ensure `audit_log` is queryable by an admin-scoped read role (not the app role) so M3's Vanta exporter can read it without bypassing RLS. The exporter runs as a tenant-scoped admin reader.
|
||||
- **No M1 build.** Just the architectural note.
|
||||
|
||||
## R-007 — Cross-tenant isolation test pattern (REQ-039 pen test)
|
||||
|
||||
**Scope:** M1 acceptance gate requires "cross-tenant isolation pen test result (must show zero leakage)".
|
||||
|
||||
**Findings:**
|
||||
- Test pattern: create two tenants (T1, T2), each with a target + a BYOM endpoint. Issue a query as T1's user attempting to read T2's data (direct table scan, join, subquery, `SET app.tenant_id` bypass attempt). Assert every query returns zero T2 rows.
|
||||
- Test the `withTenant` wrapper: any query outside `withTenant` must throw, not return rows.
|
||||
- Test the audit log: T1's audit entries are invisible to T2's admin export.
|
||||
- **Reference:** This becomes an integration test in Wave A + a dedicated pen-test script for the M1 review.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Roadmap
|
||||
|
||||
## Overview
|
||||
|
||||
Placeholder roadmap for the `coreci-chat` project. The full phase breakdown will be produced by the roadmapper once the project specification is supplied in a follow-up session and the specify → clarify → research stages complete. The current milestone is `v0.1` (branch `milestone/v0.1-bootstrap`); phase 0 is in progress on `phase/00-pre-execution`.
|
||||
|
||||
Milestone type: **NFR** (placeholder — to be re-evaluated by `getMilestoneType()` once phases are defined). Tags will run on the previous minor's patch line: phase 0 → `v0.0.1` (no prior tags exist, so the v0.0.x line is seeded here).
|
||||
|
||||
## Phases
|
||||
|
||||
- [ ] **Phase 0: pre-execution** - Capture specification, clarification, research, and plan artifacts before any implementation
|
||||
- [ ] **Phase 1: implementation** - (placeholder — to be refined by roadmapper after specification is supplied)
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 0: pre-execution
|
||||
**Goal.**: Run the pre-execution pipeline stages (SPECIFY → CLARIFY → RESEARCH → PLAN → GRILL) and land all `.ciagent/` reference files (PROJECT.md, ROADMAP.md, REQUIREMENTS.md, ARCHITECTURE.md, PERSONAS.md, GRILL.md).
|
||||
**Depends on**: Nothing
|
||||
**Requirements**: REQ-001 (specification), REQ-002 (clarification), REQ-003 (research), REQ-004 (plan)
|
||||
**Success Criteria**:
|
||||
1. Full project specification parsed and recorded in PROJECT.md
|
||||
2. Clarify stage completed with all ambiguities resolved (defaults accepted under `full` autonomy)
|
||||
3. Research artifacts committed under `.ciagent/`
|
||||
4. Plan committed and ready for execution-phase decomposition
|
||||
**Status**: in_progress
|
||||
|
||||
### Phase 1: implementation
|
||||
**Goal.**: (placeholder — to be refined by the roadmapper after the full specification is supplied)
|
||||
**Depends on**: Phase 0
|
||||
**Requirements**: (to be defined)
|
||||
**Success Criteria**:
|
||||
1. (to be defined)
|
||||
**Status**: not_started
|
||||
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"projects": [],
|
||||
"active_project": "",
|
||||
"active_projects": [],
|
||||
"autonomy": {
|
||||
"level": "full",
|
||||
"escalation_hooks": ["deploy", "delete_data", "merge_to_main"],
|
||||
"clarify_budget": 10,
|
||||
"decision_confidence_threshold": 0.6,
|
||||
"max_revision_iterations": 3,
|
||||
"max_verification_retries": 2,
|
||||
"escalation_timeout_ms": 300000
|
||||
},
|
||||
"model_profile": "quality",
|
||||
"parallelization": {
|
||||
"enabled": true,
|
||||
"max_concurrent_agents": 5,
|
||||
"min_plans_for_parallel": 2,
|
||||
"max_concurrent_projects": 3
|
||||
},
|
||||
"verification": {
|
||||
"automated_only": true,
|
||||
"escalate_visual": true,
|
||||
"escalate_external_integration": true,
|
||||
"test_first": true
|
||||
},
|
||||
"security": {
|
||||
"auto_accept_low_severity": true,
|
||||
"auto_mitigate_medium_severity": true,
|
||||
"escalate_high_severity": true,
|
||||
"bash_allowlist": {
|
||||
"allowed_commands": [
|
||||
"npm", "node", "npx", "pnpm", "yarn",
|
||||
"git", "ls", "cat", "head", "tail", "wc",
|
||||
"echo", "mkdir", "cp", "mv", "rm", "touch",
|
||||
"pwd", "which", "env", "printenv",
|
||||
"jest", "eslint", "tsc", "prettier",
|
||||
"curl", "wget",
|
||||
"docker", "docker-compose",
|
||||
"ts-node", "tsx"
|
||||
],
|
||||
"max_output_bytes": 1048576,
|
||||
"timeout_ms": 30000,
|
||||
"blocked_env_vars": [
|
||||
"HOME", "PATH", "USER", "SHELL",
|
||||
"AWS_*", "*_TOKEN", "*_KEY", "*_SECRET",
|
||||
"*_PASSWORD", "*_CREDENTIAL",
|
||||
"GITHUB_TOKEN", "GITHUB_API_KEY",
|
||||
"OPENAI_API_KEY", "ANTHROPIC_API_KEY",
|
||||
"OLLAMA_CLOUD_API_KEY"
|
||||
]
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"branching_strategy": "phase",
|
||||
"auto_commit": true,
|
||||
"auto_push": true
|
||||
},
|
||||
"secrets": {
|
||||
"sources": [".env", ".env.secrets", ".env.*"],
|
||||
"disallow": ["shell_env", "netrc", "keychain", "rc_files", "global_config"],
|
||||
"scopes": {
|
||||
"gitea": "GITEA_TOKEN",
|
||||
"github": "GITHUB_TOKEN",
|
||||
"gitlab": "GITLAB_TOKEN",
|
||||
"openai": "OPENAI_API_KEY",
|
||||
"anthropic": "ANTHROPIC_API_KEY",
|
||||
"ollama_cloud": "OLLAMA_CLOUD_API_KEY"
|
||||
}
|
||||
},
|
||||
"release": {
|
||||
"forge": "gitea",
|
||||
"gitea": {
|
||||
"base_url": "https://git.cloudinit.dev",
|
||||
"owner": "coreci",
|
||||
"repo": "coreci-chat",
|
||||
"token_scope": "gitea"
|
||||
},
|
||||
"github": { "owner": "", "repo": "", "token_scope": "github" },
|
||||
"gitlab": { "base_url": "", "owner": "", "repo": "", "token_scope": "gitlab" }
|
||||
},
|
||||
"ship": {
|
||||
"per_phase": true,
|
||||
"require_release": true,
|
||||
"allow_skip": false,
|
||||
"confirm_before_ship": false,
|
||||
"max_release_retries": 3,
|
||||
"release_blocking": false,
|
||||
"build_assets": "milestone"
|
||||
},
|
||||
"backend": {
|
||||
"provider": "auto",
|
||||
"agent_backends": { "opencode": { "enabled": true } },
|
||||
"llm_backends": {
|
||||
"ollama-local": { "base_url": "http://localhost:11434", "model_profile": "balanced" },
|
||||
"ollama-cloud": { "base_url": "", "api_key_env": "OLLAMA_CLOUD_API_KEY", "model_profile": "quality", "timeout_ms": 60000 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
# CoreCI Chat v0.1 — Engineering Specification v1.1 (FINAL)
|
||||
|
||||
**Owner:** Sarah Chen
|
||||
**Status:** Locked (v1.1)
|
||||
**Type:** Feature (MVP)
|
||||
**Target Milestone:** CoreCI Chat v0.1 — Read-Only Diagnostic MVP
|
||||
**Companion Product:** CoreCI (existing CI/CD platform by owner)
|
||||
**Locked Date:** 2026-08-24
|
||||
|
||||
> **Operating Principles for this Spec:**
|
||||
> 1. **Incremental Delivery:** Defines net-new work for CoreCI Chat v0.1. Pre-existing systems and locked architectures are referenced, not restated.
|
||||
> 2. **Zero Ambiguity:** Any requirement not translatable to a pass/fail QA test is rejected by Engineering.
|
||||
> 3. **No Scope Expansion:** Any requirement discovered during spec build that is not authorized by CoreCI Chat Vision v1.0 / Phase 2 scope will be flagged as a spec-time scope question and escalated — never silently added.
|
||||
|
||||
---
|
||||
|
||||
## 1. Objective
|
||||
|
||||
CoreCI Chat v0.1 delivers a browser-based chat interface where mid-market enterprise IT operators ask natural-language questions about their infrastructure and receive coherent, evidence-backed diagnostic answers within five minutes. CoreCI Chat speaks MCP to a Relay Agent installed as a systemd service on the customer's Linux servers (Ubuntu 24.04 or Debian), and directly to Proxmox and GitHub/Gitea APIs. All AI inference is routed through a customer-provided model endpoint (BYOM). Every prompt, tool call, and command execution is immutably audited. The wedge is deliberately conservative: CoreCI Chat investigates in v0.1 and proposes remediation in v1.1. CoreCI Chat is a companion product to CoreCI (the owner's existing CI/CD platform).
|
||||
|
||||
*Acceptance Gate:* A developer reading this can state the goal of the feature in a single sentence.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope & Target Milestones
|
||||
|
||||
### 2.1 In Scope (Explicit Additions)
|
||||
|
||||
**Core Application**
|
||||
- Browser-based chat UI (no mobile, no native apps, no CLI)
|
||||
- Streaming tool execution traces with inline citations
|
||||
- Conversation history persistence per user/tenant
|
||||
- Tenant admin dashboard
|
||||
|
||||
**AI Orchestration**
|
||||
- LLM orchestration engine (BYOM endpoint routing only — no hosted inference)
|
||||
- Async durable execution for long-running tool calls
|
||||
- Conversation memory within session
|
||||
- Rejoin-in-progress async workflow
|
||||
- LLM step limit (≤20 tool calls per workflow)
|
||||
|
||||
**MCP Layer**
|
||||
- Abstract generic MCP tool schema
|
||||
- MCP gateway with read-only enforcement at proxy layer
|
||||
- Adapter implementations (Day 1): Proxmox, SSH/Linux Server, GitHub, Gitea
|
||||
- Multi-tenancy isolation at gateway
|
||||
- SSE streaming from MCP to UI
|
||||
- Token-bucket rate limiting (per user, per tenant)
|
||||
|
||||
**Relay Agent**
|
||||
- Lightweight systemd service running on Linux hosts (Ubuntu 24.04, Debian 12+)
|
||||
- Distribution via install script (curl|bash or apt package)
|
||||
- Outbound WebSocket to CoreCI Chat SaaS (no inbound firewall rules required)
|
||||
- Read-only enforcement at Relay Agent layer for SSH command execution
|
||||
- Heartbeat, health check, and auto-reconnect
|
||||
- Per-target registration (each Relay Agent = one target)
|
||||
|
||||
**Identity & Access**
|
||||
- SSO/SAML via WorkOS (or equivalent)
|
||||
- RBAC: Admin, Operator, Viewer roles
|
||||
- Per-tenant credential storage in centralized secret manager
|
||||
|
||||
**Security & Compliance**
|
||||
- Immutable audit logging (prompts, tool calls, SSH commands, responses)
|
||||
- Row-Level Security on all tenant-scoped data
|
||||
- "SOC 2 Type 1 Audit in Progress" posture published
|
||||
- GRC platform (Vanta or Drata) instrumentation
|
||||
|
||||
**Pricing Infrastructure**
|
||||
- Usage metering layer (tool calls, LLM tokens, workflow executions)
|
||||
- Per-tenant usage dashboard for Admin
|
||||
|
||||
**Out-of-Box Setup**
|
||||
- Install script for Relay Agent
|
||||
- Configuration templates for Day 1 integrations
|
||||
- Pre-built smoke-test investigation scenarios
|
||||
|
||||
### 2.2 Out of Scope (Explicit Exclusions)
|
||||
|
||||
- Write actions of any kind (apply, delete, scale, rollback, restart, container start/stop, VM start/stop) — deferred to v1.1
|
||||
- Approval-gated remediation workflows and Senior Approver persona — v1.1
|
||||
- Remediation diff preview UI — v1.1
|
||||
- Hosted LLM inference from CoreCI Chat — never (BYOM is permanent)
|
||||
- Fine-tuning on customer data
|
||||
- Custom model deployments managed by CoreCI Chat
|
||||
- MCP integrations beyond Proxmox, SSH/Linux Server, GitHub, Gitea — v1.2+
|
||||
- Custom MCP server authoring tools for customers — v1.2+
|
||||
- Inbound webhook ingestion from alerting tools (manual chat launch only in MVP)
|
||||
- Visual drag-and-drop workflow builder (LLM-driven dynamic only in MVP)
|
||||
- Workflow templates, sharing, versioning — v1.2+
|
||||
- Slack / Teams / Discord chat interfaces — v1.1+
|
||||
- Mobile apps — not planned for v1.x
|
||||
- CLI for power users — v1.1
|
||||
- Email notifications — v1.1
|
||||
- Multi-region deployment (single region MVP)
|
||||
- White-label / multi-brand tenants
|
||||
- Custom RBAC roles beyond Admin / Operator / Viewer — v1.2+
|
||||
- Audit log query UI (logs captured and exportable, not searchable in MVP) — v1.2+
|
||||
- RAG over historical incidents, post-mortems, Slack threads — v1.1
|
||||
- SOC 2 Type 1 final certification — controls in place, audit running, cert post-MVP
|
||||
- HIPAA, FedRAMP, PCI, ISO 27001 — not planned for v1.x
|
||||
- Customer-managed encryption keys (BYOK) — v1.2+
|
||||
- Data residency controls beyond BYOM (single region MVP)
|
||||
- **Kubernetes integration** of any kind — not planned
|
||||
- **ArgoCD integration** of any kind — not planned
|
||||
- **Helm-based distribution** of any kind — not planned
|
||||
- Container orchestration surfaces (Kubernetes, Nomad, Docker Swarm, etc.) — not planned for v1.x
|
||||
- Windows server management — not planned for v1.x (Linux only)
|
||||
|
||||
### 2.3 Milestone Breakdown
|
||||
|
||||
* **Milestone 1 — Foundation (Weeks 1–3):** Ships REQ-001 through REQ-014, REQ-038, REQ-039, REQ-040.
|
||||
* _Acceptance Gate:_ Platform Lead can sign up via SSO, configure a BYOM endpoint with green validation, deploy Relay Agent via install script on at least one target Linux host, register the target, and see green status in admin dashboard. Audit logging, RLS, and secret manager are operational.
|
||||
|
||||
* **Milestone 2 — MCP Layer & Day 1 Adapters (Weeks 4–5):** Ships REQ-015 through REQ-027.
|
||||
* _Acceptance Gate:_ Platform Lead can configure all four Day 1 integrations (Proxmox, SSH target, GitHub or Gitea) and verify each adapter responds to a test call from the admin dashboard. Multi-target scoping is functional. Read-only enforcement is verified at both gateway and Relay Agent layers (including SSH command whitelist).
|
||||
|
||||
* **Milestone 3 — Chat, Orchestration, Hardening (Weeks 6–8):** Ships REQ-028 through REQ-037, REQ-041 through REQ-044.
|
||||
* _Acceptance Gate:_ Operator can ask a natural-language diagnostic question, see streamed tool execution, and receive a cited, evidence-backed answer within 5 minutes (p95). Async workflows persist and rejoin correctly. Usage is metered. SOC 2 controls are instrumented. All v0.1 release gates pass.
|
||||
|
||||
---
|
||||
|
||||
## 3. Personas & User Journeys
|
||||
|
||||
### 3.1 Personas
|
||||
|
||||
* **Persona A — The Operator (Primary User):** SRE, Sysadmin, or Platform Engineer who owns infrastructure reliability across on-prem Proxmox environments, Linux servers (Ubuntu/Debian), and Git-based CI/CD workflows. Day is SSH sessions, Proxmox UI, GitHub/Gitea, observability tools, Slack, Jira. Pain is real-time cognitive load during incidents across heterogeneous infrastructure. MVP authority: read-only across all integrated systems.
|
||||
* **Persona B — The Platform Lead / Tenant Admin (Secondary User):** Senior platform engineer or IT lead who owns the CoreCI Chat tenant. Connects MCP integrations, deploys Relay Agents, configures BYOM endpoint, manages RBAC, reviews audit logs. Pain is setup time for a small team.
|
||||
|
||||
### 3.2 Happy Paths
|
||||
|
||||
**Journey 1 — Operator performs diagnostic investigation to achieve evidence-backed root cause in under 5 minutes**
|
||||
|
||||
1. **Step 1:** Operator opens CoreCI Chat in browser → Chat UI loads with input field, history, and streaming area. _(Maps to REQ-028)_
|
||||
2. **Step 2:** Operator types natural-language question and submits → Submission accepted, orchestration begins. _(Maps to REQ-029)_
|
||||
3. **Step 3:** LLM (via BYOM) reasons about which abstract MCP tools to invoke → Tool selection recorded. _(Maps to REQ-033)_
|
||||
4. **Step 4:** Orchestrator invokes abstract tools; gateway routes to tenant-specific adapters → Routing confirmed with tenant context. _(Maps to REQ-015, REQ-016)_
|
||||
5. **Step 5:** Adapter executes read-only query (Proxmox, SSH, GitHub, or Gitea); read-only enforced at both layers → Query results returned, write attempts blocked. SSH commands go through whitelist enforcement. _(Maps to REQ-018, REQ-020, REQ-021, REQ-022, REQ-023, REQ-024, REQ-025, REQ-026, REQ-027)_
|
||||
6. **Step 6:** Tool execution output streams to UI in real-time → Trace panel updates as each tool returns. _(Maps to REQ-017, REQ-031)_
|
||||
7. **Step 7:** LLM synthesizes cited answer from tool evidence → Response rendered inline with citations. _(Maps to REQ-030)_
|
||||
8. **Step 8:** If workflow exceeds 30s, state persists; user can rejoin → Resume works seamlessly. _(Maps to REQ-036, REQ-037)_
|
||||
9. **Step 9:** Audit log captures prompt, all tool calls (including SSH commands), all responses → Immutable entries written. _(Maps to REQ-038)_
|
||||
10. **Step 10:** Conversation history persisted per user/tenant → Available next session. _(Maps to REQ-032)_
|
||||
|
||||
* **Testable Acceptance (BDD Format):**
|
||||
* [x] **Given** an Operator is authenticated and on the chat page, **when** they submit "why is `web-server-01` throwing 503s in production", **then** within 5 minutes (p95) the response includes a cited root-cause hypothesis referencing at least one tool execution (Proxmox VM state, SSH service status, or recent deploy commit).
|
||||
* [x] **Given** the LLM invokes a tool, **when** the tool returns, **then** a tool trace entry is rendered in the UI within 1 second of the return.
|
||||
* [x] **Given** the workflow completes, **when** the Operator refreshes or returns later, **then** the conversation is fully retrievable with prompts, tool calls, SSH command outputs, and responses.
|
||||
|
||||
**Journey 2 — Platform Lead onboards a CoreCI Chat tenant to achieve working integration in under 2 hours**
|
||||
|
||||
1. **Step 1:** Platform Lead signs up via SSO → Session established, tenant created, user assigned Admin. _(Maps to REQ-001, REQ-002)_
|
||||
2. **Step 2:** Platform Lead configures BYOM endpoint (URL + API key) → Endpoint validated with green test call. _(Maps to REQ-006, REQ-007)_
|
||||
3. **Step 3:** Platform Lead runs Relay Agent install script on target Linux host(s) → systemd service installed and started. _(Maps to REQ-010, REQ-011)_
|
||||
4. **Step 4:** Relay Agent establishes outbound WebSocket → Registered with tenant + target metadata (hostname, OS, IP). _(Maps to REQ-012)_
|
||||
5. **Step 5:** Platform Lead configures Day 1 integrations (Proxmox, SSH targets, GitHub or Gitea) → Adapters activated with credentials. _(Maps to REQ-020, REQ-021, REQ-022, REQ-023, REQ-025, REQ-026, REQ-027)_
|
||||
6. **Step 6:** Platform Lead verifies multi-target scope where applicable → Target selector functions correctly. _(Maps to REQ-024)_
|
||||
7. **Step 7:** Platform Lead assigns RBAC roles (Operator, Viewer) to team members → Roles enforced on next API call. _(Maps to REQ-003, REQ-004, REQ-005)_
|
||||
8. **Step 8:** Platform Lead runs smoke-test investigation → End-to-end diagnostic returns expected result. _(Maps to REQ-033, REQ-034, REQ-035)_
|
||||
9. **Step 9:** Relay Agent health and logs visible in admin dashboard → Status green, logs accessible. _(Maps to REQ-014)_
|
||||
10. **Step 10:** Usage metering is active → Counters increment for tool calls, LLM tokens, workflow executions. _(Maps to REQ-043, REQ-044)_
|
||||
|
||||
* **Testable Acceptance (BDD Format):**
|
||||
* [x] **Given** a Platform Lead completes signup via SSO, **when** the dashboard loads, **then** a new tenant exists with the user as Admin and all onboarding steps are visible.
|
||||
* [x] **Given** the Relay Agent install script is executed with valid tenant credentials, **when** the systemd service starts, **then** an outbound WebSocket to CoreCI Chat SaaS is established within 60 seconds and health status turns green.
|
||||
* [x] **Given** all onboarding steps complete, **when** the Platform Lead runs the smoke-test scenario, **then** the diagnostic returns a result, audit log entries are written (including SSH commands if invoked), and usage counters increment.
|
||||
|
||||
### 3.3 Failure & Edge Paths
|
||||
|
||||
* **Edge 1 (J1):** BYOM endpoint unreachable mid-workflow → System displays actionable error, suggests reconfiguration, halts workflow. Handled via REQ-009.
|
||||
* **Edge 2 (J1):** LLM step limit hit (>20 tool calls) → Workflow halts with clear error to user; partial results preserved. Handled via REQ-035.
|
||||
* **Edge 3 (J1):** MCP adapter times out → Surface timeout with retry option; tool trace shows failure. Handled via REQ-016, REQ-017.
|
||||
* **Edge 4 (J1):** Relay Agent WebSocket drops mid-investigation → Auto-reconnect via exponential backoff; workflow resumes from durable state. Handled via REQ-013, REQ-036.
|
||||
* **Edge 5 (J1):** Rate limit hit → HTTP 429 surfaced with retry-after window. Handled via REQ-019.
|
||||
* **Edge 6 (J1):** Multi-target scope ambiguous (multiple targets registered, no selection) → Prompt user to select target before tool execution. Handled via REQ-024.
|
||||
* **Edge 7 (J1):** Audit log write fails → Operation halts; admin alerted; no silent drops. Handled via REQ-038.
|
||||
* **Edge 8 (J1):** Write-action request submitted to MCP gateway → Rejected with HTTP 403 and audited. Handled via REQ-018.
|
||||
* **Edge 9 (J1):** SSH command not on whitelist → Command rejected at Relay Agent, error returned to LLM, audit log records attempt. Handled via REQ-026.
|
||||
* **Edge 10 (J2):** SSO provider down → Error displayed with retry; tenant creation blocked. Handled via REQ-001.
|
||||
* **Edge 11 (J2):** BYOM endpoint test fails on save → Save blocked; error details surfaced. Handled via REQ-007.
|
||||
* **Edge 12 (J2):** Relay Agent fails to register → Registration error with troubleshooting link; dashboard shows red status. Handled via REQ-012.
|
||||
* **Edge 13 (J2):** MCP adapter auth fails (Proxmox token, SSH key, Git token) → Credential validation error; integration not activated. Handled via REQ-025, REQ-026, REQ-027.
|
||||
* **Edge 14 (J2):** Smoke-test investigation fails → Specific failure point surfaced (LLM, adapter, network, auth). Handled via REQ-033, REQ-034.
|
||||
* **Edge 15 (J2):** Email invitation bounces → Admin notified; invitation marked invalid. Handled via REQ-003.
|
||||
* **Edge 16 (J2):** Unsupported OS detected during Relay Agent install → Install aborted with clear message listing supported OS versions. Handled via REQ-010.
|
||||
|
||||
---
|
||||
|
||||
## 4. Functional Requirements
|
||||
|
||||
_Every REQ must map to at least one journey and possess testable criteria._
|
||||
|
||||
| ID | Title | Journeys | Priority | Acceptance Criteria (Given/When/Then or explicit rules) |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **REQ-001** | Establish SSO session via identity provider | J2 | High | **Given** an unauthenticated user navigates to CoreCI Chat, **when** they complete SSO flow via the configured IdP, **then** a session is established and they are redirected to the dashboard. |
|
||||
| **REQ-002** | Provision tenant on first signup | J2 | High | **Given** a user completes signup for the first time, **when** tenant creation runs, **then** a new tenant is created, the user is assigned Admin role, and the admin dashboard loads. |
|
||||
| **REQ-003** | Invite users to tenant via email | J2 | High | **Given** an Admin submits an invitation, **when** the system processes the invite, **then** an email is sent to the invitee containing a single-use acceptance link. |
|
||||
| **REQ-004** | Apply RBAC role to user | J2 | High | **Given** an Admin assigns a role (Admin/Operator/Viewer), **when** the assignment is saved, **then** the user's role is updated and enforced on the next API call. |
|
||||
| **REQ-005** | Enforce RBAC at API gateway | J1, J2 | High | **Given** a user with role X calls endpoint Y, **when** the role check runs, **then** the request is allowed iff X has permission for Y. |
|
||||
| **REQ-006** | Configure BYOM endpoint (URL + API key) | J2 | High | **Given** an Admin submits endpoint URL and API key, **when** the form is saved, **then** the API key is stored in the secret manager and the URL is validated. |
|
||||
| **REQ-007** | Validate BYOM endpoint connectivity on save | J2 | High | **Given** an Admin submits a BYOM endpoint, **when** validation runs, **then** a test inference call is sent and the result is displayed as success or failure with error details. |
|
||||
| **REQ-008** | Route all LLM inference to configured BYOM endpoint | J1, J2 | High | **Given** a user submits a prompt, **when** orchestration runs, **then** 100% of LLM inference calls are sent to the configured BYOM endpoint (verified via outbound traffic log). |
|
||||
| **REQ-009** | Reject LLM request when BYOM is unconfigured or unreachable | J1, J2 | High | **Given** no BYOM endpoint is configured or it is unreachable, **when** an Operator submits a prompt, **then** the request is rejected with a clear actionable error and no inference is attempted. |
|
||||
| **REQ-010** | Distribute Relay Agent as systemd service via install script | J2 | High | **Given** a Platform Lead runs the install script on a supported host (Ubuntu 24.04 LTS or Debian 12+), **when** execution completes, **then** a systemd service is installed, started, and configured for auto-start on boot; install aborts with clear error on unsupported OS. |
|
||||
| **REQ-011** | Establish outbound WebSocket from Relay Agent to SaaS | J2 | High | **Given** the Relay Agent systemd service is running with valid tenant credentials, **when** the service starts, **then** it establishes an outbound WebSocket to CoreCI Chat SaaS within 60 seconds. |
|
||||
| **REQ-012** | Register Relay Agent with tenant + target metadata | J2 | High | **Given** a Relay Agent connects, **when** registration completes, **then** tenant ID, target ID, hostname, OS name and version, IP address, and agent version are recorded. |
|
||||
| **REQ-013** | Maintain heartbeat and auto-reconnect on WebSocket drop | J2 | High | **Given** the WebSocket drops, **when** 30 seconds elapse without reconnect, **then** the Relay Agent initiates reconnection with exponential backoff (max 5 attempts before alerting); systemd auto-restarts on hard failure. |
|
||||
| **REQ-014** | Surface Relay Agent health and logs in admin dashboard | J2 | Med | **Given** a Relay Agent is registered, **when** an Admin views the dashboard, **then** health status (green/yellow/red), target hostname, and the last 100 log lines are visible. |
|
||||
| **REQ-015** | Define abstract MCP tool schema | J1, J2 | High | **Given** the MCP gateway receives a call, **when** it routes, **then** calls match one of the approved abstract tools: `get_logs`, `list_resources`, `describe_topology`, `get_recent_deploys`, `list_events`, `get_metrics`, `list_ci_runs`, `get_ci_run`. |
|
||||
| **REQ-016** | Route abstract MCP calls to tenant-specific adapter | J1, J2 | High | **Given** an Operator submits a prompt, **when** the LLM requests an abstract tool, **then** the gateway routes to the tenant's specific adapter (Proxmox, SSH, GitHub, or Gitea) based on tool type and tenant config. |
|
||||
| **REQ-017** | Stream tool execution output to chat UI via SSE | J1 | High | **Given** an Operator submits a prompt, **when** tool calls execute, **then** partial results are streamed to the UI within 1 second of each tool return. |
|
||||
| **REQ-018** | Enforce read-only at MCP gateway proxy layer | J1, J2 | High | **Given** any write-action request (POST, PUT, DELETE, PATCH) is submitted to MCP, **when** the gateway processes it, **then** the request is rejected with HTTP 403 and audited. |
|
||||
| **REQ-019** | Apply token-bucket rate limit per user and per tenant | J1 | Med | **Given** a user exceeds 60 req/min or a tenant exceeds 300 req/min, **when** the next request arrives, **then** the request is rejected with HTTP 429 and the limit window is communicated. |
|
||||
| **REQ-020** | Implement read-only Proxmox MCP adapter | J1, J2 | High | **Given** an Operator requests VM list or VM status, **when** the adapter executes, **then** a list of VMs (ID, name, status, node, resource allocation) is returned, no state mutation is possible, and the audit log records the call. Supports Proxmox VE 7.x and 8.x. |
|
||||
| **REQ-021** | Implement read-only SSH/Linux Server MCP adapter | J1, J2 | High | **Given** an Operator requests system state (e.g., service status, disk usage, recent logs), **when** the adapter executes, **then** a whitelisted read-only command runs via SSH and the output is returned; commands not on the whitelist are rejected at the Relay Agent layer; audit log records the full command and output. |
|
||||
| **REQ-022** | Implement read-only GitHub MCP adapter | J1, J2 | High | **Given** an Operator requests recent commits, PRs, or Actions workflow runs, **when** the adapter executes, **then** the requested data is returned, no state mutation is possible, and the audit log records the call. |
|
||||
| **REQ-023** | Implement read-only Gitea MCP adapter | J1, J2 | High | **Given** an Operator requests repositories, commits, PRs, or Gitea Actions runs, **when** the adapter executes, **then** the requested data is returned, no state mutation is possible, and the audit log records the call. |
|
||||
| **REQ-024** | Scope MCP queries to explicitly selected target in multi-target tenants | J1, J2 | High | **Given** a tenant has ≥2 registered targets and the Operator selects target X, **when** any MCP call executes, **then** only target X is queried and results are labeled with target identifier. |
|
||||
| **REQ-025** | Authenticate to Proxmox via scoped API token + read-only role | J2 | High | **Given** a customer creates a Proxmox user with `PVEAuditor` role and generates an API token, **when** the adapter authenticates, **then** API calls succeed only for read-only operations; the token is stored in the secret manager. |
|
||||
| **REQ-026** | Authenticate to Linux servers via SSH key with command whitelist enforcement at Relay Agent | J2 | High | **Given** a customer generates an SSH keypair for the Relay Agent and authorizes it on the target host, **when** the Relay Agent receives a tool call, **then** only commands on the approved whitelist (`cat`, `ls`, `systemctl status`, `journalctl`, `df`, `du`, `ps`, `top`, `ss`, `netstat`, `ip`, `uptime`, `uname`, etc.) are executed; non-whitelisted commands are rejected and audited. |
|
||||
| **REQ-027** | Authenticate to GitHub and Gitea via scoped API tokens | J2 | High | **Given** a customer provides a GitHub or Gitea API token with read-only scope, **when** the adapter authenticates, **then** API calls succeed only for permitted resources; the token is stored in the secret manager. |
|
||||
| **REQ-028** | Render chat interface in browser | J1 | High | **Given** an Operator navigates to CoreCI Chat, **when** the page loads, **then** a chat interface renders with input field, message history, and streaming output area. |
|
||||
| **REQ-029** | Accept natural language input and submit for orchestration | J1 | High | **Given** an Operator types a question and submits, **when** the submission is processed, **then** the prompt is sent to the LLM orchestrator and streaming response begins within 3 seconds (p95). |
|
||||
| **REQ-030** | Display streaming LLM response with inline citations | J1 | High | **Given** the LLM returns a response, **when** the response streams to the UI, **then** citations referencing specific tool calls or evidence are rendered inline and clickable to expand the underlying evidence (including raw SSH command output where applicable). |
|
||||
| **REQ-031** | Display streaming tool execution traces | J1 | High | **Given** the orchestrator invokes tools, **when** each tool returns, **then** the UI displays the tool name, target, and a snippet of the result in a collapsible trace panel (SSH commands show full command + output snippet). |
|
||||
| **REQ-032** | Persist conversation history per user/tenant | J1 | High | **Given** an Operator completes a conversation, **when** they reload or return later, **then** the conversation is retrievable in full (prompts, tool calls, responses). |
|
||||
| **REQ-033** | Reason about which MCP tools to invoke from user prompt | J1 | High | **Given** an Operator asks a diagnostic question, **when** orchestration runs, **then** the LLM invokes at least one relevant abstract MCP tool (e.g., `get_logs`, `list_resources`, `get_recent_deploys`). |
|
||||
| **REQ-034** | Execute multi-step tool sequences (LLM-driven dynamic workflows) | J1 | High | **Given** a complex question, **when** orchestration runs, **then** the LLM can chain multiple tool calls in sequence, with each result informing the next call. |
|
||||
| **REQ-035** | Apply LLM step limit to prevent infinite loops | J1 | Med | **Given** an LLM attempts >20 tool calls in a single workflow, **when** the limit is reached, **then** the workflow halts with a clear error to the user and partial results are preserved. |
|
||||
| **REQ-036** | Persist workflow state across async boundaries via durable execution | J1 | High | **Given** a workflow exceeds 30 seconds (e.g., long log query, slow SSH execution), **when** orchestration suspends, **then** state is persisted to durable execution store and the workflow can resume. |
|
||||
| **REQ-037** | Allow user to rejoin in-progress async workflow | J1 | High | **Given** a workflow is in progress, **when** the Operator refreshes or returns, **then** they see current workflow state and can resume interaction. |
|
||||
| **REQ-038** | Log every prompt, tool call, SSH command, and response to immutable audit store | J1, J2 | High | **Given** any of these events occur, **when** the audit log write runs, **then** the entry is written to a write-once store with tenant ID, user ID, target ID (for SSH), timestamp, and correlation ID; write failures halt the operation. |
|
||||
| **REQ-039** | Implement Row-Level Security on all tenant-scoped data | J1, J2 | High | **Given** any database query is executed, **when** the query runs, **then** RLS policies enforce tenant scoping and cross-tenant queries return empty results. |
|
||||
| **REQ-040** | Store tenant credentials in centralized secret manager | J2 | High | **Given** any tenant credential (BYOM API key, Proxmox token, SSH key, Git token) is stored, **when** stored, **then** it resides in the centralized secret manager and never in plaintext in application logs or DB rows. |
|
||||
| **REQ-041** | Publish "SOC 2 Type 1 Audit in Progress" posture | J2 | Med | **Given** a prospect visits the security page, **when** they read, **then** the "Audit in Progress" posture is clearly stated with target completion timeline and current control coverage. |
|
||||
| **REQ-042** | Instrument GRC platform with control evidence | J2 | Med | **Given** CoreCI Chat runs in production, **when** the GRC platform syncs, **then** evidence of controls (access logs, change management, vendor risk, incident response) is automatically collected. |
|
||||
| **REQ-043** | Meter tool calls, LLM tokens, and workflow executions per tenant | J1, J2 | High | **Given** a tenant uses CoreCI Chat, **when** usage events occur, **then** counters increment for tool calls, LLM tokens, and workflow executions, and values are queryable per tenant per day. |
|
||||
| **REQ-044** | Display per-tenant usage dashboard to Admin | J2 | Med | **Given** an Admin opens the usage dashboard, **when** the page loads, **then** current period tool calls, LLM tokens, workflow executions, and projected cost are displayed. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Technical Constraints & NFRs (Non-Functional Requirements)
|
||||
|
||||
* **Read-Only by Default:** No write actions permitted at MCP gateway or Relay Agent layers. Threshold: 100% of write-action requests rejected at both layers with HTTP 403 (REQ-018). SSH write commands rejected at Relay Agent (REQ-026).
|
||||
* **BYOM Mandatory:** All LLM inference routed to customer-configured endpoint. Threshold: 100% of inference calls outbound to customer endpoint; zero inference calls originate from CoreCI Chat infrastructure (REQ-008, REQ-009).
|
||||
* **Multi-Tenancy Isolation:** All tenant-scoped data protected by Row-Level Security. Threshold: Zero cross-tenant data exposure verified via quarterly pen tests (REQ-039).
|
||||
* **Audit Log Immutability:** All prompts, tool calls, SSH commands, and responses logged to write-once store. Threshold: 100% capture rate, zero deletes permitted (REQ-038).
|
||||
* **SSH Command Whitelist Enforcement:** Relay Agent enforces command whitelist for all SSH-issued commands. Threshold: 100% of SSH commands checked against whitelist before execution; non-whitelisted commands rejected and audited (REQ-026).
|
||||
* **Supported Target Operating Systems:** Ubuntu 24.04 LTS and Debian 12 (Bookworm) or later. Install script aborts on unsupported OS (REQ-010).
|
||||
* **Supported Proxmox Versions:** Proxmox VE 7.x and 8.x. Older versions explicitly unsupported (REQ-020).
|
||||
* **Time to First Streamed Token:** <3 seconds (p95) from user submission to first visible token.
|
||||
* **Diagnostic Completion Time:** <5 minutes (p95) for typical investigation (≤10 tool calls).
|
||||
* **Concurrent Workflows per Tenant:** ≥5 concurrent investigations per tenant.
|
||||
* **LLM Step Limit:** ≤20 tool calls per workflow.
|
||||
* **Conversation History Retention:** 90 days minimum, 1 year target.
|
||||
* **SaaS Availability Target:** 99.5% uptime MVP, 99.9% target post-MVP.
|
||||
* **Relay Agent Reconnection:** Auto-reconnect within 30 seconds of drop, exponential backoff, max 5 attempts before alerting; systemd auto-restarts on hard failure (REQ-013).
|
||||
* **Browser Support:** Latest 2 versions of Chrome, Firefox, Safari, Edge.
|
||||
* **Rate Limiting:** Token-bucket, 60 req/min per user, 300 req/min per tenant (REQ-019).
|
||||
* **Encryption:** TLS 1.2+ in transit, AES-256 at rest.
|
||||
* **Secret Handling:** All credentials stored in centralized secret manager (AWS Secrets Manager or equivalent); never logged in plaintext (REQ-040).
|
||||
* **Single-Region Deployment:** MVP deploys to one region (us-east-1 default).
|
||||
* **Browser-Only Chat UI:** No mobile apps, no native apps, no CLI, no Slack/Teams in MVP.
|
||||
* **Systemd-Only Relay Agent Distribution:** Distributed exclusively as a systemd service via install script (REQ-010).
|
||||
* **Per-Target Relay Agent Model:** Each Relay Agent registers as one target and queries only its own host. Multi-target environments deploy multiple agents (REQ-012, REQ-026).
|
||||
* **Outbound-Only Network Model:** Relay Agent establishes outbound WebSocket to CoreCI Chat SaaS. No inbound firewall rules required from customer (REQ-011).
|
||||
* **Self-Hosted Gitea Limitation:** CoreCI Chat SaaS requires network access to customer's self-hosted Gitea instance for read-only API calls. Customers must expose the Gitea API endpoint or run a Relay Agent locally.
|
||||
* **Async Durable Execution Runtime:** **Trigger.dev** — best DX for TypeScript-first team, supports long-running workflows, cost-effective at MVP scale.
|
||||
* **Identity Provider:** **WorkOS** — best enterprise SAML/SSO/SCIM coverage at mid-market price point; clean separation from product user model.
|
||||
* **GRC Platform:** **Vanta** — best ecosystem integrations for AWS-native stacks; mature control monitoring.
|
||||
|
||||
---
|
||||
|
||||
## 6. Milestone Plan & Release Gates
|
||||
|
||||
**Test evidence required for Production Release:**
|
||||
|
||||
* [ ] Code coverage ≥ 80% on new modules
|
||||
* [ ] CI/CD pipeline builds successfully (GREEN)
|
||||
* [ ] QA sign-off: 100% of Journey 1 and Journey 2 integration tests pass
|
||||
* [ ] Security/Compliance review approved (audit logging, RLS, secret handling, read-only enforcement, SSH whitelist enforcement verified)
|
||||
* [ ] All Milestone 1, 2, and 3 acceptance gates passed (Section 2.3)
|
||||
* [ ] All 16 Failure & Edge Paths have passing test cases (Section 3.3)
|
||||
* [ ] Load test: ≥5 concurrent workflows per tenant sustained for 1 hour with no degradation
|
||||
* [ ] Pen test: Cross-tenant data leakage test passed (zero leakage)
|
||||
* [ ] SSH whitelist test: Attempt to execute non-whitelisted command (e.g., `rm -rf /tmp/test`) and verify rejection at Relay Agent with audit log entry
|
||||
* [ ] Documentation: Admin onboarding guide, Operator quick-start, security & compliance page, public status page, Relay Agent install guide
|
||||
|
||||
**Pre-Production Design Partner Gate (recommended before GA):**
|
||||
|
||||
* [ ] At least 1 mid-market design partner (ideally existing CoreCI customer) completes full onboarding via Journey 2
|
||||
* [ ] At least 3 distinct Operators across 2 design partners complete ≥10 diagnostic investigations via Journey 1
|
||||
* [ ] Audit logs (including SSH command capture), usage metering, and Relay Agent health verified end-to-end in production-like environment
|
||||
|
||||
---
|
||||
|
||||
## 7. Open Questions & Assumptions
|
||||
|
||||
_Unresolved product or architectural questions. All questions below were resolved and approved by Product Owner on 2026-08-24._
|
||||
|
||||
1. **Async Durable Execution Runtime** — Trigger.dev vs Inngest vs Temporal.
|
||||
* _Decision:_ **Trigger.dev** — best DX for TypeScript-first team, supports long-running workflows, cost-effective at MVP scale.
|
||||
|
||||
2. **Identity Provider** — WorkOS vs Clerk vs Auth0.
|
||||
* _Decision:_ **WorkOS** — best enterprise SAML/SSO/SCIM coverage at mid-market price point; clean separation from product user model.
|
||||
|
||||
3. **GRC Platform** — Vanta vs Drata vs Secureframe.
|
||||
* _Decision:_ **Vanta** — best ecosystem integrations for AWS-native stacks; mature control monitoring.
|
||||
|
||||
4. **Relay Agent Distribution Mechanism** — Install script (curl|bash) vs apt package vs Docker container.
|
||||
* _Decision:_ **Install script (curl|bash) with apt package as fallback** — install script is fastest to ship and most flexible; apt package for design partners that prefer managed distribution. Docker container deferred to v1.1+.
|
||||
|
||||
5. **SSH Command Whitelist Maintenance** — Ship fixed whitelist with Relay Agent vs allow customer extension via config file.
|
||||
* _Decision:_ **Ship fixed whitelist initially; allow customer extension via signed config in v1.1** — fixed whitelist is more secure for v0.1; extension mechanism requires careful design (signed configs, audit logging on whitelist changes).
|
||||
|
||||
6. **Proxmox Permission Model** — Built-in `PVEAuditor` role vs custom role with finer-grained permissions.
|
||||
* _Decision:_ **Built-in `PVEAuditor` for v0.1** — simpler setup, well-understood scope. Custom role support in v1.1 if design partners request specific restrictions.
|
||||
|
||||
7. **Self-Hosted Gitea Access Pattern** — Customer exposes API to SaaS vs Relay Agent on Gitea host.
|
||||
* _Decision:_ **Customer exposes API to SaaS for v0.1** (with documented firewall rules); Relay Agent variant deferred until demand justifies the engineering.
|
||||
|
||||
8. **Vector Store for v1.1 RAG (future-facing, not v0.1)** — PostgreSQL+pgvector vs dedicated (Pinecone, Weaviate).
|
||||
* _Decision:_ **pgvector** — avoids new infrastructure; co-located with primary database.
|
||||
|
||||
---
|
||||
|
||||
## 8. Changelog
|
||||
|
||||
| Version | Date | Author | What Changed | REQs Affected |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| v1.0 | 2026-08-24 | Sarah Chen | Initial Draft generated from Steer Vision v1.0 and locked Phase 2 scope (superseded by v1.1) | REQ-001 – REQ-043 (original) |
|
||||
| v1.1 | 2026-08-24 | Sarah Chen | **Major revision per product owner pivot:** Product renamed CoreCI Chat (companion to existing CoreCI CI/CD platform). Removed all Kubernetes, ArgoCD, and Helm dependencies. Replaced Kubernetes MCP adapter with Proxmox MCP adapter. Removed ArgoCD MCP adapter. Added SSH/Linux Server MCP adapter with command whitelist enforcement at Relay Agent. Replaced ServiceAccount auth with SSH key auth; replaced Helm distribution with systemd service via install script. Renamed "multi-cluster" to "multi-target" scoping. Added supported OS constraint (Ubuntu 24.04, Debian 12+). Added Proxmox version support constraint (7.x, 8.x). Added 16th failure path (unsupported OS). Added 8th open question (self-hosted Gitea access pattern). Total REQs now 44 (was 43). Product Owner set to Sarah Chen. | REQ-010, REQ-011, REQ-012 (Relay Agent deployment), REQ-020 (Proxmox), REQ-021 (SSH/Linux — new), REQ-022/023 (GitHub/Gitea expanded for Actions), REQ-024 (multi-target), REQ-025 (Proxmox auth), REQ-026 (SSH auth + whitelist), REQ-027 (Git auth — new split), REQ-038 (audit log expanded for SSH commands) |
|
||||
|
||||
---
|
||||
|
||||
*End of CoreCI Chat v0.1 Engineering Specification v1.1*
|
||||
|
||||
*Product Owner: Sarah Chen — Locked 2026-08-24 — Ready for ciagent Milestone 1 implementation.*
|
||||
@@ -0,0 +1,3 @@
|
||||
.env
|
||||
.env.secrets
|
||||
.env.*
|
||||
Reference in New Issue
Block a user