docs(P00): research findings

v0.2 RESEARCH stage. Synthesizes the 4-phase v0.2 scope (P01-P04) into
updated static docs. No code changes. Decisions are derived from
CLARIFY D-011..D-018 (already on main) and direct investigation of
go.mod, the codebase, and ecosystem docs (Go 1.25+ iter.Seq, govulncheck,
gosec, gitleaks, step-ca).

Key research conclusions logged here:

- ConnectRPC is NOT in go.mod (.ciagent/config.json lists it in
  frameworks but the dependency was never added). v0.2 falls back to
  stdlib net/http with h2c for the orca.v1.Dispatch service. Zero new
  direct deps. (ARCHITECTURE.md AD-014)

- Roll-our-own CA via crypto/x509 (not step-ca/cfssl/vault-pki) keeps
  the binary single, dependency-free, and aligned with offline-first
  (no external PKI network calls). (ARCHITECTURE.md AD-010)

- govulncheck default mode requires network access to vuln.go.dev. CI
  step must use -format json (always exits 0) + a wrapper that gates
  on findings via jq/cat, OR pre-mirror the database. Caller to decide
  in PLAN. Logged as REQ candidate for IDEATE.

- gosec exit codes: 0 clean, 1 unsuppressed finding. -no-fail always
  returns 0. Baseline JSON via -track-suppressions + exclude=. We
  adopt -no-fail on initial run, baseline suppressed findings, then
  tighten to fail-on-finding once baseline is empty.

- gitleaks default config covers most cases; we extend .gitleaks.toml
  with stopwords for our test data paths and CA cert PEM (which would
  otherwise trigger the generic-api-key rule).

- iter.Seq: yield func(V) bool, iter.Pull for pull-style, range over
  function types since Go 1.25. Cancellation flows through ctx
  (consumer-driven backpressure). Single-use vs multi-use semantics
  documented in Go spec; we use multi-use for repo.Watch() since
  callers can re-iterate.

- mTLS hot-swap via tls.Config.GetCertificate callback enables cert
  rotation without daemon restart. tls.Config is read on every
  handshake; reload picks up new server.crt/server.key.

ARCHITECTURE.md changes:
- Added Transport Layer (internal/transport) and Dispatcher
  (internal/engine/dispatcher.go) components.
- Added Security Manager (internal/security) component with full cert
  lifecycle API.
- Added certs table schema (migration 0004) and Cert Go struct.
- Extended Node with NodeCapacity (CPU/memory) for bin-packing.
- Added v0.2 Component Graph ASCII diagram.
- Added 4 named flows: cert issuance, mTLS handshake, job dispatch,
  iter.Seq streaming.
- Added 8 new AD-009..AD-016 decisions and AD-014 notes the
  ConnectRPC-not-in-go.mod reality.

PERSONAS.md changes:
- Added network-engineer (custom, NEW in v0.2) for transport/dispatcher.
- security-engineer marked phase_specific: [P01, P02] (off after P02).
- network-engineer marked phase_specific: [P02].
- cli-engineer marked phase_specific: [P04] (--watch is a CLI concern).
- data-engineer.territory extended to include
  internal/store/migrations/0004_certs.sql.
- security-engineer.territory extended to TLS-config portion of
  internal/transport.
- Frontmatter updated: active_personas, phase_specific, reason.

PROJECT.md changes:
- Moved "Multi-node scheduling" out of "Out of Scope" (it ships in P02).
- Added "External PKI / Let's Encrypt / cert transparency logs" to
  Out of Scope (per D-011).
- Added "gRPC framework dependency" to Out of Scope (per AD-014).
- Added v0.2 Scope Summary section (4 phases) with cross-refs to
  ARCHITECTURE.md flows.

REQ candidates surfaced for IDEATE stage (not added to REQUIREMENTS.md
in this commit — that's the IDEATE stage's job):
- REQ-cand-A: Bounded cert rotation history (retain last N=3 server
  certs per node for rollback; documented in ARCHITECTURE.md certs
  table as "retention" implication of the schema).
- REQ-cand-B: Trusted-CA fingerprint pinning (D-012 requires operator
  to pass --ca-fingerprint at join; the daemon should refuse to start
  if the on-disk CA's fingerprint doesn't match a config-pinned value,
  to protect against operator typos).
- REQ-cand-C: govulncheck offline mode (CI must not call vuln.go.dev
  by default; either pre-mirror the DB or set GOVULNCHECK_DB env to
  a local file).
- REQ-cand-D: HCL/YAML schema for NodeCapacity declaration (where
  does the operator declare a node's CPU/RAM? Current v0.1 Node model
  has no capacity field. P02 will add this — needs a config file
  surface, e.g. ~/.orca/node.hcl or flag on `orca node join`).
- REQ-cand-E: gitleaks baseline for pre-existing secrets in history
  (the v0.1 .env leak was rotated forward but git history still has
  a SHA-1 leak — gitleaks/git filter-repo remediation may need a
  baseline file to avoid the same class of false positive recurring).
- REQ-cand-F: --watch output format mode (iter.Seq stream is
  table-style by default; users may want --watch --json one-line-per-
  event for piping). P04 scope decision; log for IDEATE.

---ci---
project: orca
phase: 0
milestone: v0.2
status: research
---/ci---
This commit is contained in:
Jon Chery
2026-06-03 20:59:32 +00:00
parent b48f5cfde6
commit 08d321f57f
3 changed files with 461 additions and 63 deletions
+385 -54
View File
@@ -2,66 +2,168 @@
## System Overview
Orca is a single-binary, offline-first orchestration engine. The system consists of three logical components, all compiled into one `orca` binary and selected via subcommands.
Orca is a single-binary, offline-first orchestration engine. The system consists
of three logical layers (CLI, Daemon, Engine) compiled into one `orca` binary
and selected via subcommands. v0.2 adds a **cross-node transport layer** (mTLS)
and a **dispatcher** for multi-node job execution.
```
┌─────────────────────────────────────────────────────────────┐
│ orca (single binary)
├─────────────────────────────────────────────────────────────┤
│ CLI Layer (Cobra) │
│ ├── orca version │
│ ├── orca init
│ ├── orca status
│ ├── orca node {join,leave,list}
── orca job {run,list,stop,logs}
├─────────────────────────────────────────────────────────────┤
Daemon Layer (net/http server)
├── /healthz (liveness)
── /readyz (readiness)
│ ├── /v1/jobs/* (job control API) │
├── /v1/nodes/* (node registry API)
── /v1/tasks/* (task lifecycle API)
├─────────────────────────────────────────────────────────────┤
Core Engine
│ ├── Node Registry (in-memory + SQLite persistence)
│ ├── Task Executor (os/exec with WaitDelay, Go 1.25+)
│ ├── Job Scheduler (single-node for v0.1)
│ └── Audit Logger (log/slog JSON handler)
├─────────────────────────────────────────────────────────────┤
State Store (modernc/sqlite, CGO-free)
~/.orca/orca.db
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────
│ orca (single binary, v0.2)
├─────────────────────────────────────────────────────────────────────────────
│ CLI Layer (Cobra)
│ ├── orca version
│ ├── orca init # local node bootstrap
│ ├── orca cert {init,join,renew,show} # NEW (P01)
│ ├── orca status
── orca node {join,leave,list} # join = mTLS handshake (P01)
│ │ └── orca node list --watch # NEW iter.Seq (P04) │
├── orca job {run,list,stop,logs}
│ └── orca job list --watch # NEW iter.Seq (P04)
── orca daemon
├─────────────────────────────────────────────────────────────────────────────┤
Daemon Layer (net/http over h2c, mTLS in P01)
── /healthz (liveness)
│ ├── /readyz (readiness) │
├── /v1/jobs/* (job control API)
│ ├── /v1/nodes/* (node registry API)
│ ├── /v1/tasks/* (task lifecycle API)
│ ├── /orca.v1.Dispatch/... # NEW (P02) — cross-node dispatch
│ └── /orca.v1.Register/... # NEW (P02) — peer join ack
├─────────────────────────────────────────────────────────────────────────────
Transport Layer (NEW — internal/transport)
├── mTLS client (dialer pool per peer)
│ ├── mTLS server config (TLS 1.3 only, AEAD allowlist) │
│ ├── Retry+backoff (exponential, jittered, capped) │
│ └── Graceful disconnect (ctx-aware Conn.Close) │
├─────────────────────────────────────────────────────────────────────────────┤
│ Core Engine │
│ ├── Node Registry (in-memory + SQLite persistence) │
│ ├── Task Executor (os/exec with WaitDelay, Go 1.25+) │
│ ├── Job Scheduler (single-node FIFO; bin-pack P02) │
│ ├── Dispatcher # NEW internal/engine/dispatcher.go │
│ │ ├── Local decision (does this job fit on this node?) │
│ │ ├── Remote dispatch (POST to peer via transport) │
│ │ └── Streaming callback (iter.Seq[DispatchResult] for CLI) │
│ ├── Security Manager # NEW internal/security (P01) │
│ │ ├── CA lifecycle (init, fingerprint, sign CSR) │
│ │ ├── Server cert lifecycle (issue, renew, rotate) │
│ │ ├── mTLS config builder │
│ │ └── Cert store (filesystem + SQLite metadata) │
│ └── Audit Logger (log/slog JSON handler) │
├─────────────────────────────────────────────────────────────────────────────┤
│ State Store (modernc/sqlite, CGO-free) │
│ ~/.orca/orca.db │
│ ├── nodes, jobs, tasks, audit_log (v0.1) │
│ └── certs # NEW (P01) — CA + server certs │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Component Details
### 1. CLI Layer (`cmd/orca`, `internal/cli`)
- **Framework**: Cobra (industry standard, familiar to operators)
- **Subcommands**: `version`, `init`, `status`, `node`, `job`
- **v0.1 subcommands**: `version`, `init`, `status`, `node`, `job`, `daemon`
- **v0.2 additions (P01)**: `orca cert {init,join,renew,show}`
- **v0.2 additions (P04)**: `--watch` flag on `orca job list` and `orca node list`
- **Output**: Human-readable by default; `--json` flag for machine consumption
- **Discovery**: All subcommands self-document via Cobra's auto-generated help
- **Watch semantics (P04)**: `--watch` consumes `iter.Seq[Job|Node]`, exits on
ctrl-c (via `signal.NotifyContext`), refreshes on internal change events.
### 2. Daemon Layer (`internal/daemon`)
- **Server**: `net/http` with `http.ServeMux` (no external router for v0.1)
- **TLS**: `crypto/tls` with self-signed certs (mTLS-ready)
- **Ports**: Configurable (default `:8443` for API, `:8080` for health)
- **Graceful Shutdown**: `signal.NotifyContext` with SIGINT/SIGTERM
### 3. Core Engine (`internal/engine`)
- **Server**: `net/http` with `http.ServeMux` (no external router)
- **TLS (P01)**: `crypto/tls` with `MinVersion=tls.VersionTLS13` and
AEAD cipher allowlist
(`TLS_AES_256_GCM_SHA384`, `TLS_CHACHA20_POLY1305_SHA256`,
`TLS_AES_128_GCM_SHA256`)
- **Ports**: Configurable (default `:8443` for API+mTLS, `:8080` for health)
- **Graceful Shutdown**: `signal.NotifyContext` with SIGINT/SIGTERM
- **v0.2 endpoints (P02)**:
- `POST /orca.v1.Dispatch/Submit` — receive cross-node job submission
- `POST /orca.v1.Dispatch/Status` — query dispatched job status
- `POST /orca.v1.Register/Hello` — peer join ack (used during `orca node join`)
### 3. Transport Layer (`internal/transport`, NEW in P01/P02)
- **Client**: `http.Client` with `http.Transport.TLSClientConfig` populated
from `internal/security.NewClientTLSConfig`
- **Server**: `http.Server.TLSConfig` populated from
`internal/security.NewServerTLSConfig`
- **Retry policy**: exponential backoff with jitter (start 100ms, x2, cap 5s,
max 5 attempts); only idempotent verbs (`GET`, `HEAD`, `OPTIONS`) are
retried automatically; `POST` retries require an explicit
`X-Orca-Idempotency-Key` header
- **Conn lifecycle**: `context.Context`-aware dials and reads; graceful
`Close` on `ctx.Done()`
- **Peer dial pool**: small `sync.Map` of `peerID → *http.Client` to reuse
TLS handshakes (TCP keep-alive) within a session
### 4. Core Engine (`internal/engine`)
- **Node Registry**: In-memory map of node IDs → metadata, persisted to SQLite
- **Task Executor**: `os/exec.CommandContext` with `WaitDelay` (Go 1.25+) for clean process termination
- **Job Scheduler**: Single-node FIFO queue (multi-node deferred to v0.2+)
(CPU/memory capacity, available slots, last-seen)
- **Task Executor**: `os/exec.CommandContext` with `WaitDelay` (Go 1.25+) for
clean process termination
- **Job Scheduler (v0.2 P02)**:
- **Algorithm**: best-fit bin-packing by `available_cpu` and `available_memory`
- **Within-node ordering**: FIFO queue
- **Cross-node**: if local node is full, `Dispatcher.Submit(peer, job)` is
invoked; peers are tried in round-robin order
- **Fallback**: if all peers reject, return `ErrNoFit` and requeue
- **Dispatcher (NEW, P02)**:
- `Submit(peerID, spec) (jobID, error)` — blocking call with retry
- `Watch(peerID) iter.Seq[DispatchEvent]` — pull-style event stream for the
CLI's `--watch` flag
- Stateless: every call uses the latest mTLS client config and peer address
- **Security Manager (NEW, P01)**:
- `InitCA(commonName) (*CA, error)` — generates a self-signed CA, writes
`ca.crt` (0644) and `ca.key` (0600) to `~/.orca/`
- `Fingerprint(certPath) (sha256hex, error)` — used by `orca cert join`
- `SignServerCert(csr, validity) (*cert, error)` — signs a CSR with the CA
- `IssueServerCert(nodeName, dnsNames, ips) (*cert, *key, error)` — generates
a keypair + CSR + signs it, returns PEM bytes for `orca cert join --server`
- `ServerTLSConfig() (*tls.Config, error)` — loads `server.crt`/`server.key`
and the CA pool from disk
- `ClientTLSConfig(caPath) (*tls.Config, error)` — returns a client config
pinned to the supplied CA
- **Rotation policy**: server certs valid 90d; CA cert valid 10y. On
`orca cert renew`, `IssueServerCert` is called and the daemon
gracefully reloads the in-process `tls.Config` via `GetCertificate`
hot-swap (no restart required)
- **Audit Logger**: `slog.NewJSONHandler(os.Stderr, ...)` with structured fields
### 4. State Store (`internal/store`)
### 5. State Store (`internal/store`)
- **Driver**: `modernc.org/sqlite` (pure Go, CGO-free)
- **Location**: `~/.orca/orca.db` (user-mode) or `/var/lib/orca/orca.db` (system-mode)
- **Schema**: `nodes`, `jobs`, `tasks`, `audit_log` tables
- **Schema (v0.1)**: `nodes`, `jobs`, `tasks`, `audit_log` tables
- **Schema (v0.2 P01)**: NEW `certs` table
```sql
CREATE TABLE certs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT NOT NULL, -- 'ca' | 'server'
node_id TEXT, -- NULL for CA
serial_hex TEXT NOT NULL, -- x509.SerialNumber.Hex()
subject_cn TEXT NOT NULL,
issuer_cn TEXT NOT NULL,
not_before INTEGER NOT NULL, -- unix seconds
not_after INTEGER NOT NULL, -- unix seconds
fingerprint TEXT NOT NULL, -- sha256 of DER, hex
source_path TEXT NOT NULL, -- on-disk PEM path
created_at INTEGER NOT NULL
);
CREATE INDEX idx_certs_node_kind ON certs(node_id, kind);
CREATE INDEX idx_certs_not_after ON certs(not_after);
```
- **Migrations**: Embedded SQL files, applied on startup
- **Migration 0004** is added in P01 with the schema above
## Data Model
### Node
### Node (v0.1, extended in v0.2 P02)
```go
type Node struct {
ID string
@@ -71,10 +173,22 @@ type Node struct {
JoinedAt time.Time
LastSeen time.Time
Metadata map[string]string
// v0.2 P02 — capacity for bin-packing
Capacity NodeCapacity
}
type NodeCapacity struct {
CPUMillicores int // total, e.g. 4000 = 4 cores
MemoryBytes int64 // total RAM
CPUUsed int // currently allocated
MemoryUsed int64 // currently allocated
}
func (c NodeCapacity) AvailableCPU() int { return c.CPUMillicores - c.CPUUsed }
func (c NodeCapacity) AvailableMemory() int64 { return c.MemoryBytes - c.MemoryUsed }
```
### Job
### Job (v0.1)
```go
type Job struct {
ID string
@@ -87,7 +201,7 @@ type Job struct {
}
```
### Task
### Task (v0.1)
```go
type Task struct {
ID string
@@ -104,23 +218,211 @@ type Task struct {
}
```
### Certificate (NEW, v0.2 P01)
```go
type Cert struct {
Kind CertKind // CertCA | CertServer
NodeID string // empty for CA
SerialHex string
SubjectCN string
IssuerCN string
NotBefore time.Time
NotAfter time.Time
Fingerprint string // sha256 of DER (hex)
SourcePath string // PEM path on disk
CreatedAt time.Time
}
```
## v0.2 Component Graph (ASCII)
```
┌─────────────────┐
│ Operator Host │
│ (orca CLI) │
└────────┬────────┘
│ 1. cert join --ca-fingerprint <sha>
┌──────────────────────────────────────────────────────────────────────────────┐
│ NODE A (Bootstrap / CA holder) │
│ │
│ ┌──────────────┐ CSR ┌────────────────────┐ PEM sign ┌────────┐ │
│ │ orca cert │──────────▶│ internal/security │─────────────▶│ CA │ │
│ │ {init,join} │ │ .SignServerCert() │ │ key │ │
│ └──────────────┘ └────────────────────┘ │ 0600 │ │
│ ┌──└────────┘ │
│ ┌─────────────────┐ │ │
│ │ internal/daemon │ ◀──tls.Config── internal/security │ │
│ │ (http.Server) │ .ServerTLSConfig() │ │
│ │ │ │ │
│ │ /v1/jobs/* │ │ │
│ │ /v1/nodes/* │ │ │
│ │ /orca.v1.* │ │ │
│ └────────┬────────┘ │ │
│ │ Submit(job) (bin-pack) │ │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ internal/engine │ │ │
│ │ .scheduler │──── if local fits → executor │ │
│ │ .dispatcher │──── else → Submit(peer, job) ───────────┼──┐ │
│ └─────────────────┘ │ │ │
│ │ │ mTLS │
└──────────────────────────────────────────────────────────────┼──┼───────────┘
│ │
ORCA NODE NETWORK │ │
│ │
┌──────────────────────────────────────────────────────────────┼──┼───────────┐
│ NODE B (Peer) │ │ │
│ │ │ │
│ ┌─────────────────┐ ◀── TLS 1.3 handshake ─────────────────┘ │ │
│ │ internal/daemon │ │ │
│ │ (http.Server) │ POST /orca.v1.Dispatch/Submit │ │
│ │ │──── 200 + jobID │ │
│ └────────┬────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ internal/engine │ │ │
│ │ .scheduler (FIFO) │ │
│ │ .executor │ │
│ └─────────────────┘ │ │
└──────────────────────────────────────────────────────────────────────────────┘
```
## v0.2 Flows
### Flow 1: Cert Issuance (CA-init → CSR → sign → install) — P01
```
Operator (Node A) Operator (Node B)
───────────────── ─────────────────
orca cert init
↳ InitCA("orca-ca")
↳ write ca.crt (0644), ca.key (0600)
↳ record in certs table (kind='ca')
orca cert join --ca-fingerprint <sha>
↳ operator copies ca.crt → Node B
↳ verifies fingerprint matches local
--ca-fingerprint arg
↳ IssueServerCert("node-b", SANs)
↳ generate 2048-bit RSA key
↳ build CSR with SANs
↳ read ca.crt + ca.key
↳ sign CSR (90d validity)
↳ write server.crt (0644),
server.key (0600)
↳ record in certs table
(kind='server', node_id='node-b')
```
### Flow 2: mTLS Handshake at `node join` — P01
```
Node B (joiner) Node A (CA holder)
──────────────── ─────────────────
orca node join --name node-b
--ca-fingerprint <sha>
--peer node-a:8443
↳ load ca.crt → verify sha256 == --ca-fingerprint
↳ load server.crt + server.key
↳ tls.Config{MinVersion: TLS1.3, ...}
↳ ClientHello (SNI=node-a)
◀── ServerHello (TLS 1.3)
◀── Certificate (Node A's cert)
↳ verify Node A's cert chains to ca.crt ◀── CertificateRequest
↳ send Certificate (Node B's cert) ◀── Finished
↳ Finished
↳ GET /healthz (over mTLS) — sanity check
↳ POST /v1/nodes (over mTLS) — register
↳ insert into nodes table
↳ audit log
↳ 200 OK
↳ record node_a in peers table
↳ audit log
```
### Flow 3: Job Dispatch (CLI → dispatcher → peer) — P02
```
User (Node A CLI) Node A (scheduler) Node B (peer)
────────────────── ─────────────────── ─────────────
orca job run spec.hcl
↳ parse HCL
↳ POST /v1/jobs (mTLS, local)
↳ scheduler.Submit(spec)
↳ bin-pack: spec.cpu + spec.mem
↳ local node A has 2000mc + 4GiB free → fit!
↳ executor.Run(spec)
↳ 202 Accepted + jobID
↳ returns jobID
```
```
User (Node A CLI) Node A (scheduler) Node B (peer)
────────────────── ─────────────────── ─────────────
↳ scheduler.Submit(spec)
↳ bin-pack: spec.cpu + spec.mem
↳ local node A has 0 free → NO FIT
↳ dispatcher.Submit(peer="node-b", spec)
↳ load transport.Client("node-b")
↳ POST /orca.v1.Dispatch/Submit
↳ mTLS handshake
↳ authenticate cert
↳ scheduler.Submit(spec)
↳ executor.Run(spec)
↳ 200 OK + jobID
↳ 200 OK + jobID
↳ return jobID to local caller
↳ returns jobID
```
### Flow 4: iter.Seq Streaming (`--watch`) — P04
```
User orca job list --watch
──── ─────────────────────
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
seq := store.Jobs().Watch(ctx)
for job := range seq {
print(job) // human or --json
}
// ctrl-c → ctx.Done() → seq stops yielding
```
Internally `store.Jobs().Watch(ctx) iter.Seq[Job]` polls the
`jobs` table on a 1s ticker (or subscribes to an in-process
notifier channel) and yields the current snapshot of each job
until `ctx.Done()`. The store repo implements `iter.Seq[Job]`
as a function that takes a `yield func(Job) bool` callback.
## Security Architecture
### Authentication
- **v0.1**: mTLS for all API endpoints (self-signed CA)
- **v0.2+**: Token-based auth as alternative
- **v0.2 P01**: Internal CA with CSR join (see Flow 1 + 2)
- **v0.2+**: Token-based auth deferred to v0.3+
### Cert Rotation
- Server certs: 90-day validity, rotate at 60 days (30d before expiry)
- CA cert: 10-year validity, manual rotation
- Hot-swap: `tls.Config.GetCertificate` callback re-reads the
`server.crt`/`server.key` files on each handshake so `orca cert renew`
takes effect without a daemon restart.
### Audit Logging
- All state-changing operations emit structured log records
- Fields: `timestamp`, `actor`, `action`, `resource`, `result`, `error`
- Stored in SQLite `audit_log` table and stderr (JSON)
- **v0.2 P01 additions**: `cert.issued`, `cert.renewed`, `cert.joined`,
`node.handshake_ok`, `node.handshake_failed`
### Input Validation
- All CLI inputs validated via Cobra's `Args`/`ValidArgs` functions
- All API inputs validated at handler boundary
- HCL/YAML specs parsed with strict schemas
## Key Architectural Decisions
## Key Architectural Decisions (v0.1 + v0.2)
| ID | Decision | Rationale |
|----|----------|-----------|
@@ -132,6 +434,14 @@ type Task struct {
| AD-006 | slog for logging | Native to Go 1.21+, no external dependency |
| AD-007 | HCL for job specs | Familiar to Nomad/HashiCorp users |
| AD-008 | Single-node scheduling (v0.1) | Multi-node scheduling deferred to v0.2+ |
| AD-009 | Internal CA, no external PKI (v0.2) | Self-contained, no operational PKI requirement |
| AD-010 | Roll-our-own CA in `crypto/x509` (v0.2) | step-ca/cfssl/vault-pki too heavyweight for Orca's footprint |
| AD-011 | Operator-mediated CA cert distribution (v0.2) | No secret distribution over the wire; matches offline-first |
| AD-012 | TLS 1.3 only, AEAD allowlist (v0.2) | Modern crypto only; no downgrade risk |
| AD-013 | Eager mTLS at `node join` (v0.2) | Fail fast; don't defer handshake to first request |
| AD-014 | `orca.v1.Dispatch` via stdlib h2c (v0.2) | ConnectRPC not in go.mod; stdlib suffices for a single-RPC service |
| AD-015 | Best-fit bin-packing (v0.2) | Simple, deterministic, optimal for small fleets |
| AD-016 | iter.Seq for streaming lists (v0.2) | Go 1.25+ native, context-aware, pull semantics |
## Anti-Patterns (Explicitly Avoided)
@@ -144,9 +454,11 @@ type Task struct {
- No cloud provider integrations
- No auto-scaling
- No admission controllers
- No complex scheduling algorithms
- No complex scheduling algorithms (best-fit only)
- No gRPC framework dependency (stdlib net/http with h2c, Go 1.25+ native)
- No external PKI / no cert transparency logs (offline-first)
## Dependency Map (minimal)
## Dependency Map (minimal — v0.2 adds zero direct deps)
```
github.com/spf13/cobra # CLI framework
@@ -155,18 +467,37 @@ modernc.org/sqlite # SQLite (pure Go)
github.com/google/uuid # UUID generation
```
Total: ~4 direct dependencies. No web framework, no ORM, no RPC framework.
Total: ~4 direct dependencies. No web framework, no ORM, no RPC framework,
no PKI library. mTLS via `crypto/tls` and `crypto/x509` (stdlib).
## Deployment Model
> **ConnectRPC note**: `.ciagent/config.json` lists `connectrpc` in
> `frameworks`, but the actual `go.mod` does not depend on
> `connectrpc.com/connect`. v0.2 falls back to plain `net/http` with
> HTTP/2 cleartext (h2c) for `orca.v1.Dispatch`. The protocol is a
> simple JSON-over-HTTP POST: client sends
> `{"spec": "..."}` to `/orca.v1.Dispatch/Submit`; server replies
> `{"job_id": "..."}`. This keeps the zero-new-dep promise and the
> codebase coherent with the rest of the daemon's `http.ServeMux`.
## Deployment Model (v0.2)
```
User Machine Server Node
────────── ┌──────────────────
orca CLI │─────── mTLS ──────────▶│ orca daemon │
├── API server │
│ │ ├── Engine
└── SQLite store
└──────────┘ └──────────────────┘
Operator Machine Node A (CA holder) Node B (Peer)
──────────────── ───────────────── ─────────────
orca CLI orca daemon orca daemon
│ ▲
│ mTLS handshake │ mTLS │ │
│ at `node join` ────────────┼──┘ │ │
│ │ │ │
│ submit job ───POST────────▶│ POST (cross-node) ──────────▶│
│ │ mTLS only │ │
│ │ │ │
│ ◀───────jobID──────────────│ ◀─────jobID (200 OK)─────────│
│ │ │
│ orca job list --watch │ │
│ (iter.Seq stream) ◀────────│── polls local + stream events│
```
For v0.1, the CLI and daemon can be the same binary on the same machine. Multi-node is deferred.
For v0.2, one node must be the CA holder (`orca cert init` was run
on it). The CA holder's `ca.crt` is copied to each peer manually by
the operator; peers do not auto-fetch it.
+49 -8
View File
@@ -5,10 +5,14 @@ active_personas:
- data-engineer
- cli-engineer
- security-engineer
- network-engineer
deactivated_personas:
- frontend-engineer
- devops-sre
phase_specific: []
phase_specific:
- security-engineer
- network-engineer
- cli-engineer
reason: |
Orca is a CLI-first, offline-first orchestration engine with no web UI and
a single-binary distribution model. The persona roster reflects this:
@@ -17,12 +21,18 @@ reason: |
- backend-engineer: core engine and API handlers
- data-engineer: SQLite state store and migrations
- cli-engineer: Cobra subcommands and CLI UX
- security-engineer: mTLS, audit logging, input validation
- security-engineer: mTLS, cert lifecycle, audit logging, input validation
- network-engineer: transport layer, dispatcher, peer-to-peer resilience
Deactivated:
- frontend-engineer: no web UI in v0.1
- devops-sre: no container/cloud integrations; release flow is
handled by CoreCI (not a persona territory)
Phase-specific (v0.2):
- security-engineer: P01 (mTLS/CA) + P02 (peer transport hardening)
- network-engineer: P02 only (multi-node scheduling & dispatch)
- cli-engineer: P04 only (--watch flag is a CLI concern)
---
# Personas: Orca
@@ -47,7 +57,7 @@ reason: |
- **Domain**: data
- **Frameworks**: `modernc/sqlite`
- **Constraints**: `schema-first`, `migration-safe`, `local-storage-only`
- **Territory**: `**/store/**`, `**/model.go`, `**/migration*`, `migrations/**`
- **Territory**: `**/store/**`, `**/model.go`, `**/migration*`, `migrations/**`, `internal/store/migrations/0004_certs.sql`
- **Active**: true
### cli-engineer (custom)
@@ -60,11 +70,21 @@ reason: |
### security-engineer (custom)
- **Domain**: security
- **Frameworks**: `crypto/tls`, `slog`
- **Constraints**: `no-panic-in-production`, `structured-audit-logging`, `no-secret-in-logs`, `input-validation`
- **Territory**: `**/auth/**`, `**/audit/**`, `internal/security/**`
- **Frameworks**: `crypto/tls`, `crypto/x509`, `slog`
- **Constraints**: `no-panic-in-production`, `structured-audit-logging`, `no-secret-in-logs`, `input-validation`, `least-privilege`
- **Territory**: `**/auth/**`, `**/audit/**`, `internal/security/**`, `internal/transport/**` (TLS config only)
- **Active**: true
- **Reason**: mTLS, audit logging, and input validation are first-class concerns.
- **Phase scope**: P01 (mTLS + internal CA), P02 (transport hardening for peer handshakes). Deactivates after P02 ships — P03/P04 have lighter security needs.
### network-engineer (custom, NEW in v0.2)
- **Domain**: networking
- **Frameworks**: `net/http`, `crypto/tls` (via `internal/security`), `iter`
- **Constraints**: `connection-resilience`, `retry-with-backoff`, `graceful-disconnect`, `context-propagation`
- **Territory**: `**/transport/**`, `**/engine/dispatcher*`, `**/engine/peer*`, `internal/engine/dispatcher.go`, `internal/transport/**`
- **Active**: true
- **Reason**: v0.2 introduces cross-node dispatch and peer-to-peer transport. This persona owns the transport layer, dispatcher, and peer lifecycle concerns that are distinct from the API-handler territory of `backend-engineer`.
- **Phase scope**: P02 only. Deactivates after P02 ships.
### frontend-engineer
- **Active**: false
@@ -80,6 +100,27 @@ reason: |
- **Behavior**: Out-of-territory file changes log a warning but do not block.
- **Rationale**: Allows flexibility during early development; tighten to `strict` post-v0.1.
## Phase-Specific Personas
## Phase-Specific Personas (v0.2)
None for v0.1. All personas persist across all 6 phases.
| Persona | Active in | Reason |
|---------|-----------|--------|
| `security-engineer` | P01, P02 | mTLS/CA in P01, transport hardening in P02. Lighter security needs in P03 (CI scanning) and P04 (streaming UX). |
| `network-engineer` | P02 | Multi-node dispatch is a P02 concern only. P01 builds the transport primitives but P02 wires them into cross-node scheduling. |
| `cli-engineer` | P04 | The `--watch` flag is a CLI surface; P01-P03 don't add new CLI commands. |
In full-autonomy mode, all personas are auto-accepted and the phase-scope
assignments are applied automatically when a phase is committed.
## Migration from v0.1
- `backend-engineer` territory unchanged: `internal/daemon/**` still owns HTTP
handlers. The new `internal/transport/**` package is shared with
`network-engineer` but `transport` owns the *connection lifecycle* (dial,
retry, close) while `daemon` owns the *request handlers*.
- `data-engineer` territory expanded to include the new
`internal/store/migrations/0004_certs.sql` migration in P01.
- `security-engineer` territory extended from `internal/security/**` to
include the TLS-config portion of `internal/transport/**` (the
`NewServerTLSConfig` / `NewClientTLSConfig` helpers).
- `cli-engineer` territory unchanged; the new `orca cert` subcommands in P01
fall under the existing `internal/cli/**` glob.
+27 -1
View File
@@ -48,7 +48,33 @@ Build a lightweight system to manage and execute workloads across a set of nodes
- Full-blown Kubernetes-compatible API.
- Complex cloud-provider integrations.
- GUI-based management consoles.
- Multi-node scheduling.
- Container runtime integration.
- Service mesh / sidecar injection.
- Auto-scaling / horizontal pod autoscaler.
- External PKI / Let's Encrypt / cert transparency logs.
- gRPC framework dependency (ConnectRPC in `config.json` frameworks but
not in `go.mod`; v0.2 uses stdlib `net/http` with h2c for
`orca.v1.Dispatch` — see ARCHITECTURE.md AD-014).
## v0.2 Scope Summary
v0.2 is a focused 4-phase milestone that turns Orca from a single-node
process executor into a small cluster engine with strong transport
security and richer I/O. The 4 phases are:
- **P01 — mTLS handshake + internal CA with CSR join.** Internal CA, CSR
join, eager handshake at `node join`, TLS 1.3 + AEAD allowlist.
See ARCHITECTURE.md Flow 1 + Flow 2.
- **P02 — Multi-node scheduling & job dispatch.** Best-fit bin-packing by
CPU/memory, FIFO within a node, `orca.v1.Dispatch` over mTLS. See
ARCHITECTURE.md Flow 3.
- **P03 — `gosec` + `govulncheck` + `gitleaks` in CI.** `gosec` baseline
JSON in repo, `govulncheck ./...` in `validate` pipeline, `gitleaks`
in pre-commit (opt-in).
- **P04 — `iter.Seq` streaming for `--watch` flags.** Go 1.25+ range-over-func
semantics, `context.Context` cancellation, `signal.NotifyContext` on
ctrl-c. See ARCHITECTURE.md Flow 4.
The vision ("minimalist, offline-first, CLI-first orchestration engine")
is unchanged. v0.2 is a hardening + small-cluster extension, not a
direction change.