diff --git a/.planning/MILESTONES.md b/.planning/MILESTONES.md deleted file mode 100644 index 62a74b015..000000000 --- a/.planning/MILESTONES.md +++ /dev/null @@ -1,52 +0,0 @@ -# Milestones - -Shipped milestones for Hive API Platform. Details archived under `milestones/`. - ---- - -## v1.0 — developer-api-core - -**Shipped:** 2026-04-21 -**Phases:** 1–10 -**Plans:** 49/49 -**Timeline:** 2026-02-23 → 2026-04-21 (58 days) -**Commits:** 580 total, 126 `feat` commits -**Status:** tech_debt — ship-ready with 4 documented deferred items. - -**Delivered:** Full Go rewrite of the prior Hive v1.0 implementation (control-plane + -edge-api in Go 1.24), delivered for efficiency and operational control — lean hot-path -latency, precise `math/big` FX, full source-level control over routing, sanitization, and -billing. OpenAI-compatible developer API gateway with provider-agnostic routing, prepaid -credit ledger, multi-rail BDT/USD checkout, and a developer console. Drop-in compatible -with official OpenAI JS/Python/Java SDKs for chat/completions/responses/embeddings, images, -audio, files, and batches (failure-path). - -**Key accomplishments:** - -1. OpenAI contract fidelity with JS/Python/Java SDK smoke tests, golden fixtures, and Swagger docs (Phase 1). -2. Money-safe immutable credit ledger with reservation/finalize/refund for every request lifecycle (Phase 3). -3. Provider-agnostic routing with capability matrix, cache-aware attribution, and provider-blind errors (Phase 4). -4. Full inference + media surface: chat/completions/responses/embeddings, SSE streaming, images, audio, files, batches (Phases 6–7). -5. Multi-rail BDT/USD checkout with `math/big` FX, BD VAT 15%, and payment-intent state machine (Phase 8). -6. Developer console + Prometheus/Grafana/Alertmanager observability (Phase 9). -7. Supabase Storage migration, KEY-04 per-key attribution, cold-start healthcheck stabilization (Phase 10). - -**Requirements:** 13 satisfied, 2 partial (API-07 batch success-path + KEY-04 success-path attribution — both blocked by upstream provider capability and deferred to v1.1). - -**Known Gaps (deferred to v1.1):** - -- Batch success-path terminal settlement — blocked by LiteLLM file-upload provider matrix; OpenRouter + Groq have no native batch API. Failure-path settlement verified live. See `KNOWN-ISSUE-batch-upstream.md`. -- `ensureCapabilityColumns` targets `route_capabilities` instead of `provider_capabilities` — latent (seed path populates required columns). -- `amount_usd` exposed on BD checkout — regulatory risk. -- Formal VERIFICATION.md for Phases 2 & 3 — UAT/VALIDATION artifacts stand as evidence. -- Phases 11–14 (compliance cleanup, KEY-05 hot-path rate limiting, console integration, invoicing + budget integration). - -**Archive:** - -- `.planning/milestones/v1.0-ROADMAP.md` — full phase + plan breakdown -- `.planning/milestones/v1.0-REQUIREMENTS.md` — requirement traceability at shipping moment -- `.planning/milestones/v1.0-MILESTONE-AUDIT.md` — goal-backward audit -- `.planning/milestones/v1.0-INTEGRATION-CHECK.md` — cross-phase integration report -- `.planning/v1.1-DEFERRED-SCOPE.md` — v1.1 scope definition - -**Tag:** `v1.0` diff --git a/.planning/MVP.md b/.planning/MVP.md deleted file mode 100644 index d1693615d..000000000 --- a/.planning/MVP.md +++ /dev/null @@ -1,51 +0,0 @@ -# Hive MVP Definition, 2026-06-11 - -Decision by orchestrator with owner mandate. Owner sketch: chat UI plus OpenAI spec API plus RAG, cloud and hardware versions from one script, voice dictation if cheap. This doc locks scope. - -## Market read (short) - -No Bangladesh-localized ChatGPT-class product exists with BDT prepaid billing (bKash, SSLCommerz). Global products price in USD with foreign cards, a hard barrier for BD consumers and SMEs. Developers in BD lack an OpenAI-compatible API billable in BDT. RTX Spark class hardware (fall 2026) and DGX Spark (shipping now) create a near-term enterprise self-host story no local player serves. Window: ship cloud MVP before global players localize payments, ship EnterpriseEdge before local system integrators assemble their own. - -## Capability read (what already exists) - -| MVP ingredient | Status | -|---|---| -| OpenAI-compatible API (/v1 chat, embeddings, files, images, audio) | Shipped v1.0 | -| Prepaid BDT billing (bKash, SSLCommerz, Stripe), math/big FX | Shipped v1.0 | -| Chat UI (Open WebUI, admin stripped behind Caddy) | Phase 19, merged | -| Bangla chat UI locale | NOT merged: current OWUI is a pinned upstream image with DEFAULT_LOCALE en; the bn-BD work lived in the dropped LibreChat fork. MVP work item: enable and verify OWUI bn locale | -| Personal RAG (file upload, doc Q&A) | Open WebUI built in, ships with chat | -| Signup abuse protection | PR #166 in review | -| Tool calls for agentic clients | Explicit 400 now (PR #162), real routing lands Phase 20 | -| Provider catalog, model management | Phase 20, plans being written | -| One deploy script cloud plus enterprise | Partial: compose declares only local, test, tools, monitoring, chat profiles. cloud and enterprise profiles are NEW MVP work, not verification of existing ones | - -## MVP scope (locked) - -**Product name framing: one product, two SKUs. Hive Cloud (hosted, BDT prepaid) and Hive EnterpriseEdge (self-hosted, same compose).** - -1. **Chat workstation**: Open WebUI chat with histories, file upload RAG (OWUI native), image input on multimodal models, English UI plus Bangla locale enabled and verified (new work item, see capability read). -2. **Developer API**: OpenAI spec surface as shipped in v1.0, plus capability-based tool call routing (Phase 20) so coding agents and SDK tool use work against OpenRouter tool-capable models. -3. **Billing**: prepaid BDT credits as shipped. No new billing features. -4. **Deploy**: single compose. `cloud` and `enterprise` profiles are new MVP work (today only local, test, tools, monitoring, chat exist). Demo tier: EnterpriseEdge runs on a dev machine with optional Ollama backend in LiteLLM (config only) for the investor demo. Production serving (vLLM or NVIDIA NIM class engine, model hot swap, multi user batching for 100B+ models) is specced in the v1.3 device doc and built post funding when hardware exists. -5. **Stretch (only if zero schedule risk)**: voice input in chat via Open WebUI built-in STT pointed at a server-side faster-whisper container (Whisper large v3 turbo, covers Bangla). Config plus one compose service, size S. - -## Explicitly NOT in MVP - -Web search tool (Phase 26), shared tenant RAG (Phase 22), credit buckets (Phase 21), full admin console pages (Phase 23 beyond existing), Anthropic API surface, MCP connectors, router LLM, model advisor, mobile and desktop apps, on-device capability suite. All tracked in roadmap issues and v1.2/v1.3 docs. - -## Critical path to MVP launch - -1. Merge in-flight PRs (#161 to #167 train). -2. Phase 19 closeout: C4 live JWT verification (needs running stack), M12 CI decision. -3. Phase 20 execution: 5 plans drafted from the Phase 20 brief, plus plan 20-06: capability-based tool call passthrough (issue #118 medium term). -4. Phase 25 chat app re-audit (existing ship gate). -5. Add `cloud` and `enterprise` compose profiles; enable and verify OWUI bn locale. -6. EnterpriseEdge demo on dev machine with Ollama backend config (investor demo). Real GPU hardware verification deferred to post funding. -7. Stretch: whisper STT container. - -## Owner answers (recorded 2026-06-11) - -1. Hardware: none today. Plan is build MVP, demo to investors, buy hardware (DGX Spark class) after funding. Demo tier runs on dev machine. -2. Production domain: hive.scubed.com.bd (Cloudflare cert for *.scubed.com.bd). Turnstile widget already covers it. A dedicated hive domain may come later. -3. Scope lock: pending owner confirmation. diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md deleted file mode 100644 index 52c193fb5..000000000 --- a/.planning/PROJECT.md +++ /dev/null @@ -1,104 +0,0 @@ -# Hive API Platform - -## What This Is - -Hive is a developer-focused, OpenAI-compatible AI gateway and billing platform. **v1.0 developer-api-core is a full Go rewrite of the prior implementation** (control-plane + edge-api in Go 1.24), undertaken for efficiency and operational control: lean hot-path latency, predictable memory, precise `math/big` FX, and full source-level control over routing, sanitization, and billing semantics that the prior stack could not guarantee. Routes requests to internally managed upstream providers such as OpenRouter, Groq, and future providers. Drop-in compatible with official OpenAI JavaScript/TypeScript, Python, and Java SDKs for chat/completions, responses, embeddings, images, audio, files, and batches. Hides upstream provider identity, enforces prepaid credit controls, and provides a developer console for billing, usage, tax/profile data, and API key management. **v1.0 shipped 2026-04-21.** - -## Core Value - -Developers can switch from OpenAI to Hive with only a base URL and API key change, while keeping predictable prepaid billing and provider-agnostic operations — backed by a native Go rewrite of the prior v1.0 stack for efficiency and full operational control. - -## Current State - -**Shipped:** v1.0 developer-api-core (2026-04-21). Phases 1–10, 49 plans, 580 commits. -**Next:** v1.1 — compliance cleanup, hot-path rate limiting, console integration, invoicing + budget integration. Scope in `.planning/v1.1-DEFERRED-SCOPE.md`. - -## Requirements - -### Validated (v1.0) - -- ✓ **OpenAI contract fidelity** — Official JS/TS, Python, Java SDKs work against Hive with only base URL + API key change for the supported launch subset. Unsupported endpoints return OpenAI-style errors. Swagger/OpenAPI docs generated from support matrix. — v1.0 (Phase 1). -- ✓ **OpenAI-compatible text inference + streaming + reasoning** — chat/completions, completions, responses, embeddings with SSE streaming, terminal events, and reasoning-field normalization. — v1.0 (Phase 6). -- ✓ **OpenAI-compatible media + file workflows** — images generation/edits, audio speech/STT/translation, files, uploads, batches (failure-path settlement verified; success-path deferred to v1.1 pending upstream provider capability). — v1.0 (Phase 7 + Phase 10). -- ✓ **Provider abstraction** — Hive-owned aliases, capability matrix, fallback policy, cache-aware usage attribution, provider-blind errors at both edge and control-plane boundaries. — v1.0 (Phase 4 + Phase 10). -- ✓ **Money-safe prepaid credit ledger** — Immutable Postgres ledger, reservations before dispatch, finalize/refund for success/failure/cancel/retry/interrupted-stream paths. Per-key + per-model attribution (KEY-04 edge-level). — v1.0 (Phases 3, 5, 10). -- ✓ **Multi-rail BDT/USD checkout** — Stripe, bKash, SSLCommerz with reproducible FX snapshots, 3% conversion fee on BDT rails, BD VAT 15% tax math, `math/big` precision, payment-intent state machine. — v1.0 (Phase 8). -- ✓ **Developer console + observability** — Billing, invoices, API key management, analytics with Recharts, model catalog, Prometheus + Grafana + Alertmanager monitoring profile. — v1.0 (Phase 9). -- ✓ **Docker-only developer workflow** — Hot reload, code generation, builds, and tests run entirely in containers. No host-installed Go or Node required. — v1.0 (Phase 1). - -### Active (v1.1 target) - -- [ ] **Regulatory compliance on BD checkout** — Remove `amount_usd` and any FX-exposing field from BD-visible payment responses (Phase 11). -- [ ] **Formal verification of authentication + ledger + privacy requirements** — VERIFICATION.md for Phase 2 (AUTH-01..04) and Phase 3 (BILL-01, BILL-02, PRIV-01); live-verify analytics + monitoring (Phase 11). -- [ ] **Hot-path rate limiting** — Edge proxy enforces account-tier + per-key rate limits with 429 + Retry-After; close KEY-02 + KEY-05 (Phase 12). -- [ ] **Console integration fixes** — Web-console proxy routes for checkout modal + API key create/revoke/rotate; close BILL-03, BILL-07, CONS-01, CONS-02, KEY-01, KEY-03 (Phase 13). -- [ ] **Invoice-row + budget threshold integration** — Payment webhook inserts `payment_invoices` rows; budget thresholds enforced on spend/grant paths with real notifier; close BILL-05 + BILL-06 (Phase 14). -- [ ] **RBAC + verification-aware authorization model** — Replace the current `owner`/`member` plus ad hoc gate booleans with a reusable permission model that can express guest, unverified, member, owner, billing, and API-key access consistently across control-plane handlers and web-console routes. -- [ ] **Batch success-path terminal settlement** — Local batch executor in control-plane (fan-out `/v1/chat/completions`, compose output JSONL, settle from per-request usage). Unblocks API-07 success-path + KEY-04 success-path attribution (upstream OpenRouter/Groq have no native batch API). -- [ ] **`ensureCapabilityColumns` wrong-table fix** — Target `provider_capabilities` not `route_capabilities`. Latent since seed path populates columns; code fix removes dead path. - -### Out of Scope - -- ChatGPT-style end-user chat product — defer until API product is stable and validated. -- RAG projects/workspaces — defer until after developer API and billing foundation ship. -- Hosted code runner / dev environment — high-complexity future product area, not part of API launch. -- Subscription plans for launch — prepaid-only at launch, ledger primitives support subscriptions later. -- OpenAI org/admin management endpoints — not part of drop-in developer value proposition. -- Storing prompt or completion bodies by default — conflicts with launch privacy requirement. -- Customer-supplied upstream provider keys — Hive manages provider credentials internally. - -## Context - -v1.0 shipped with: - -- **Codebase:** Go 1.24 control-plane + edge-api, Next.js 15 / React 19 / TS 5.8 web-console, 17 Supabase migrations, 580 commits over 58 days. -- **Infrastructure:** Docker Compose-only local stack (edge-api + control-plane + Redis + LiteLLM + web-console + monitoring profile); Supabase hosted Postgres + auth + object storage (buckets: `hive-files`, `hive-images`). -- **LLM routing:** LiteLLM proxy with OpenRouter + Groq upstreams configured; batch success-path blocked pending upstream support or local batch executor. -- **Payment rails:** Stripe, bKash, SSLCommerz — BDT anchored to XE USD/BDT FX + 3% conversion fee (note: REQUIREMENTS.md originally specified 5%; implementation landed on 3%). -- **Observability:** Prometheus metrics on both Go services (custom registries exclude Go runtime), Grafana dashboards across 4 signal categories, Alertmanager with 3 critical alerts. -- **Compatibility target:** Full public OpenAI surface except org/admin. Reasoning, streaming, usage metering, cache-aware token categories supported where upstream provides them. - -**Known issues as of v1.0 ship (deferred v1.1):** - -- Batch success-path not exercisable with current provider mix. -- `ensureCapabilityColumns` targets wrong table (latent). -- `amount_usd` leaks to BD checkout responses (regulatory). -- Phase 5 rate-limit work incomplete (lifecycle + KEY-04 shipped; full KEY-05 hot-path enforcement carried to Phase 12). - -## Constraints - -- **Compatibility**: Public behavior must track OpenAI API closely enough for drop-in official SDK use — streaming formats, errors, reasoning-related fields. -- **Privacy**: No storing request/response bodies at rest. Retain only operational metadata for billing, support, reliability. -- **Provider abstraction**: Public responses must not reveal upstream provider identity. Provider-blind sanitization enforced at edge + control-plane boundaries. -- **Commercial model**: Prepaid credits at launch; ledger + catalog structured for future subscription bundles resolving to credits. -- **Payments**: Stripe + bKash + SSLCommerz. BDT uses XE-backed FX snapshot + 3% fee. No FX rate or currency-exchange language visible to BD customers (regulatory). -- **FX precision**: `math/big` for all financial calculations — never float64. -- **Storage backend**: Supabase Storage (S3 protocol) only. `edge-api` and `control-plane` fail fast unless S3 env vars present and `hive-files` + `hive-images` buckets exist. -- **Performance**: Lean request-serving hot path, horizontally scalable. Prefer proven OSS components over custom code. -- **Auth & primary DB**: Hosted Supabase for auth, account identity, primary transactional Postgres in v1. -- **Developer workflow**: Entire local dev loop runs in Docker containers. No host-installed Go, Node, or database tooling. -- **Observability**: Capture health + rate-limit + billing + provider metrics without violating no-message-storage rule. - -## Key Decisions - -| Decision | Rationale | Outcome | -|----------|-----------|---------| -| Mirror full public OpenAI API surface except org/admin | Product promise is drop-in compatibility, not partial imitation | ✓ Good — SDK smoke tests for JS/Python/Java validate the contract | -| Prioritize official OpenAI SDK compatibility over custom SDK ergonomics | Existing SDK compatibility minimizes migration cost | ✓ Good — v1.0 ships with zero custom Hive SDK; users change only base URL + key | -| Hide upstream provider identity behind Hive model aliases | Provider abstraction core to customer-facing simplicity and routing flexibility | ✓ Good — provider-blind errors enforced; capability matrix lives internally | -| Launch with prepaid credits, no subscriptions | Simplifies initial revenue mechanics while preserving room for credit-based subscriptions | ✓ Good — ledger + reservation + attribution verified end-to-end | -| Exclude end-user chat, RAG projects, code execution from launch | Keeps first product focused on developer API, billing, control plane | ✓ Good — scope held; shipped on target | -| Avoid storing API prompts/completions at rest | Privacy + operational simplicity > transcript retention | ✓ Good — enforced in code; formal VERIFICATION.md deferred to Phase 11 | -| Hosted Supabase as auth + primary relational data + object storage in v1 | Managed Postgres + auth + S3 primitives with low ops overhead | ✓ Good — v1.0 shipped on single Supabase backend; no MinIO, no separate Postgres | -| Run entire local dev workflow in Docker | Prevents host toolchain drift, keeps onboarding + builds reproducible | ✓ Good — 580 commits delivered without host-installed Go or Node | -| `math/big` for all FX calculations | Prevent float64 corruption on financial math | ✓ Good — BDT rails ship without precision bugs | -| Never show FX rates or currency-exchange language to BD customers | Bangladesh regulatory requirement | ⚠️ Revisit v1.1 — `amount_usd` still leaks in BD checkout response (Phase 11) | -| Internal endpoints at `/internal/*` bypass auth middleware | Service-to-service calls avoid duplicating auth layer | ✓ Good — edge-to-control-plane calls work cleanly | -| `io.Pipe` zero-copy multipart forwarding + binary relay for media | No disk writes for TTS/STT/image passthrough | ✓ Good — shipped in Phase 7 | -| Defer formal Nyquist validation, treat live UAT as verification | Workflow preference; live UAT covers test-first discipline | — Ongoing — all 10 v1.0 VALIDATION.md files remain draft; may revisit for v1.1 | -| Local batch executor over upstream batch API dependency | OpenRouter + Groq have no batch API; LiteLLM managed upload only supports openai/azure/vertex_ai/manus/anthropic | — Pending v1.1 design | -| Phase 5 KEY-05 hot-path limiter deferred to Phase 12 | Lifecycle + KEY-04 attribution covers v1.0 integrator needs; hot-path Lua limiter needs dedicated hardening phase | ✓ Good — v1.0 scope held, Phase 12 owns closure | - ---- - -*Last updated: 2026-04-21 after v1.0 milestone completion — developer-api-core shipped.* diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md deleted file mode 100644 index a0f877fb5..000000000 --- a/.planning/REQUIREMENTS.md +++ /dev/null @@ -1,261 +0,0 @@ -# Hive Requirement Matrix (active) - -**Created:** 2026-04-25 (Phase 11). -**Supersedes:** archived `.planning/milestones/v1.0-REQUIREMENTS.md` for **live** status. -The archive remains the v1.0 ship-gate snapshot (frozen 2026-04-21). - -This file is the active source of truth for v1.0 + v1.1 requirement status. Each -row's `Evidence` column either resolves to an on-disk evidence file under -`.planning/phases/.../evidence/` (Satisfied / Partial) or names the planned -phase target (Pending). The validator -`scripts/verify-requirements-matrix.sh` enforces that every link of the first -form points at an existing file with required frontmatter. - ---- - -## v1.0 Requirements (shipped 2026-04-21) - -### Compatibility & Contract - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| COMP-01 | 01 | Satisfied | Phase 01 (archive — pre-Phase-11 evidence in `milestones/v1.0-REQUIREMENTS.md`) | -| COMP-02 | 01 | Satisfied | Phase 01 (archive — pre-Phase-11 evidence in `milestones/v1.0-REQUIREMENTS.md`) | -| COMP-03 | 01 | Satisfied | Phase 01 (archive — pre-Phase-11 evidence in `milestones/v1.0-REQUIREMENTS.md`) | - -### Inference Surface - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| API-01 | 06 | Satisfied | [evidence/API-01.md](phases/11-verification-cleanup/evidence/API-01.md) | -| API-02 | 06 | Satisfied | [evidence/API-02.md](phases/11-verification-cleanup/evidence/API-02.md) | -| API-03 | 06 | Satisfied | [evidence/API-03.md](phases/11-verification-cleanup/evidence/API-03.md) | -| API-04 | 06 | Satisfied | [evidence/API-04.md](phases/11-verification-cleanup/evidence/API-04.md) | -| API-05 | 10 | Satisfied | Phase 10 (archive — `phases/10-routing-storage-critical-fixes/10-UAT.md` Test 7) | -| API-06 | 10 | Satisfied | Phase 10 (archive — `phases/10-routing-storage-critical-fixes/10-UAT.md` Test 8) | -| API-07 | 10 | Partial | Phase 10 (archive — `phases/10-routing-storage-critical-fixes/KNOWN-ISSUE-batch-upstream.md`); success-path Phase 12 (planned) | -| API-08 | 01 | Satisfied | Phase 01 (archive) | - -### Model Catalog & Routing - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| ROUT-01 | 04 | Satisfied | Phase 04 (archive) | -| ROUT-02 | 10 | Satisfied | Phase 10 (archive — `phases/10-routing-storage-critical-fixes/10-VERIFICATION.md`) | -| ROUT-03 | 04 | Satisfied | Phase 04 (archive) | - -### API Keys & Attribution (v1.0 subset) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| KEY-04 | 10 | Partial | Phase 10 (archive — edge-level reservation attribution verified; success-path attribution unexercisable until API-07 success-path lands) | - -### Authentication & Accounts (Phase 02 — recovered v1.0 satisfied) - -The archived v1.0 matrix listed AUTH-01 / AUTH-02 as "Pending — Deferred v1.1". -Audit on 2026-04-25 (Phase 11 Task 1) confirmed Phase 02 shipped the underlying -code paths (Supabase auth migrations, web-console `/auth/{sign-up,sign-in,forgot-password,reset-password,callback}` -routes, `middleware.ts` session gate, control-plane account/membership -provisioning). Status corrected to **Satisfied** with evidence files below. -AUTH-03 + AUTH-04 remain Pending and route to a future phase — out of scope for -Phase 11. - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| AUTH-01 | 02 | Satisfied | [evidence/AUTH-01.md](phases/11-verification-cleanup/evidence/AUTH-01.md) | -| AUTH-02 | 02 | Satisfied | [evidence/AUTH-02.md](phases/11-verification-cleanup/evidence/AUTH-02.md) | -| AUTH-03 | TBD | Pending | Phase TBD (planned) | -| AUTH-04 | TBD | Pending | Phase TBD (planned) | - ---- - -## v1.1 Requirements — Deferred from v1.0 - -These were scoped to v1.0 originally but reassigned to v1.1 phases. Status -remains **Pending** until the target phase produces an evidence file. - -### Billing & Payments - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| BILL-01 | 11 | Pending | Phase 11 (planned — formal verification artifact deferred to ship-gate audit) | -| BILL-02 | 11 | Pending | Phase 11 (planned — formal verification artifact deferred to ship-gate audit) | -| BILL-03 | 13 | Pending | Phase 13 (planned) | -| BILL-04 | 11 | Pending | Phase 11 (planned — math shipped Phase 08; formal artifact deferred) | -| BILL-05 | 14 | Pending | Phase 14 (planned) | -| BILL-06 | 14 | Pending | Phase 14 (planned) | -| BILL-07 | 13 | Pending | Phase 13 (planned) | - -### API Keys & Rate Limits - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| KEY-01 | 13 | Pending | Phase 13 (planned) | -| KEY-02 | 12 | Pending | Phase 12 (planned) | -| KEY-03 | 13 | Pending | Phase 13 (planned) | -| KEY-05-01 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — RPM bucket per-key + tier scope wired in `apps/edge-api/internal/authz/ratelimit.go` (`CheckWithTier`) | -| KEY-05-02 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — TPM bucket per-key + tier scope wired in `apps/edge-api/internal/authz/ratelimit.go` (`CheckWithTier`) | -| KEY-05-03 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — `X-RateLimit-Limit/Remaining/Reset` emitted by `apps/edge-api/internal/authz/authorizer.go` `rateLimitHeaders` | -| KEY-05-04 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — 429 + `Retry-After` emitted by existing authorizer rejection path | -| KEY-05-05 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — `TierResolver` in `apps/edge-api/internal/authz/tier.go` reads JWT claim `hive_tier` w/ env defaults; Phase 20 swap point preserved | -| KEY-05-06 | 12 | Partial | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — Prometheus alert `deploy/prometheus/alerts/rate-limit.yml` validated by `promtool check rules`. Counter `rate_limit_exceeded_total` emission deferred to follow-up commit before Phase 13; rules are inert until then. | -| KEY-05-07 | 12 | Satisfied | [12-VERIFICATION.md](phases/12-key05-rate-limiting/12-VERIFICATION.md) — owner-gated `/console/api-keys/[id]/limits` page + `RateLimitForm` w/ vitest unit tests | - -### Developer Console - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| CONS-01 | 13 | Pending | Phase 13 (planned) | -| CONS-02 | 13 | Pending | Phase 13 (planned) | -| CONS-03 | 11 | Pending | Phase 11 (planned — chart UIs shipped Phase 09; live-data verification deferred) | - -### Console Integration (Phase 13) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| CONSOLE-13-01 | 13 | Satisfied | [evidence/CONSOLE-13-01.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-01.md) — every console route reachable; 18/21 Green, 2 Phase-14-deferred (fixture-seed flake), 1 Broken-P0 fixed inline | -| CONSOLE-13-02 | 13 | Satisfied | [evidence/CONSOLE-13-02.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-02.md) — `apps/web-console/lib/control-plane/types.ts` re-export shim over canonical `client.ts` interface set | -| CONSOLE-13-03 | 13 | Satisfied | [evidence/CONSOLE-13-03.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-03.md) — strict-TS clean: `tsc --noEmit` exit 0, zero `as any`/`as unknown`/``/`` matches | -| CONSOLE-13-04 | 13 | Satisfied | [evidence/CONSOLE-13-04.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-04.md) — zero customer-surface FX/USD leak in `apps/web-console/{app,components,lib}` (PHASE-17-OWNER-ONLY annotated remnants only) | -| CONSOLE-13-05 | 13 | Satisfied | [evidence/CONSOLE-13-05.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-05.md) — viewer-gates honoured on owner-only routes; vitest 18 tests cover owner / non-owner role matrix | -| CONSOLE-13-06 | 13 | Satisfied | [evidence/CONSOLE-13-06.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-06.md) — auth flows (sign-in/up/forgot/reset/out/callback) green via `__tests__/auth-routes.test.ts` (12 tests) + `tests/e2e/unauth.spec.ts` (5 tests) | -| CONSOLE-13-07 | 13 | Partial | [evidence/CONSOLE-13-07.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-07.md) — workspace switch + invitation accept code paths exercised; workspace-switcher E2E spec fails on pre-existing fixture-seed race (HANDOFF-13-01 → Phase 14) | -| CONSOLE-13-08 | 13 | Satisfied | [evidence/CONSOLE-13-08.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-08.md) — Playwright spec coverage map + 2 new specs (console-billing BDT-only, console-fx-guard whole-console) | -| CONSOLE-13-09 | 13 | Satisfied | [evidence/CONSOLE-13-09.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-09.md) — `tsc --noEmit` + `npm run build` + `npm run test:unit` all exit 0 | -| CONSOLE-13-10 | 13 | Satisfied | [evidence/CONSOLE-13-10.md](phases/13-console-integration-fixes/evidence/CONSOLE-13-10.md) — 6 hand-offs filed (HANDOFF-13-01..06) targeting Phases 14, 17, 18 | - -### Privacy & Operations - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| PRIV-01 | 11 | Pending | Phase 11 (planned — policy enforced in code; formal VERIFICATION.md deferred) | -| OPS-01 | 11 | Pending | Phase 11 (planned — Prometheus/Grafana/Alertmanager shipped Phase 09; live-stack verification deferred) | - ---- - -## v1.1 Requirements (in flight) - -### Routing & Catalog - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| CAP-16-01 | 16 | Satisfied | [evidence/CAP-16-01.md](phases/16-capability-columns-fix/evidence/CAP-16-01.md) | - -### Payments / Budget / Grant (Phase 14) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| PAY-14-01 | 14 | Satisfied | [evidence/PAY-14-01.md](phases/14-payments-budget-grant/evidence/PAY-14-01.md) | -| PAY-14-02 | 14 | Satisfied | [evidence/PAY-14-02.md](phases/14-payments-budget-grant/evidence/PAY-14-02.md) | -| PAY-14-03 | 14 | Satisfied | [evidence/PAY-14-03.md](phases/14-payments-budget-grant/evidence/PAY-14-03.md) | -| PAY-14-04 | 14 | Satisfied | [evidence/PAY-14-04.md](phases/14-payments-budget-grant/evidence/PAY-14-04.md) | -| PAY-14-05 | 14 | Satisfied | [evidence/PAY-14-05.md](phases/14-payments-budget-grant/evidence/PAY-14-05.md) | -| PAY-14-06 | 14 | Satisfied | [evidence/PAY-14-06.md](phases/14-payments-budget-grant/evidence/PAY-14-06.md) | -| PAY-14-07 | 14 | Satisfied | [evidence/PAY-14-07.md](phases/14-payments-budget-grant/evidence/PAY-14-07.md) | -| PAY-14-08 | 14 | Satisfied | [evidence/PAY-14-08.md](phases/14-payments-budget-grant/evidence/PAY-14-08.md) | -| PAY-14-09 | 14 | Satisfied | [evidence/PAY-14-09.md](phases/14-payments-budget-grant/evidence/PAY-14-09.md) | -| PAY-14-10 | 14 | Satisfied | [evidence/PAY-14-10.md](phases/14-payments-budget-grant/evidence/PAY-14-10.md) | -| PAY-14-11 | 14 | Satisfied | [evidence/PAY-14-11.md](phases/14-payments-budget-grant/evidence/PAY-14-11.md) | -| PAY-14-12 | 14 | Satisfied | [evidence/PAY-14-12.md](phases/14-payments-budget-grant/evidence/PAY-14-12.md) | - -### RBAC Matrix (Phase 18) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| RBAC-18-01 | 18 | Satisfied | [evidence/RBAC-18-01.md](phases/18-rbac-matrix/evidence/RBAC-18-01.md) | -| RBAC-18-02 | 18 | Satisfied | [evidence/RBAC-18-02.md](phases/18-rbac-matrix/evidence/RBAC-18-02.md) | -| RBAC-18-03 | 18 | Satisfied | [evidence/RBAC-18-03.md](phases/18-rbac-matrix/evidence/RBAC-18-03.md) | -| RBAC-18-04 | 18 | Satisfied | [evidence/RBAC-18-04.md](phases/18-rbac-matrix/evidence/RBAC-18-04.md) | -| RBAC-18-05 | 18 | Satisfied | [evidence/RBAC-18-05.md](phases/18-rbac-matrix/evidence/RBAC-18-05.md) | -| RBAC-18-06 | 18 | Satisfied | [evidence/RBAC-18-06.md](phases/18-rbac-matrix/evidence/RBAC-18-06.md) | -| RBAC-18-07 | 18 | Satisfied | [evidence/RBAC-18-07.md](phases/18-rbac-matrix/evidence/RBAC-18-07.md) | -| RBAC-18-08 | 18 | Satisfied | [evidence/RBAC-18-08.md](phases/18-rbac-matrix/evidence/RBAC-18-08.md) | -| RBAC-18-09 | 18 | Satisfied | [evidence/RBAC-18-09.md](phases/18-rbac-matrix/evidence/RBAC-18-09.md) | -| RBAC-18-10 | 18 | Satisfied | [evidence/RBAC-18-10.md](phases/18-rbac-matrix/evidence/RBAC-18-10.md) | -| RBAC-18-11 | 18 | Satisfied | [evidence/RBAC-18-11.md](phases/18-rbac-matrix/evidence/RBAC-18-11.md) | - -### Web Search Tool (Phase 26) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| SEARCH-26-01 | 26 | Pending | Phase 26 (planned) — self-hosted SearXNG available only on the internal network | -| SEARCH-26-02 | 26 | Pending | Phase 26 (planned) — `POST /v1/tools/web_search` validates input and normalises results | -| SEARCH-26-03 | 26 | Pending | Phase 26 (planned) — `GET /v1/tools` advertises `web_search` as an OpenAI-compatible function tool | -| SEARCH-26-04 | 26 | Pending | Phase 26 (planned) — guest/unverified users blocked at OWUI and edge-api boundaries | -| SEARCH-26-05 | 26 | Pending | Phase 26 (planned) — verified quota and credited BDT debit paths settle through the prepaid ledger | -| SEARCH-26-06 | 26 | Pending | Phase 26 (planned) — provider-blind errors expose no SearXNG, engine, or internal network detail | -| SEARCH-26-07 | 26 | Pending | Phase 26 (planned) — SDK function-tool roundtrip covered against the real Hive stack | -| SEARCH-26-08 | 26 | Pending | Phase 26 (planned) — EnterpriseEdge packaging includes or explicitly excludes SearXNG | - -### FX/USD Zero-Leak (Phase 17) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| FX-17-01 | 17 | Satisfied | [evidence/FX-17-01.md](phases/17-fx-usd-zero-leak/evidence/FX-17-01.md) | -| FX-17-02 | 17 | Satisfied | [evidence/FX-17-02.md](phases/17-fx-usd-zero-leak/evidence/FX-17-02.md) | -| FX-17-03 | 17 | Satisfied | [evidence/FX-17-03.md](phases/17-fx-usd-zero-leak/evidence/FX-17-03.md) | -| FX-17-04 | 17 | Satisfied | [evidence/FX-17-04.md](phases/17-fx-usd-zero-leak/evidence/FX-17-04.md) | -| FX-17-05 | 17 | Satisfied | [evidence/FX-17-05.md](phases/17-fx-usd-zero-leak/evidence/FX-17-05.md) | -| FX-17-06 | 17 | Satisfied | [evidence/FX-17-06.md](phases/17-fx-usd-zero-leak/evidence/FX-17-06.md) | -| FX-17-07 | 17 | Satisfied | [evidence/FX-17-07.md](phases/17-fx-usd-zero-leak/evidence/FX-17-07.md) | -| FX-17-08 | 17 | Satisfied | [evidence/FX-17-08.md](phases/17-fx-usd-zero-leak/evidence/FX-17-08.md) | -| FX-17-09 | 17 | Satisfied | [evidence/FX-17-09.md](phases/17-fx-usd-zero-leak/evidence/FX-17-09.md) | -| FX-17-10 | 17 | Satisfied | [evidence/FX-17-10.md](phases/17-fx-usd-zero-leak/evidence/FX-17-10.md) | - -Phase 17 closes the v1.1.0 BD regulatory blocker (deferred `amount-usd-on-bd-checkout` -from Phase 11). Customer surfaces across control-plane HTTP, ledger wire DTOs, -web-console DOM, invoice PDF, and chat-app rendered strings carry zero customer-USD/FX keys. -Internal accounting USD path (DB columns + server→Stripe payload) preserved. Lint -`packages/openai-contract/scripts/lint-no-customer-usd.mjs --all` walks Go + TS + -chat-app sources and is wired into CI as a blocking step. Hand-offs emitted to Phase 18 -(HANDOFF-17-01 — RBAC matrix) and Phase 25 (HANDOFF-17-02 — chat-app re-audit -post-Phase-23 i18n bundles). - -CAP-16-01 closes the v1.0 latent bug formerly recorded in `CLAUDE.md` Known -Issues §1 (`ensureCapabilityColumns` targeting `route_capabilities` instead -of `provider_capabilities`). The bug was eliminated by Phase 14's -media-columns work (DDL moved to -`supabase/migrations/20260414_01_provider_capabilities_media_columns.sql`, -which targets `public.provider_capabilities`); Phase 16 formally verifies -that closure with a regression-guard test -(`TestRoutingRepositoryDoesNotRunCapabilityDDL`) and an evidence file. - ---- - -## Out of Scope - -| Feature | Reason | -|---------|--------| -| End-user chat web application | Launch is strictly a developer API + control-plane product. | -| RAG projects or workspaces | Requires separate retrieval, workspace, content-governance semantics. | -| Hosted code runner or dev environment | Separate isolation + cost model from the API launch. | -| Credit subscriptions at launch | Commercial model is prepaid only for v1. | -| Customer-supplied upstream provider keys | Hive manages provider credentials internally; provider identity hidden. | -| OpenAI org/admin management endpoints | Not part of the drop-in developer value proposition. | -| Storing prompt or completion bodies by default | Conflicts with launch privacy requirement. | - ---- - -## v2 Requirements (Out of v1.0 + v1.1) - -- **SDK-01**: First-party branded SDK wrappers for JS/TS, Python, Java. -- **SUBS-01**: Subscription-like credit bundles resolving to Hive Credits. -- **ENT-01**: Org hierarchies, procurement controls, approval workflows. -- **ANAL-01**: Warehouse-backed deep analytics. - ---- - -## Validator - -`scripts/verify-requirements-matrix.sh` parses this file, extracts every -`[label](phases/.../evidence/*.md)` link, asserts the file exists with required -frontmatter (`requirement_id`, `status`, `verified_at`, `verified_by`, -`evidence`), and exits non-zero on any miss. Rows whose Evidence column reads -`Phase NN (planned)` or `Phase NN (archive ...)` are skipped — the former is an -intentional pending marker, the latter points back at archived v1.0 evidence -predating Phase 11. - ---- - -*Active matrix established 2026-04-25 by Phase 11 — Compliance, Verification & -Artifact Cleanup. Archive: `.planning/milestones/v1.0-REQUIREMENTS.md`.* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md deleted file mode 100644 index 461eb8504..000000000 --- a/.planning/ROADMAP.md +++ /dev/null @@ -1,185 +0,0 @@ -# Roadmap: Hive API Platform - -## Milestones - -- ✅ **v1.0 developer-api-core** — Phases 1–10 (shipped 2026-04-21) — see `.planning/milestones/v1.0-ROADMAP.md` -- 🚧 **v1.1 — deferred scope + Hive Chat** — Phases 11–26 (planned) — see `.planning/v1.1-DEFERRED-SCOPE.md` and `.planning/v1.1-chatapp/V1.1-MASTER-PLAN.md` - -## Overview - -Hive v1.0 shipped the developer-API core: OpenAI contract fidelity, prepaid billing -correctness, and provider abstraction. v1.1 closes the regulatory, hot-path-rate-limit, -console-integration, and invoice-row gaps surfaced during v1.0 stabilization, then -layers the Open WebUI-based Hive Chat track on top. - -## Phases - -
-✅ v1.0 developer-api-core (Phases 1–10) — SHIPPED 2026-04-21 - -- [x] Phase 1: Contract & Compatibility Harness (4/4 plans) — 2026-03-29 -- [x] Phase 2: Identity & Account Foundation (7/7 plans) — 2026-03-29 -- [x] Phase 3: Credits Ledger & Usage Accounting (3/3 plans) — 2026-03-30 -- [x] Phase 4: Model Catalog & Provider Routing (3/3 plans) — 2026-03-31 -- [x] Phase 5: API Keys & Hot-Path Enforcement (6/6 plans) — 2026-04-05 (lifecycle + KEY-04; rate-limit carried to Phase 12) -- [x] Phase 6: Core Text & Embeddings API (4/4 plans) — 2026-04-09 -- [x] Phase 7: Media, File, and Async API Surface (4/4 plans) — 2026-04-10 -- [x] Phase 8: Payments, FX, and Compliance Checkout (3/3 plans) — 2026-04-11 -- [x] Phase 9: Developer Console & Operational Hardening (4/4 plans) — 2026-04-11 -- [x] Phase 10: Routing & Storage Critical Fixes (11/11 plans) — 2026-04-21 - -Full breakdown: `.planning/milestones/v1.0-ROADMAP.md` - -
- -### 🚧 v1.1 — deferred scope (Planned) - -- [ ] **Phase 11: Compliance, Verification & Artifact Cleanup** — Remove `amount_usd` from BD checkout, formal VERIFICATION.md for Phases 2 & 3, live-verify analytics/monitoring, close AUTH-01..04, BILL-01/02/04, CONS-03, PRIV-01, OPS-01. -- [ ] **Phase 12: KEY-05 Hot-Path Rate Limiting** — Edge-enforced account-tier + per-key rate limits; close KEY-02 + KEY-05. -- [x] **Phase 13: Console Integration Fixes** — Audit-first web-console integration sweep; FX/USD leak strip on customer-surface `Invoice` interface, strict-TS cast removal, `lib/control-plane/types.ts` re-export shim, BDT-only billing + whole-console FX-guard Playwright specs. Closes CONSOLE-13-01..10. Six hand-offs filed to Phases 14/17/18 (fixture-seed flake, control-plane FX response strip, tier-aware viewer-gates). Shipped 2026-04-27. -- [ ] **Phase 14: Payments, Invoicing & Budget Integration** — Invoice-row creation on payment success + budget threshold enforcement on spend/grant paths; close BILL-05/06. -- [x] **Phase 15: Batch Local Executor** — Local fan-out batch executor in control-plane to settle success-path terminal state for OpenRouter/Groq (no native batch API). Closes deferred tech-debt item #5. Known caveat #2 (resume sentinel) inherited by Phase 18-batch follow-up. -- [x] **Phase 16: Capability Columns Fix** — Remove dead `ensureCapabilityColumns` DDL path in `routing/repository.go` (targeted wrong table); migration `20260414_01_provider_capabilities_media_columns.sql` is authoritative; regression guard `TestRoutingRepositoryDoesNotRunCapabilityDDL` enforces non-recurrence. -- [x] **Phase 17: FX/USD Zero-Leak** — Strip `amount_usd` / FX rate / provider hints from all customer-bound surfaces (payments DTOs, ledger invoice rows, web-console types, PDF rendering, post-purchase grant metadata). Adversarial walk of every `map[string]any` customer wire. CI-blocking lint `lint-no-customer-usd.mjs`. Closes BD regulatory gap. PR #137 ready-for-review 2026-05-09. -- [x] **Phase 18: RBAC Matrix** — Reusable verification-aware permission matrix replacing ad hoc `CanInviteMembers` / `CanManageAPIKeys` / `is_platform_admin` booleans. Roles (member/owner/platform_admin) × named permissions (billing.*, api_keys.*, analytics.*, members.*, workspace.settings.*, grants.create, ledger.view, platform.admin) enforced in control-plane handlers AND mirrored in web-console route/nav gating. Inherits HANDOFF-17-01 (`is_platform_admin` overlay) and Phase 14 stub `internal/platform/role.go`. Blocks v1.1 ship-gate. (completed 2026-05-15) -- [x] **Phase 19: Foundation Slice** — Tenant settings, identity bridge, Open WebUI compose, Caddy admin strip, chat happy path, SOC 2 audit primitive, and Open WebUI nightly/dev-time E2E. Plans 19-01, 19-02, and 19-03 merged (PR #146 merged 2026-05-29). Remaining: M12 (CI live-integration OWUI+Caddy, infra decision pending) and C4 live JWT curl verification (needs running stack) — deferred to Phase 20 kickoff. -- 🚧 **Phase 20: Provider Catalog** — Waves 1-3 shipped (PRs #197, #199, #204, #205, #206): stock providers seeded, custom providers DB-managed (HTTP CRUD + repository + service), LiteLLM YAML regenerated/reloaded, tenant model visibility, tools capability flag. Wave 4 pending. -- [ ] **Phase 21: Credit and Quota Engine** — Tenant pool, per-user soft caps, monthly grants, owner top-ups, extra-usage top-ups, and bucket rate limits. -- [ ] **Phase 22: Shared Knowledge-Base RAG** — Admin-managed tenant KB ingestion, embeddings through LiteLLM, and edge-api retrieval injection. -- [ ] **Phase 23: Admin Console Pages** — Tenant settings, provider management, audit viewer, users/roles, and credit controls in web-console. -- [ ] **Phase 24: EnterpriseEdge Self-Host Packaging** — Bootstrap script, single-tenant defaults, docs, backup/restore, and optional SearXNG inclusion if Phase 26 is kept in v1.1. -- [ ] **Phase 25: Payments Tenant-Gating and Hive Cloud Cutover** — Gate Stripe/bKash/SSLCommerz behind tenant settings and re-audit billing/chat surfaces before cutover. -- [ ] **Phase 26: Web Search Tool** — Append-numbered scope addition. Self-host SearXNG plus `/v1/tools/web_search` and OWUI native web-search. Execute after Phase 21 and before Phase 24/25 if included in v1.1 launch scope. - -Plus four tech-debt items from v1.0 (see `.planning/v1.1-DEFERRED-SCOPE.md`): - -- Batch success-path terminal settlement (local batch executor design). -- `ensureCapabilityColumns` wrong-table fix. -- `amount_usd` BD checkout removal. -- Formal VERIFICATION.md artifacts for Phases 2 + 3. - -## Phase Details - -### Phase 11: Compliance, Verification & Artifact Cleanup - -**Status:** Undecided, 0 plans drafted, needs owner decision: execute or drop. Flagged 2026-06-10. - -**Goal:** Close the regulatory gap in BD checkout responses, formally verify orphaned Phase 2-3 requirements, and update stale planning artifacts. -**Depends on:** Phases 2, 3, 5, 8 -**Requirements:** [AUTH-01, AUTH-02, AUTH-03, AUTH-04, BILL-01, BILL-02, PRIV-01, BILL-04, CONS-03, OPS-01] -**Gap Closure:** Closes integration gaps #4 (amount_usd exposed, resolved Phase 17) and #5 (ViewerAccount.slug empty). Formally verifies 7 orphaned requirements. Live-verifies analytics and monitoring. Updates stale planning artifacts. -**Success Criteria** (what must be TRUE): - 1. BD checkout responses never include `amount_usd` or any field exposing FX rates. - 2. ViewerAccount.slug is populated from control-plane viewer endpoint. - 3. 02-VERIFICATION.md exists and formally verifies AUTH-01 through AUTH-04. - 4. 03-VERIFICATION.md exists and formally verifies BILL-01, BILL-02, and PRIV-01. - 5. REQUIREMENTS.md checkboxes for KEY-02 and KEY-04 are checked. Phase 5 ROADMAP progress is accurate. - 6. Live analytics charts render correct data; batch completeness is verified end-to-end. - 7. Prometheus, Grafana, and Alertmanager are verified live against the running stack. - -Plans: 0 plans - -### Phase 12: KEY-05 Hot-Path Rate Limiting - -**Goal:** Complete the last unsatisfied requirement — account-tier and per-key rate limits enforced on the hot path. -**Depends on:** Phase 5 -**Requirements:** [KEY-05, KEY-02] -**Gap Closure:** Closes KEY-05 (rate limiting) and KEY-02 (media/batch auth policy bypass). Re-verifies current implementation state and fills remaining hot-path gaps. -**Success Criteria** (what must be TRUE): - 1. Edge proxy enforces account-tier rate limits before dispatch. - 2. Edge proxy enforces per-key rate limits before dispatch. - 3. Rate-limited requests receive 429 with Retry-After header. - 4. Rate limit configuration flows from control-plane snapshot to edge enforcement. - 5. Phase 5 VERIFICATION.md marks KEY-05 as SATISFIED. - 6. Image, audio, and batch auth adapters pass a non-empty model and correct estimated credits to the policy engine — allowlist, budget, and quota scoring apply. - -Plans: 0 plans - -### Phase 13: Console Integration Fixes (SHIPPED 2026-04-27) - -**Goal:** Audit-first console integration sweep — strip customer-surface FX/USD leak, eliminate unsafe TypeScript widening casts, add canonical control-plane types re-export shim, lock regression with BDT-only billing + whole-console FX-guard Playwright specs. -**Depends on:** Phases 8, 9, 12 -**Requirements:** [CONSOLE-13-01..10] -**Gap Closure:** Removes `Invoice.amount_usd` from customer-facing type/decoder (P0 regulatory). Replaces unsafe `as { rails?: unknown }` cast with structural guard. Adds `lib/control-plane/types.ts` re-export shim. Files Phase 14/17/18 hand-offs (HANDOFF-13-01..06) for fixture-seed flake, control-plane FX response strip, tier-aware viewer-gates, discretionary credit-grant UI. The original BILL-03/BILL-07/CONS-01/CONS-02/KEY-01/KEY-03 remain Pending — re-routed to a future phase. -**Outcome:** - 1. `Invoice` interface and decoder strip `amount_usd`; runtime fallback to `"USD"` removed (now treated as decode failure). - 2. Strict-TS cleanliness: zero `as any` / `as unknown` / `` / `` matches in `apps/web-console/{app,components,lib}`. - 3. New `tests/e2e/console-billing.spec.ts` and `tests/e2e/console-fx-guard.spec.ts` lock the BDT-only customer surface across 9 console routes. - 4. New `tests/unit/invoice-decode.test.ts` enforces type-level + runtime FX-leak guard, including optional-field reintroduction. - 5. CONSOLE-13-01..10 satisfied with evidence files; six Phase 14/17/18 hand-offs filed. - -Plans: 0 plans - -### Phase 14: Payments, Invoicing & Budget Integration - -**Goal:** Wire the two missing backend accounting integrations: invoice row creation on payment success and budget threshold enforcement on spend/grant paths. -**Depends on:** Phases 8, 9, 13 -**Requirements:** [BILL-05, BILL-06] -**Gap Closure:** Closes integration gaps #7 (invoice not created after payment) and #8 (budget threshold not enforced). -**Success Criteria** (what must be TRUE): - 1. Payment webhook success handler inserts a `payment_invoices` row; invoice appears in console list and PDF download. - 2. Credit spend paths call budget threshold check; threshold breach triggers notifier. - 3. Credit grant paths call budget threshold check after top-up. - 4. Notifier sends an actual notification (email or webhook) — not log-only. - 5. Budget threshold alert banner appears in console when threshold is breached. - -Plans: 0 plans - -### Phase 18: RBAC Matrix - -**Goal:** Replace ad hoc workspace-role + `is_platform_admin` + `CanInviteMembers` / `CanManageAPIKeys` derived booleans with a reusable, verification-aware authorization model (roles × named permissions) enforced authoritatively in the control-plane and mirrored in the web-console route/nav gating layer. -**Depends on:** Phases 2 (identity), 5 (API keys), 9 (console), 13 (viewer-gates seed), 14 (`platform.IsWorkspaceOwner` / `IsPlatformAdmin` Phase 14 stub), 17 (HANDOFF-17-01). -**Requirements:** [RBAC-18-01, RBAC-18-02, RBAC-18-03, RBAC-18-04, RBAC-18-05, RBAC-18-06, RBAC-18-07, RBAC-18-08, RBAC-18-09, RBAC-18-10, RBAC-18-11] -**Gap Closure:** Closes v1.1 ship-gate item `rbac_matrix`. Replaces the latent gap surfaced in v1.1-DEFERRED-SCOPE.md item #8 (authorization is limited to workspace membership roles plus feature-specific booleans). Inherits HANDOFF-17-01 — `is_platform_admin` becomes a derived attribute of the new model rather than a free-standing flag. -**Success Criteria** (what must be TRUE): - 1. A single Go authz package defines an explicit `MembershipRole` (`member`, `owner`) + `IsAdmin` overlay, an explicit `Permission` enum (billing.view, billing.write, api_keys.read, api_keys.write, analytics.view, members.invite, members.manage, workspace.settings, grants.create, ledger.view, platform.admin), and a `Policy.Can(actor, permission)` decision function with per-permission `RequiresVerified` flag. - 2. Every control-plane handler that today checks `viewer.EmailVerified && chosen.Role == "owner"` or `IsPlatformAdmin` (or equivalent ad hoc) routes through the policy package — no direct role/flag comparison in business code (CI lint enforces). - 3. `apps/web-console/lib/viewer-gates.ts` exports a single `can(viewer, permission)` helper derived from the same matrix via codegen'd `Permission` union type; `canInviteMembers` / `canManageApiKeys` / `allowedUnverifiedRoutes` are **removed** (not aliased); consumers (sidebar nav, route guards) use the new helper. - 4. Regression coverage: Go integration tests assert that an unverified actor cannot access billing, api_keys, analytics, members, or workspace.settings handlers; a verified member can access member-scoped reads (analytics.view, ledger.view) but not owner-only writes; an owner can access workspace-scoped surfaces; a platform_admin can access platform-scoped surfaces. - 5. Web-console: vitest unit tests assert `can()` returns the same decisions for every (role, verified, perm) tuple as the Go matrix; one Playwright spec covers the unverified flow on `/console/billing` and `/console/api-keys` (must redirect or block). - 6. STATE.md `v1_1_ship_gate.rbac_matrix` flipped to `true`. Pending todo `2026-04-22-design-rbac-authorization-model.md` resolved to `done`. - -Plans: 7 plans (single `PLAN.md` with 7 plans across 5 waves) - -- [ ] Plan 01 (Wave 1) — authz package + matrix test + codegen + lint scaffolds [RBAC-18-01, RBAC-18-04, RBAC-18-07] -- [ ] Plan 02 (Wave 2) — wire authz middleware in main.go + ActorResolver [RBAC-18-01, RBAC-18-02] -- [ ] Plan 03 (Wave 2) — backend handler migration across 8 modules [RBAC-18-02, RBAC-18-08] -- [ ] Plan 04 (Wave 3) — viewer wire flip (drop gates, emit permissions:[]) [RBAC-18-03] -- [ ] Plan 05 (Wave 4) — viewer-gates.ts rewrite + 4 FE consumers + parity vitest [RBAC-18-05, RBAC-18-06, RBAC-18-09] -- [ ] Plan 06 (Wave 4) — Playwright unverified spec + CI lint/codegen wiring [RBAC-18-07, RBAC-18-10] -- [ ] Plan 07 (Wave 5) — REQUIREMENTS rows, evidence files, VERIFICATION.md, STATE flip, todo resolve [RBAC-18-11] - -## Progress - -| Phase | Milestone | Plans Complete | Status | Completed | -|-------|-----------|----------------|--------|-----------| -| 1. Contract & Compatibility Harness | v1.0 | 4/4 | Complete | 2026-03-29 | -| 2. Identity & Account Foundation | v1.0 | 7/7 | Complete | 2026-03-29 | -| 3. Credits Ledger & Usage Accounting | v1.0 | 3/3 | Complete | 2026-03-30 | -| 4. Model Catalog & Provider Routing | v1.0 | 3/3 | Complete | 2026-03-31 | -| 5. API Keys & Hot-Path Enforcement | v1.0 | 6/6 | Complete | 2026-04-05 | -| 6. Core Text & Embeddings API | v1.0 | 4/4 | Complete | 2026-04-09 | -| 7. Media, File, and Async API Surface | v1.0 | 4/4 | Complete | 2026-04-10 | -| 8. Payments, FX, and Compliance Checkout | v1.0 | 3/3 | Complete | 2026-04-11 | -| 9. Developer Console & Operational Hardening | v1.0 | 4/4 | Complete | 2026-04-11 | -| 10. Routing & Storage Critical Fixes | v1.0 | 11/11 | Complete | 2026-04-21 | -| 11. Compliance, Verification & Artifact Cleanup | v1.1 | 0/0 | Planned | - | -| 12. KEY-05 Hot-Path Rate Limiting | v1.1 | 0/0 | Planned | - | -| 13. Console Integration Fixes | v1.1 | n/a | Complete | 2026-04-27 | -| 14. Payments, Invoicing & Budget Integration | v1.1 | 0/0 | Planned | - | -| 15. Batch Local Executor | v1.1 | n/a | Complete | 2026-04-?? | -| 16. Capability Columns Fix | v1.1 | n/a | Complete | 2026-04-25 | -| 17. FX/USD Zero-Leak | v1.1 | n/a | Complete | 2026-05-09 (PR #137) | -| 18. RBAC Matrix | v1.1 | Complete | 2026-05-15 | - | -| 19. Foundation Slice | v1.1 | 3/4 | Complete (deferred: M12, C4) | 2026-05-29 | -| 20. Provider Catalog | v1.1 | waves 1-3 | In Progress (wave 4 pending) | - | -| 21. Credit and Quota Engine | v1.1 | TBD | Planned | - | -| 22. Shared Knowledge-Base RAG | v1.1 | TBD | Planned | - | -| 23. Admin Console Pages | v1.1 | TBD | Planned | - | -| 24. EnterpriseEdge Self-Host Packaging | v1.1 | TBD | Planned | - | -| 25. Payments Tenant-Gating and Hive Cloud Cutover | v1.1 | TBD | Planned | - | -| 26. Web Search Tool | v1.1 | scaffold | Drafted | - | - ---- - -*Last updated: 2026-06-11 — Phase 20 waves 1-3 shipped (PRs #197, #199, #204, #205, #206); wave 4 pending. Phase 19 closed (PR #146 merged 2026-05-29). Phase 26 web-search scope in v1.1 sequence; Open WebUI pivot authoritative in `.planning/v1.1-chatapp/V1.1-MASTER-PLAN.md`.* diff --git a/.planning/SECURITY-P0-BACKLOG.md b/.planning/SECURITY-P0-BACKLOG.md deleted file mode 100644 index 92f991e9d..000000000 --- a/.planning/SECURITY-P0-BACKLOG.md +++ /dev/null @@ -1,83 +0,0 @@ -# Security P0/P1 Backlog — Triage & Progress (#106–#120) - -Started 2026-05-29 after Phase 19 (PR #146) merged. User directive: tackle the -P0/P1 security backlog before continuing the GSD roadmap (Phase 20+), because -these block any real launch of the metered-inference reselling product. - -## Status - -| Issue | Title | Status | PR / Note | -|-------|-------|--------|-----------| -| #109 | Hardcoded LiteLLM master key fallback | ✅ fixed | PR #150 | -| #110 | edge-api no HTTP timeouts / body limit | ✅ fixed | PR #150 | -| #114 | FX math/big cast to float64 | ✅ fixed | PR #150 | -| #115 | amount_usd leaked in BD checkout | ✅ already fixed (Phase 17) | closed, no code change | -| #117 | No email-verify gate on API key creation | ✅ already fixed (Phase 18 RBAC) | closed, no code change | -| #119 | getSession() server-side auth (3 sites) | ✅ fixed | PR #151 | -| #120 | Email-verify only in layout, not middleware | ✅ fixed | PR #151 | -| #106 | Credit reservation TOCTOU double-spend | ✅ merged | PR #154 | -| #107 | No RLS on tenant tables | ✅ merged | PR #155 — `20260529_01_rls_tenant_tables.sql`; live anon→0 verify at deploy | -| #108 | /internal/* control-plane endpoints unauth | ✅ merged | PR #156 — X-Internal-Token shared-secret middleware (fails closed) | -| #111 | CONTROL_PLANE_BASE_URL leaked into HTML | ✅ FIXED | PR open — server-side Route Handler proxy; form action now relative | -| #112 | SUPABASE_SERVICE_ROLE_KEY silent failure on edge | ✅ FIXED | PR open — authed control-plane `POST /api/v1/.../email-verification/finalize`; edge forwards session bearer only, service-role key off the edge | -| #113 | Auth snapshot 1hr cache lets revoked keys work | ⏳ TODO | see below | -| #51 | Redis rate-limit fail-open bypass | ✅ FIXED | PR #160 — fail-closed default + explicit `RATE_LIMIT_FAIL_OPEN` opt-in + structured degraded log | -| #116 | Free-tier abuse (CAPTCHA / IP limit / disposable email) | ⏳ TODO | needs Turnstile infra | - -## Remaining — implementation notes (pre-investigated) - -### #106 Credit TOCTOU (HIGHEST severity — financial) — ✅ FIXED -- File: `apps/control-plane/internal/accounting/service.go` `CreateReservation` + `ExpandReservation` (both were vulnerable). -- Bug: `GetBalance` and the `ReserveCredits` hold ran in separate txns; N concurrent requests read the same balance, all pass `enforcePolicy` → N× over-reserve. -- Fix shipped: new `AccountLocker` abstraction (`lock.go`, `pglock.go`). The balance-read → policy → reservation-hold critical section now runs inside `WithAccountLock` for both `CreateReservation` and `ExpandReservation`. Production wiring (`cmd/server/main.go`) installs `PgxAccountLocker` = `pg_advisory_lock(hashtext(account_id)::int8)` on a dedicated pooled conn, held across the section, always released — cross-process safe. `NewService` defaults to an in-process locker for single-instance/tests. -- **Deliberately NOT added** the ledger non-negative CHECK/trigger the issue suggested: `PolicyModeTemporaryOverage` intentionally lets available balance go negative within a buffer, so a hard DB constraint would break the overage policy. Policy stays in Go where the buffer logic lives; the advisory lock is the correctness mechanism. -- Tests: `service_concurrency_test.go` — `TestCreateReservationSerializesConcurrentReservations` (acceptance: balance=1000, reserve=50, 100 concurrent → ≤20 succeed, balance never negative), `TestCreateReservationAcquiresAccountLock` (deterministic: lock taken exactly once, keyed on account), `TestNoopLockerOverReserves` (discrimination). Verified: `go build` + `go vet` + `go test -race` (full control-plane suite) → exit 0. -- Follow-up (v1.1 hardening, optional): live ~100-RPS smoke against a real DB to exercise `PgxAccountLocker` end-to-end (unit suite covers the serialization logic via the in-process locker). - -### #107 RLS on tenant tables (largest surface) — ✅ PR #155 -- Migration `supabase/migrations/20260529_01_rls_tenant_tables.sql`. -- **Schema is bifurcated**: Phase 19 `tenant_*` already had RLS (20260518_04). This migration covers the LEGACY `account_*` family — 34 tables incl `accounts`, `account_*`, `api_key*`, `credit_*`, `payment_*`, `invoices`, `budgets`, `spend_alerts`, `request_attempts`, `usage_events`, `fx_snapshots`, `files`/`uploads`/`upload_parts`/`batches`/`batch_lines`. -- **Mechanism (final, after review)**: each table gets `ENABLE`+`FORCE` RLS + `CREATE POLICY _service_role_all FOR ALL TO hive_app USING(true) WITH CHECK(true)` — mirrors the Phase 19 audit RLS. The app connects as the **non-BYPASSRLS `hive_app`** role (documented in 20260518_04), so the explicit hive_app policy is REQUIRED; a postgres pooler role would also bypass. NO `authenticated` SELECT policy (web-console has zero direct PostgREST reads; a member SELECT would leak `api_keys.token_hash` since RLS is row- not column-level). ⇒ anon/authenticated read 0 rows on every tenant table. -- Acceptance SQL (run post-deploy as anon): `select count(*) from public.credit_ledger_entries;` → 0; control-plane (hive_app) reads/writes unaffected. - -### #108 /internal/* shared-secret auth — ✅ PR open -- Control-plane: new `RequireInternalToken` middleware (`internal/platform/http/internalauth.go`, `crypto/subtle` constant-time compare of `X-Internal-Token`). Wired in `router.go` over every `/internal/*` route (catalog/routing/accounting/usage/apikeys) and in `filestore.RegisterRoutes` (files/uploads/batches — now takes a `gate` wrapper). Config: `CONTROL_PLANE_INTERNAL_TOKEN` (`config.go`); `main.go` logs a loud warning when unset. -- Edge-api: new `internal/cpauth` helper (`SetHeader`) attaches `X-Internal-Token` from `CONTROL_PLANE_INTERNAL_TOKEN`; applied to ALL 7 control-plane callers (authz client + resolver, routing, accounting, catalog, files×3, batches×2). -- Fail-mode: token unset ⇒ pass-through + startup warning (no CD breakage during rollout); set on both apps ⇒ enforced. `.env.example` + dev/staging compose updated (passthrough); **ops action: seed `CONTROL_PLANE_INTERNAL_TOKEN` in `/opt/hive/.env` to activate enforcement in staging/prod**. -- Tests: `internalauth_test.go` (4 cases), `cpauth_test.go` (2 cases). build+vet+test both apps → exit 0. - -### #113 revoked-key cache invalidation — ✅ FIXED (PR open) -- Was stated as "revoke doesn't invalidate", but recon found active invalidation ALREADY wired and correct: control-plane `apikeys.Service` calls `invalidateSnapshot(tokenHash)` on revoke/disable/rotate, deleting `snapshotRedisKey = "auth:key:{}"` — byte-identical to the edge cache key (`authz/client.go:91`) — on the shared Redis (`main.go:257` `NewRedisSnapshotCache(redisClient)`). So revoke cutoff is already ~immediate. -- Remaining gap = the **backstop**: edge set the snapshot TTL to **1h**, so if the active DELETE is ever missed (transient Redis error, instance divergence) a revoked key could authorize for up to an hour. Fixed by lowering the edge snapshot TTL to **60s** (`const snapshotTTL = 60 * time.Second`, `authz/client.go`). Acceptance (revoked → 401 within ≤60s across instances) now holds even in the worst case. -- Verified: edge authz unit test asserts the cached-snapshot Set TTL is a positive ≤60s value. (Go verification is via CI "Go tests (edge-api)" — local toolchain output is unobservable in this env.) - -### #112 service-role key on Cloudflare Worker edge route — ✅ FIXED (PR open) -- Was: `apps/web-console/app/auth/callback/route.ts` guarded admin write (`if process.env.SUPABASE_SERVICE_ROLE_KEY` + `.catch(()=>undefined)`) silently skipped on Workers when the key was missing → users stuck unverified, plus a god-key in a public edge bundle. -- Fixed by an **authenticated** control-plane endpoint `POST /api/v1/accounts/current/email-verification/finalize` (`internal/identity`). Edge forwards only the user session bearer (no service-role key, no internal token on the edge). The control-plane flips `hive_email_verified` via its service-role DB pool and **only when `email_confirmed_at IS NOT NULL`** (Supabase already confirmed) — a caller cannot self-verify an unconfirmed address. Write errors are loud 500 (never a silent no-op); 0 rows → 409. -- Deviation from original plan (internal `finalize-signup` endpoint): chose an authed `/api/v1` endpoint instead, so the edge holds neither the service-role key nor the #108 internal token — strictly less privilege on the public edge. The session bearer is itself the proof of email ownership (issued post code-exchange). -- Ops: remove `SUPABASE_SERVICE_ROLE_KEY` from the web-console/Cloudflare env; ensure `CONTROL_PLANE_BASE_URL` points at the public control-plane URL there. - -### #111 CONTROL_PLANE_BASE_URL in HTML — ✅ FIXED (PR #157) -- Was: `apps/web-console/app/console/members/page.tsx` invite `
` inlined the internal URL into rendered HTML and POSTed cross-origin without the session bearer. -- Fixed by server-side Route Handler `app/api/console/members/route.ts` (auth-check → `createInvitation()` helper attaches bearer + base URL server-only → 303 redirect). Form action is now relative `/api/console/members`. Errors map to generic status-class messages (no raw upstream text in URL); redirect resolves against canonical origin (`lib/http/origin.ts`). -- Follow-up (separate): no invite mailer exists in repo — the acceptance token returned by the control-plane is not yet delivered to invitees. Tracked outside #111 (security scope). - -### #116 free-tier abuse -- Cloudflare Turnstile on sign-up/sign-in (CLOUDFLARE_API_TOKEN already present), Supabase per-IP signup limit, disposable-domain blocklist, gmail +tag normalization, cap free credits per verified identity. -- Larger, multi-surface; needs product input on free-credit policy. - -## Recommended next order -1. ✅ Merge #150 + #151 + #152 + #153 (done). -2. ✅ #106 (TOCTOU) — MERGED (PR #154). -3. ✅ #107 (RLS) — PR #155 open (CI green, threads resolved). -4. ✅ #108 (internal auth) — PR open (`fix/108-internal-endpoint-auth`). -5. ✅ #111 (URL leak) — FIXED (server-side Route Handler proxy `app/api/console/members/route.ts`). -6. ✅ #112 (service-role) — FIXED (authed control-plane finalize endpoint; service-role off the edge). -7. ✅ #113 (revoked cache) — FIXED (active invalidation already wired; lowered edge backstop TTL 1h→60s). -8. ✅ #51 (P0) — Redis rate-limit fail-open bypass — FIXED (PR #160). Then #116 (abuse) **NEXT**. - -### #51 rate-limit fail-open — ✅ FIXED (PR #160) -- Was: `apps/edge-api/internal/authz/authorizer.go` Authorize() limiter-error branch was **empty** → any Redis error (outage/connection/auth) silently bypassed ALL rate + fraud-window (5h/weekly) enforcement, no request-boundary signal. (Issue cited pre-rewrite TS `redis-rate-limiter.ts`; live Go path had the identical bug.) -- Fixed: default **fail CLOSED** (deny with retryable 429 `rate_limit_error`/`rate_limit_exceeded` + retry-after; provider-blind message). Explicit `RATE_LIMIT_FAIL_OPEN=true` opt-in for dev/local (loud startup warning). Structured `authz: rate limiter degraded … fail_open=…` log at the boundary on every backend error. `NewAuthorizer` gained variadic `WithFailOpen` option (all 2-arg callers unchanged, default closed). -- Tests: `TestAuthorizeFailsClosedWhenLimiterErrors`, `TestAuthorizeFailsOpenWhenExplicitlyEnabled` (`authorizer_test.go`). Go verification via CI "Go tests (edge-api)" (local toolchain unobservable). -- Ops: leave `RATE_LIMIT_FAIL_OPEN` unset (fail closed) in staging + prod. diff --git a/.planning/STATE.md b/.planning/STATE.md deleted file mode 100644 index 632aacce3..000000000 --- a/.planning/STATE.md +++ /dev/null @@ -1,281 +0,0 @@ ---- -gsd_state_version: 1.0 -milestone: v1.1 -milestone_name: deferred-scope -previous_milestone: v1.0 -previous_milestone_shipped: "2026-04-21" -previous_milestone_name: developer-api-core -current_phase: 20 -current_plan: null -status: phase_complete -stopped_at: "Phase 20 — Provider Catalog — IN PROGRESS. Waves 1-3 implemented (PRs #197, #199, #204, #205, #206): stock providers seeded, custom providers DB-managed, LiteLLM YAML regeneration, tenant model visibility, tools capability flag. Wave 4 pending." -last_updated: "2026-06-11T00:00:00.000Z" -progress: - total_phases: 16 - completed_phases: 7 - total_plans: 11 - completed_plans: 7 - total_phases_v1_0: 10 - completed_phases_v1_0: 10 - total_plans_v1_0: 49 - completed_plans_v1_0: 49 -deferred: - target_milestone: v1.1 - doc: .planning/v1.1-DEFERRED-SCOPE.md - phases: [11] - known_issues: - - batch-success-path-terminal-settlement - - formal-verification-phase-2-3 -v1_1_phase_status: - phase_12: complete - phase_13: complete - phase_14: complete - phase_15: complete - phase_16: complete - phase_17: complete - phase_18: complete - phase_19: complete - phase_20: in_progress - phase_21: pending - phase_22: pending - phase_23: pending - phase_24: pending - phase_25: pending - phase_26: drafted -v1_1_ship_gate: - fx_usd_zero_leak: true # Phase 17 — closed 2026-05-09 — PR #137 - rbac_matrix: true # Phase 18 — closed 2026-05-14 — PR #138 - chat_app_reaudit: false # Phase 25 — pending (HANDOFF-17-02) - web_search_tool: conditional # Phase 26 — include before packaging/cutover if kept in v1.1 scope -archive: - roadmap: .planning/milestones/v1.0-ROADMAP.md - requirements: .planning/milestones/v1.0-REQUIREMENTS.md - audit: .planning/milestones/v1.0-MILESTONE-AUDIT.md - integration_check: .planning/milestones/v1.0-INTEGRATION-CHECK.md - tag: v1.0 ---- - -# Project State - -## Project Reference - -See: .planning/PROJECT.md (updated 2026-04-21 after v1.0 milestone completion) - -**Core value:** Developers can switch from OpenAI to Hive with only a base URL and API key change, while keeping predictable prepaid billing and provider-agnostic operations. -**Current focus:** Phase 19 — Foundation Slice - -## Current Position - -Phase: 20 (Provider Catalog) — IN PROGRESS -Waves 1-3 implemented (PRs #197, #199, #204, #205, #206). Wave 4 pending. - -## Performance Metrics - -| Execution | Duration | Scope | Files | -|-----------|----------|-------|-------| -| Phase 05 P04 | 73min | 2 tasks | 8 files | -| Phase 05 P01 | 10min | 2 tasks | 9 files | -| Phase 06 P03 | 10min | 2 tasks | 12 files | -| Phase 06 P04 | 12min | 2 tasks | 17 files | -| Phase 09 P03 | 20min | 1 task | 14 files | - -**Velocity:** - -- Total plans completed: 18 -- Average duration: 19.1min -- Total execution time: 5.74 hours - -**By Phase:** - -| Phase | Plans | Total | Avg/Plan | -|-------|-------|-------|----------| -| 01-contract-compatibility-harness | 4/4 | 40min | 10min | -| 02-identity-account-foundation | 7/7 | 93min | 13.3min | -| 03-credits-ledger-usage-accounting | 3/3 | 87min | 29min | -| 04-model-catalog-provider-routing | 3/3 | 51min | 17min | -| 05-api-keys-hot-path-enforcement | 2/6 | 83min | 41.5min | - -**Recent Trend:** - -- Last 5 plans: 04-01 (30min), 04-02 (16min), 04-03 (5min), 05-04 (73min), 05-01 (10min) -- Trend: Phase 5 now has both the prior hot-path hardening slice and the API-key lifecycle foundation in place, leaving policy, projection, and limiter follow-up plans. - -| Phase 07 P01 | 9min | 3 tasks | 8 files | -| Phase 07 P02 | 22min | 2 tasks | 9 files | -| Phase 07 P03 | 45min | 2 tasks | 17 files | -| Phase 07 P04 | 35min | 2 tasks | 11 files | -| Phase 08-payments-fx-and-compliance-checkout P01 | 35 | 2 tasks | 11 files | -| Phase 08 P02 | 45 | 2 tasks | 8 files | -| Phase 08 P03 | 9 | 2 tasks | 6 files | -| Phase 10-routing-storage-critical-fixes P01 | 9 | 3 tasks | 6 files | -| Phase 10-routing-storage-critical-fixes P02 | 8 | 3 tasks | 5 files | -| Phase 10-routing-storage-critical-fixes P03 | 5 | 2 tasks | 6 files | -| Phase 10-routing-storage-critical-fixes P04 | 11 | 2 tasks | 7 files | -| Phase 10-routing-storage-critical-fixes P06 | 12 | 3 tasks | 8 files | -| Phase 10-routing-storage-critical-fixes P05 | 13 | 3 tasks | 7 files | -| Phase 10-routing-storage-critical-fixes P07 | 6 | 2 tasks | 21 files | -| Phase 10-routing-storage-critical-fixes P08 | 7 | 2 tasks | 3 files | -| Phase 10-routing-storage-critical-fixes P09 | 7min | 3 tasks | 8 files | -| Phase 10-routing-storage-critical-fixes P10 | 7min | 2 tasks | 11 files | -| Phase 10-routing-storage-critical-fixes P11 | 18min | 2 tasks | 8 files | - -## Accumulated Context - -### Decisions - -Decisions are logged in PROJECT.md Key Decisions table. -Recent decisions affecting current work: - -- Launch scope is the developer API, billing control plane, and developer console only. -- Hive must mirror the public OpenAI API surface except org and admin management endpoints. -- Prompt and response bodies must not be stored at rest for the API product. -- Launch monetization is prepaid Hive Credits only; subscriptions are deferred. -- Hosted Supabase is the v1 auth and primary relational data platform; no separate standalone Postgres server is planned initially. -- The developer workflow must run entirely inside Docker containers, including hot reload, builds, codegen, and tests. -- [01-01] Used GOTOOLCHAIN=auto to install air v1.64.5 (requires Go 1.25) on Go 1.24 base image. -- [01-01] Air build command uses absolute paths from /app workspace root for go.work compatibility. -- [01-01] SDK test services use Docker Compose profiles (test) so they only run on demand. -- [01-03] Java fine-tuning test uses raw HTTP to avoid coupling to SDK fine-tuning API surface changes. -- [01-03] Golden fixtures capture minimal expected shapes for regression, not full response bodies. -- [Phase 01]: Published docs are generated from support-matrix.json plus the upstream spec — Keeps runtime support classification as the single source of truth for the served contract and markdown docs. -- [Phase 01]: The generated contract drops top-level upstream x-oaiMeta — Prevents organization and admin documentation metadata from leaking back into Hive's published contract artifact. -- [Phase 01]: The generator entrypoint is POSIX-sh compatible and the toolchain image includes py3-yaml — Ensures Docker verification uses the same generation path as local development instead of a host-only workflow. -- [02-01]: DB connection failure at startup is non-fatal in control-plane — /health responds even without SUPABASE_DB_URL provisioned, enabling phased environment setup. -- [02-01]: token_hash stored (not raw token) in account_invitations — Security best practice to prevent token exposure from DB reads. -- [02-02]: HashToken (SHA-256 hex) is exported for test use — enables pre-computing known hashes in stubRepo tests without exposing private internals. -- [02-02]: X-Hive-Account-ID fallback is silent — invalid or unauthorized account IDs fall back to default membership without erroring the request. -- [02-02]: AcceptInvitation does not alter current-account on same request — switching workspace is an explicit later action. -- [02-03]: Middleware uses named export `middleware` (not default export) per Next.js App Router convention. -- [02-03]: Callback route uses an explicit allowlist for next= redirect targets (/console, /auth/reset-password) — simpler than regex and easier to audit. -- [02-03]: apps/web-console/.gitignore negates root-level Python lib/ gitignore entry so Next.js lib/ source can be committed. -- [02-04]: WorkspaceSwitcher uses HTML form POST to /console/account-switch — works without JS and keeps cookie mutation in the route handler. -- [02-04]: account-switch route validates account_id against viewer.memberships before persisting — prevents unauthorized workspace switching. -- [02-04]: invitations/accept does not set hive_account_id — newly joined workspace appears in switcher only after explicit user selection. -- [02-04]: VerificationBanner in console layout applies to all console routes without per-page logic. -- [Phase 02]: Core profile completion stays limited to owner name, login email, display name, account type, country, and state/province — Keeps billing and tax completeness out of Phase 2 onboarding gates. -- [Phase 02]: Profile writes update public.accounts display_name and account_type alongside public.account_profiles — Keeps viewer and current-account profile data consistent after edits. -- [Phase 02]: Profiles handler resolves the current account from the authenticated viewer context — Avoids trusting client-supplied account identifiers for profile reads and writes. -- [Phase 02]: The setup flow submits the existing login email as a hidden value so onboarding stays limited to the five visible core fields — Keeps initial setup minimal while satisfying the profile API contract. -- [Phase 02]: Profile editing uses shared server-action form handling while email maintenance stays browser-side — Keeps control-plane profile writes server-side and uses Supabase client auth APIs only where they are required. -- [Phase 02]: Dashboard setup guidance is a reminder card instead of a redirect gate — Preserves /console as the landing route after setup completion. -- [Phase 02]: Billing-profile reads fall back to core-profile contact and location data — Lets optional billing settings render useful defaults before the first billing-specific save. -- [Phase 02]: Billing settings redirect unverified users to /console/settings/profile instead of broadening the restricted-console allowlist — Keeps profile maintenance reachable without turning billing into a Phase 2 gate. -- [Phase 02]: The web-console control-plane client now uses explicit JSON decoders instead of assertion-based parsing — Keeps the touched billing/profile surface aligned with the strict TypeScript policy. -- [03-01]: Reservation holds are negative deltas and releases are positive deltas — keeps reserved-credit math derivable from immutable ledger entries without a mutable balance counter. -- [03-01]: Credit mutation idempotency is anchored in Postgres `credit_idempotency_keys` — Redis is runtime plumbing for later hot-path helpers, not the source of financial truth. -- [03-01]: Ledger balance and history routes resolve current account via `accounts.Service` — avoids trusting client-supplied account IDs on credit read APIs. -- [03-02]: Request accounting keeps both `request_id` and `attempt_number` — retries and interrupted executions stay reconcilable without inventing a second wallet model. -- [03-02]: Usage-event metadata is recursively redacted before persistence — prompt, message, input, response, completion, content, and output_text keys never reach durable storage. -- [03-02]: Current-account usage responses omit `provider_request_id` and `internal_metadata` — customer-visible APIs stay provider-blind even when internal records retain diagnostics. -- [03-03]: Reservation lifecycle state is stored durably in Postgres while immutable ledger entries remain the financial source of truth. -- [03-03]: Ambiguous interruptions default to customer-favoring release plus reconciliation instead of assuming full reserve consumption. -- [03-03]: Current-account reservation mutations reuse the control-plane account resolver and reject invalid reservation IDs at the HTTP boundary. -- [04-01]: The control-plane snapshot drives both `/v1/models` and `/catalog/models` so Hive's public model surfaces cannot drift. -- [04-01]: Edge catalog fetch failures return the provider-blind `catalog_unavailable` OpenAI error instead of leaking snapshot or provider detail. -- [04-02]: Alias capability, allowlist, and fallback checks run inside Hive before LiteLLM receives a route handle. -- [04-02]: LiteLLM model groups are keyed by private route handles rather than public alias IDs. -- [04-03]: Cache-aware provider billing is normalized into the existing `cache_read_tokens` and `cache_write_tokens` fields, and zero-value cache fields stay omitted from customer responses. -- [04-03]: Edge upstream errors mirror the provider-blind sanitization rules locally so customer-visible failures never depend on control-plane routing packages. -- [Phase 05]: API-key mutations remain gated by accounts.Service.EnsureViewerContext and CanManageAPIKeys instead of trusting client ownership claims. -- [Phase 05]: API-key list, detail, create, and rotate responses share a customer-safe serializer that applies expiry projection and never re-emits secrets after issuance. -- [06-03]: SelectRouteResult has no SupportsReasoning field; reasoning capability gating uses NeedReasoning bool as proxy for route capability. -- [06-03]: Responses API streaming ends with event: response.completed — no data: [DONE] sentinel — matching OpenAI Responses SDK expectations. -- [06-04]: dimensions gating uses model name heuristic (contains 'embedding-3') rather than capability flag — pragmatic Phase 6 approach; future phase can add SupportsDimensions to routing types. -- [06-04]: EmbeddingObject.Embedding stays json.RawMessage to handle both float arrays and base64 encoding_format without type assertions. -- [Phase 07-01]: old storage client core used instead of *old storage client for multipart upload access — NewMultipartUpload/PutObjectPart/CompleteMultipartUpload/AbortMultipartUpload are private on Client but public on Core -- [Phase 07-01]: legacy S3-compatible client pinned to v7.0.91 (latest compatible with Go 1.24 — v7.0.100+ requires Go 1.25) -- [07-02]: images.StorageInterface returns (string, error) for PresignedURL — avoids *url.URL dependency in the images package; storageAdapter in main.go bridges the real files.StorageClient -- [07-02]: Audio Handler has no storage field by design — enforces that audio is never stored; no storage parameter means no accidental storage calls possible -- [07-02]: NeedImageGeneration/NeedTTS/NeedSTT as package constants — documents routing capability intent without requiring a full orchestrator in unit tests -- [07-03]: FilestoreClient and BatchstoreClient use plain http.Client with 10s timeout — no shared transport needed at this scale -- [07-03]: Batches package uses adapter layer (accounting, authz, file, storage) to decouple handler from direct service imports — enables clean unit testing -- [07-03]: Asynq selected for batch worker task queue — consistent with control-plane async patterns; simple Redis-backed queue fits polling use case -- [07-03]: All file/upload/batch operations validate account ownership via AuthSnapshot.AccountID before any data access — no cross-account leakage -- [07-04]: handleMultipartAudio gains accountingEndpoint parameter separate from litellmPath — transcription and translation share the same handler but need different endpoint strings for reservation records -- [07-04]: Model rewriting in multipart goroutine uses captured litellmModel local variable — avoids closure-over-loop-variable hazard -- [07-04]: Test doubles (mock Authorizer/RoutingInterface/AccountingInterface) added in _test packages — existing test assertions preserved, only wiring changed to match new NewHandler signatures -- [Phase 08]: [08-01]: FXService uses FXCache interface (not *redis.Client directly) — enables in-memory test doubles without real Redis in unit tests -- [Phase 08]: [08-01]: BD rails (bkash/sslcommerz) transition to confirming on payment.succeeded; Stripe transitions directly to completed — BD payment clearing requires 3-minute confirming delay before ledger grant -- [Phase 08]: [08-01]: PostPurchaseGrant idempotency key is payment:purchase:{intentID} — deterministic key prevents double-crediting across retries -- [Phase 08]: [08-02]: Stripe uses ConstructEventWithOptions with IgnoreAPIVersionMismatch: true — stripe-go v84 validates event API version by default; test events built locally lack the SDK-matching api_version field -- [Phase 08]: [08-02]: bKash always grants fresh token per request — tokens are never cached to avoid 401s on concurrent requests with short-lived access tokens -- [Phase 08]: [08-02]: SSLCommerz ProcessEvent returns sessionkey as ProviderIntentID (not tran_id) — ensures GetPaymentIntentByProviderID lookup matches what Initiate stored -- [Phase 08]: PaymentService and AccountResolver interfaces defined in http.go — accept-interfaces pattern enables stub-based testing without importing full service -- [Phase 08]: accountsResolverAdapter bridges 3-arg accounts.Service.EnsureViewerContext to narrow 1-arg payments.AccountResolver interface — isolates payments from accounts internals -- [Phase 18-02]: ActorFor is a pure stateless mapping (no DB) — all handler-level authz builds Actor inline then calls policy.Can, keeping the decision function side-effect-free. -- [Phase 18-02]: NewActorResolver closure calls IsPlatformAdmin via *platform.RoleService — direct type avoids unnecessary interface indirection at the single call site. -- [Phase 18-03]: billing.view has RequiresVerified=false — unverified workspace owners can view their own budget; old blanket EmailVerified gate was stricter than necessary. -- [Phase 18-03]: CreateInvitation gate code changed from email_verification_required to permission_denied — canonical authz error code for all policy.Can failures. -- [Phase 18-03]: analytics.view and ledger.view grant any verified actor (owner OR member) — mirrors pre-Phase-18 behavior (EmailVerified only, no role check). - -- [09-04]: ExternalMux pattern: RouterConfig.Mux field lets main.go pre-create *http.ServeMux so filestore.RegisterRoutes works after NewRouter returns http.Handler -- [09-04]: NewRouter returns http.Handler (not *http.ServeMux) — Plan 01 Wave 2 depends on this changed signature -- [09-04]: Custom prometheus.Registry per service (not DefaultRegistry) — excludes Go runtime noise from /metrics output -- [09-04]: UUID normalization via regexp.MustCompile in normalizeEndpoint — ensures raw UUIDs never appear as Prometheus label values - -- [09-02]: renderToBuffer typed via React.ComponentProps — avoids importing ReactPDF namespace from CommonJS export = module while satisfying renderToBuffer's DocumentProps constraint -- [09-02]: BillingOverview Buy Credits uses anchor link not inline modal — overview tab is a server component; checkout modal client state handled on client re-render -- [09-02]: LedgerCsvExport extracted to separate use client file — keeps LedgerTable a pure server component while enabling browser Blob/URL CSV download - -- [09-03]: AnalyticsControls extracted to separate use-client file — keeps analytics page a pure server component while enabling client-side tab/window navigation via useRouter -- [09-03]: /api/budget route handler bridges client BudgetAlertForm/Banner to server-only client.ts functions — avoids exposing CONTROL_PLANE_BASE_URL or session tokens to browser -- [09-03]: Promise.allSettled for balance/budget in console layout — prevents layout render failure if either fetch errors; falls back to zero balance and null threshold -- [Phase 10-routing-storage-critical-fixes]: Wave 0 storage tests validate constructor env errors but leave S3 methods stubbed with storage implementation pending. -- [Phase 10-routing-storage-critical-fixes]: Edge route registration tests target a small helper signature that registers prebuilt handlers onto an http.ServeMux. -- [Phase 10-routing-storage-critical-fixes]: Smoke probes capture HTTP status and body files before checking response content. -- [Phase 10]: Wave 0 stayed red-only: production routing, filestore, and batch worker code was not changed. -- [Phase 10]: Verification used corrected Docker toolchain invocation because the documented sh -lc form is swallowed by the toolchain entrypoint. -- [Phase 10]: Routing and filestore constructors now trust Supabase migrations instead of mutating schema at runtime. -- [Phase 10]: route-openrouter-auto is explicitly backfilled for media and batch capability filters so the existing hive-auto route remains eligible. -- [Phase 10]: Filestore migration contract coverage was split from runtime-DDL source coverage so Task 1 can validate migrations before Task 2 removes constructors. -- [Phase 10-routing-storage-critical-fixes]: Presigned URLs set X-Amz-Expires explicitly before calling v4.Signer.PresignHTTP because aws-sdk-go-v2 v1.41.5 does not expose a signer Expires option. -- [Phase 10-routing-storage-critical-fixes]: UploadPart returns the ETag header exactly as received, including quotes, and CompleteMultipartUpload forwards that value into the XML payload. -- [Phase 10-routing-storage-critical-fixes]: Verification uses the Docker toolchain with --entrypoint /bin/sh and /usr/local/go/bin/go so the tests actually execute under this compose entrypoint. -- [Phase 10-routing-storage-critical-fixes]: Internal control-plane responses now expose storage metadata for edge-api clients while public edge response types keep those values out of customer JSON. -- [Phase 10-routing-storage-critical-fixes]: UpdateBatchStatus rejects unsupported update fields instead of ignoring them, so no caller-supplied key can enter generated SQL. -- [Phase 10-routing-storage-critical-fixes]: Control-plane records a local replace for packages/storage because Docker go mod tidy otherwise attempts to fetch the private workspace module from GitHub. -- [Phase 10-routing-storage-critical-fixes]: Edge API startup now treats storage as required and exits with storage unavailable errors when any required S3 env var or client setup fails. -- [Phase 10-routing-storage-critical-fixes]: files.CompletePart aliases packages/storage.CompletePart so *storage.S3Client satisfies files.StorageBackend directly. -- [Phase 10-routing-storage-critical-fixes]: The edge module records a local replace for packages/storage to keep Docker go mod tidy from fetching the private workspace module from GitHub. -- [Phase 10-routing-storage-critical-fixes]: Supabase Storage is documented as the only object storage backend, and both edge-api and control-plane require S3 env vars at startup. -- [Phase 10-routing-storage-critical-fixes]: Phase 10 roadmap progress kept the current 6/8 execution state instead of reverting to the stale 0/8 baseline from the original plan text. -- [Phase 10-routing-storage-critical-fixes]: Historical planning references are scrubbed mechanically with a generated rg candidate list instead of hand-selected file paths. -- [Phase 10-routing-storage-critical-fixes]: Final Go verification uses the corrected Docker toolchain invocation with --entrypoint /bin/sh so go test actually runs. -- [Phase 10-routing-storage-critical-fixes]: Live smoke was skipped because S3_REGION and HIVE_API_KEY were missing from the combined shell and .env configuration. -- [Phase 10-routing-storage-critical-fixes]: edge-api now receives S3_REGION from Docker Compose, matching its fail-fast storage startup requirements. -- [Phase 10-09]: Image, audio, and batch reservations use policy_mode strict — Control-plane accounting accepts strict today, and prepaid reservation paths must not silently overrun credits. -- [Phase 10-09]: Batch reservation attribution derives model_alias from JSONL body.model — Rejecting missing or mixed model aliases before reservation creation keeps downstream spend attribution correct by model. -- [Phase 10-10]: Batch attribution persists on public.batches — Storing api_key_id, model_alias, estimated_credits, and actual_credits on the batch record gives terminal settlement a stable source of truth. -- [Phase 10-10]: Batch worker payload attribution fields stay optional — omitempty preserves compatibility for already-enqueued poll jobs while letting new producers pass attribution directly. -- [Phase 10-11]: Terminal batch settlement finalizes from persisted batch attribution and caps actual credits to the reserved estimate — terminal spend stays attributable per API key/model without overcharging beyond the batch reservation. -- [Phase 10-11]: Runtime Dockerfiles copy packages/storage because go.work declares it as a workspace module — live compose images now build the same storage code the toolchain tests exercised. -- [Phase 10-11]: Live smoke request failures now surface honestly, and the remaining chat blocker is the current upstream provider key quota rather than a routing, storage, or batch-contract regression. - -### v1.1 Ship Gate - -| Gate | Status | Closed | -|------|--------|--------| -| rbac_matrix | true | Phase 18 — closed 2026-05-14 — PR #TBD-Phase-18 | - -### Pending Todos - -- (none — Design RBAC authorization model resolved to `.planning/todos/done/` by Phase 18) - -### Blockers/Concerns - -- Provider capability gaps must be handled explicitly so unsupported endpoints fail in an OpenAI-style way. -- Payment-tax behavior across Stripe, bKash, and SSLCommerz needs careful validation during Phase 8. -- The current OpenRouter key in `.env` is out of quota, so the live chat smoke cannot return HTTP 200 until provider capacity is restored. - -## Session Continuity - -Last session: 2026-05-17T03:00:00.000Z -Stopped at: Phase 19 Plan 03 — Open WebUI deploy + chat happy path — after Plan 02 merged in PR #140 -Resume file: None - -## v1.1.0 ship-gate checkboxes - -- [x] **Phase 17 — FX/USD Zero-Leak.** Closed 2026-05-09. PR #137. Evidence FX-17-01..10. BD regulatory surface clean. -- [x] **Phase 18 — RBAC matrix.** Closed 2026-05-14. PR #138. Evidence RBAC-18-01..11. HANDOFF-17-01 `is_platform_admin` replacement landed. -- [ ] Phase 25 — Chat-app re-audit (HANDOFF-17-02 inherits non-BD locale upstream USD prose). -- [ ] Phase 26 — Web search tool, if kept in v1.1 launch scope, executes after Phase 21 and before Phase 24/25 packaging/cutover. diff --git a/.planning/UAT-REPORT.md b/.planning/UAT-REPORT.md deleted file mode 100644 index dccdfba66..000000000 --- a/.planning/UAT-REPORT.md +++ /dev/null @@ -1,246 +0,0 @@ -# Hive v1.0 Runtime UAT Report - -**Date:** 2026-04-13 -**Tester:** Automated (Claude Code agents) -**Stack:** edge-api + control-plane + web-console + redis + litellm + prometheus + grafana + alertmanager -**S3 Storage:** Gracefully degraded (Supabase Storage S3 endpoint format incompatible with legacy S3-compatible client path restriction) -**Inference Model:** hive-default (openrouter/free — zero cost) -**Test Account:** uat.test.hive@gmail.com (auto-confirmed via Supabase MCP) - ---- - -## Results Summary - -| Metric | Count | -|--------|-------| -| Total tests | 24 | -| Passed | 21 | -| Failed | 3 | -| Pass rate | 87.5% | - ---- - -## Test Results - -### Group A: Infrastructure Health (5/5 PASS) - -| # | Test | HTTP | Result | -|---|------|------|--------| -| 1 | Edge API /health | 200 | PASS | -| 2 | Control Plane /health | 200 | PASS | -| 3 | Web Console / redirect | 307 | PASS | -| 4 | Prometheus /-/healthy | 200 | PASS | -| 5 | Grafana /api/health | 200 | PASS | - -### Group B: Auth & Account (5/5 PASS) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 6 | GET /api/v1/viewer | 200 | PASS | Auto-provisioned workspace, gates populated | -| 7 | GET /api/v1/accounts/current/profile | 200 | PASS | profile_setup_complete=false (expected) | -| 8 | GET /api/v1/accounts/current/members | 200 | PASS | Test user listed as owner | -| 9 | GET /api/v1/accounts/current/credits/balance | 200 | PASS | Zero balance | -| 10 | GET /api/v1/accounts/current/credits/ledger | 200 | PASS | Empty ledger with cursor pagination | - -### Group C: Catalog & Models (3/3 PASS) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 11 | GET /api/v1/catalog/models | 200 | PASS | 3 models with pricing, no provider names | -| 12 | GET /v1/models (no auth) | 401 | PASS | OpenAI error format, invalid_api_key code | -| 13 | GET /v1/models (with API key) | 200 | PASS | owned_by:"hive", provider-blind | - -### Group D: API Key & Headers (2/3 — 1 FAIL) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 14 | HEAD /v1/models | 404 | FAIL | Router doesn't register HEAD handler (low severity) | -| 15 | GET /v1/models response headers | 200 | PASS | x-request-id, openai-version, openai-processing-ms present | -| 16 | Checkout rails | 200 | PASS | Stripe active, predefined tiers returned | - -### Group E: Inference (0/2 — CRITICAL FAIL) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 17 | POST /v1/chat/completions (sync) | 502 | FAIL | "Failed to select a route for this request" | -| 18 | POST /v1/chat/completions (stream) | 502 | FAIL | Same routing error | - -**Root cause:** `ensureCapabilityColumns` in `apps/control-plane/internal/routing/repository.go` targets `route_capabilities` instead of `provider_capabilities`. The 5 media capability columns are never added, and `ListRouteCandidates` queries fail. - -### Group F: Error Handling (2/2 PASS) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 19 | GET /v1/fine_tuning/jobs (unsupported) | 404 | PASS | type:"unsupported_endpoint", code:"endpoint_unsupported" | -| 20 | POST /v1/chat/completions (no auth) | 401 | PASS | OpenAI error format, invalid_api_key | - -### Group G: Monitoring (2/2 PASS) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 21 | Control Plane /metrics | 200 | PASS | hive_http_request_duration_seconds exposed | -| 22 | Edge API /metrics | 200 | PASS | Custom registry, no Go runtime noise | - -### Group H: Web Console (2/2 PASS) - -| # | Test | HTTP | Result | Notes | -|---|------|------|--------|-------| -| 23 | /auth/sign-in | 200 | PASS | HTML page served | -| 24 | /auth/sign-up | 200 | PASS | HTML page served | - -### Provider-Blind Check: PASS - -Scanned all 24 responses for forbidden strings: `openrouter`, `groq`, `litellm`, `provider`. None found. - ---- - -## Critical Issues - -### 1. Inference Routing Broken (BLOCKER) -- **Symptom:** All chat completions return 502 with "Failed to select a route" -- **Root cause:** `ensureCapabilityColumns()` in `routing/repository.go` ALTER TABLEs `route_capabilities` but actual table is `provider_capabilities`. Error silently swallowed (`_ = err`). -- **Impact:** No inference possible. The core product feature is broken. -- **Fix:** Change table name to `provider_capabilities`, or add a proper SQL migration for the 5 columns. - -### 2. Supabase Storage S3 Endpoint Incompatible with legacy S3-compatible client -- **Symptom:** `Endpoint url cannot have fully qualified paths` -- **Root cause:** `old storage client constructor()` accepts only `host:port`, but Supabase S3 endpoint includes path `/storage/v1/s3` -- **Impact:** File, image, audio, and batch endpoints disabled (gracefully degraded after fix) -- **Fix applied:** Made storage init non-fatal in edge-api `main.go`. Permanent fix needed: either use a reverse proxy, or switch to Supabase Storage REST API instead of S3 protocol. - -### 3. HEAD /v1/models Returns 404 (LOW) -- **Symptom:** HEAD method not registered on model routes -- **Impact:** Minimal — OpenAI SDKs don't use HEAD. - ---- - -## Mistakes Discovered & Fixed During UAT - -### Mistake 1: Fatal S3 Init Killed the Server -- **What happened:** Removing legacy local object-store emulator caused `log.Fatalf` on S3 client init failure, killing edge-api entirely -- **Fix:** Changed to `log.Printf` warning + conditional route registration. Server starts without S3; file/media endpoints disabled gracefully. -- **File:** `apps/edge-api/cmd/server/main.go` -- **Lesson:** Infrastructure dependencies should degrade gracefully, not crash the server. - -### Mistake 2: .env Not Found by Docker Compose -- **What happened:** `docker compose up` from `deploy/docker/` couldn't find `.env` at repo root -- **Fix:** Must use `--env-file ../../.env` flag -- **Lesson:** Document the exact command including `--env-file` path. - -### Mistake 3: API Key Field Name Mismatch -- **What happened:** Plans and test scripts used `name` but actual API requires `nickname` -- **Fix:** Used correct field `nickname`. -- **Lesson:** API field names in plans should be verified against actual handler code. - -### Mistake 4: Supabase S3 Endpoint Format -- **What happened:** Set `S3_ENDPOINT=host/storage/v1/s3` but legacy S3-compatible client rejects paths in endpoint -- **Fix:** Made S3 optional (graceful degradation). Permanent fix needed. -- **Lesson:** Test infrastructure changes against real clients before assuming drop-in compatibility. - -### Mistake 5: Silent Error Swallowing in ensureCapabilityColumns -- **What happened:** `_ = err` on ALTER TABLE hid the wrong table name bug for the entire development cycle -- **Impact:** The routing system is fundamentally broken but all unit tests pass (they mock the DB) -- **Lesson:** Never swallow errors on DDL operations. Use proper SQL migrations instead of runtime ALTER TABLE. - -### Mistake 6: Supabase Email Confirmation Required -- **What happened:** Test user sign-up succeeded but sign-in failed with "Email not confirmed" -- **Fix:** Used Supabase MCP `execute_sql` to set `email_confirmed_at` directly -- **Lesson:** For UAT automation, either disable email confirmation in Supabase settings or use service role admin API / direct SQL. - ---- - -## What Works (Confirmed by Runtime Testing) - -- Supabase auth (sign-up, sign-in, JWT validation) -- Auto-workspace provisioning on first login -- Account profile and member management -- API key lifecycle (create, list, policy, revoke) -- Edge-api key authentication and cache invalidation -- Model catalog (3 aliases, no provider leakage) -- OpenAI-compatible error format on all error paths -- Compat headers (x-request-id, openai-version, openai-processing-ms) -- Support matrix endpoint rejection with correct error types -- Credits balance and ledger (empty but functional) -- Payment rails listing (Stripe active) -- Prometheus metrics on both services (custom registry) -- Grafana + Alertmanager monitoring stack -- Web console auth pages - -## What Does NOT Work - -- Inference (routing bug — BLOCKER) -- File/image/audio/batch endpoints (S3 not connected) -- Payment checkout flow (untested — no credits loaded) -- Web console authenticated pages (would need browser/Playwright test) - ---- - -## Next Steps - -1. **Fix routing bug** — add migration for the 5 capability columns on `provider_capabilities` -2. **Fix S3 integration** — either use Supabase Storage REST API or configure a reverse proxy for the S3 path -3. **Re-run inference UAT** after routing fix -4. **Browser-based UAT** for web console authenticated pages -5. **Payment flow test** with Stripe test mode - ---- - -## Post-Phase-10 Annotations - -> Annotations added by Phase 11 (2026-04-25) reconciling the 2026-04-13 report -> against Phase 10 closure. **Original 2026-04-13 entries above are preserved -> verbatim** — this section is append-only. See -> `.planning/phases/10-routing-storage-critical-fixes/10-UAT.md` for the Phase -> 10 closure log and `.planning/REQUIREMENTS.md` for the active requirement -> matrix. - -### Storage status — was "Gracefully degraded", now Required + Live - -- **Original entry (2026-04-13):** "S3 Storage: Gracefully degraded (Supabase - Storage S3 endpoint format incompatible with legacy S3-compatible client path - restriction)". File / image / audio / batch endpoints listed as disabled. -- **Phase 10 reality (2026-04-21):** Plans 10-07, 10-08, and 10-11 wired - edge-api + control-plane to Supabase Storage and made the storage init - fail-fast at startup unless required S3 env vars are present and the - `hive-files` + `hive-images` buckets exist. File + media endpoints are - routable; live smoke completed in Phase 10 Plan 10-08 (see - `.planning/phases/10-routing-storage-critical-fixes/10-UAT.md`). -- **Live status (2026-04-25):** Required at startup; Supabase Storage is the - only object-storage backend. See `apps/edge-api/internal/...` storage init - + Phase 10 UAT for the closure evidence. - -### File / media endpoints — was "Disabled", now Live (subject to API-07 caveat) - -- **Original entry:** Endpoints disabled gracefully when storage init failed. -- **Phase 10 reality:** Endpoints are wired and exercised. Failure-path is - conditional only on Supabase Storage availability (managed dependency). -- **Live status:** API-05 + API-06 → Satisfied per archived - `.planning/milestones/v1.0-REQUIREMENTS.md` (Phase 10 UAT Tests 7 + 8). - API-07 (`files`/`uploads`/`batches`) is **Partial**: success-path of - `/v1/batches` remains blocked by upstream provider capability — neither - OpenRouter nor Groq exposes a native batch API and LiteLLM's managed file - upload path does not list them. Tracked in - `.planning/phases/10-routing-storage-critical-fixes/KNOWN-ISSUE-batch-upstream.md` - and `CLAUDE.md` Known Issues §4. - -### Batch success-path — confirmed still blocked upstream - -- **Original entry:** Not explicitly listed in the 2026-04-13 report (Phase 10 - introduced the batch surface). -- **Phase 10 reality:** Submitter + failure-path terminal settlement - verified live (reservation release + attribution). Success-path - (`status=completed`) not exercisable with the current provider mix. -- **Live status:** Unblocks via either (a) adding a key for a LiteLLM-supported - batch provider (OpenAI / Azure / Vertex AI / Manus / Anthropic) or - (b) implementing a local batch executor in control-plane. Both routes are - v1.1 scope. - -### Cross-reference - -For every annotation above, the affected requirement's evidence file in -`.planning/phases/11-verification-cleanup/evidence/` carries the matching -caveat under its `## Known Caveats` heading. AUTH-01 / AUTH-02 are independent -of these annotations — they were verified as Satisfied via Phase 02 code paths -during Phase 11 Task 1. - -*Annotations dated 2026-04-25.* diff --git a/.planning/carl/CHARTER.md b/.planning/carl/CHARTER.md deleted file mode 100644 index 8e182f4f4..000000000 --- a/.planning/carl/CHARTER.md +++ /dev/null @@ -1,150 +0,0 @@ -# Carl.sh — Sovereign Workspace Leadership Charter - -> ## Amendment 2026-07-07 -> -> This block was added on 2026-07-07 and governs on any conflict with the historical charter text below. The original text is preserved for the record and retains its historical naming; the historical sections are not rewritten. -> -> **Product naming.** The product is now **Hive** (cloud, us-hosted) and **Hive Enterprise** (customer-hosted). The "Carl" and "Carl.sh" names are retired. The one-command installer remains `scripts/install.sh` and carries no product branding. All "Carl" and "Carl.sh" naming below is historical. -> -> **Sovereignty posture.** The locked decision "zero external API keys at the edge, `install --sovereign` refuses external provider keys" (decision 2 plus the Meeting 1 sovereign egress hardening) is superseded by three inference postures: cloud (external providers); enterprise default (local models, where an admin may knowingly opt in to add external provider keys); and strict-sovereign lockdown (the `HIVE_SOVEREIGN` guard from #245, now an optional toggle for buyers who need a provable air gap, rather than an always-on default). The Grok and free-model test-only rule (decision 8) is unchanged: test-time conveniences, never shipped runtime dependencies. -> -> **Tenancy.** A single organization is a single tenant. Departments are separated by RBAC inside that one tenant, not by additional tenants. The multi-tenant schema exists for extensibility only. -> -> **Chat client.** Open WebUI is the v1 chat client, per the PR #291 decision, which supersedes the Meeting 1 choice of Lobe Chat recorded below. PR #291 was open at the time of this amendment. - -Initiative: a single one-command tool (`Carl.sh`) that stands up a fully self-hosted AI workspace -(a co-work ChatGPT with RAG) on a target machine, exposing OpenAI-compatible and -Anthropic-compatible APIs over the existing Hive control plane. Built by gap-closing the existing -`fundmoreai/hive` repository, not greenfield. The product is a sovereign alternative to OpenAI and -Anthropic: it speaks both API dialects but runs entirely on the customer's own server with no -external SaaS dependency and no external API keys. - -Established 2026-06-25 by the orchestrator acting as the leadership team, per owner directive -"You are the leadership team, create any roles you need." Owner retains veto on every decision. - -## Roles and responsibilities - -| Role | Held by | Mandate | -|------|---------|---------| -| CEO / Orchestrator | main agent (this thread) | Vision, segment strategy, go/no-go, resource asks, merge calls, ledger and memory upkeep. Never writes product code. | -| CTO | `architect` agent + orchestrator synthesis | Architecture review and design of the deltas, technology selection validated against live sources, design docs. | -| Head of Product | `planner` agent | Requirements traceability, GitHub board and issues, phase slicing, roadmap. | -| CMO | `ecc:marketing-agent` | Positioning for the sovereign pivot, two marketing sites already live, regulatory talking points. | -| Security & Compliance Lead | `security-reviewer` agent | Data sovereignty, US CLOUD Act avoidance wedge, secrets, audit logging, all auth and money paths. | -| Build Leads | `go-reviewer` / `typescript-reviewer` / `database-reviewer` + builder agents | Edge-api and control-plane (Go), workspace and console (TS), PGVector and migrations (SQL). | -| QA / E2E | `e2e-runner` agent | End-to-end verification of installer, RAG, voice, relay, co-work flows before any ship claim. | - -Each role is realized as a dispatched subagent with an explicit library `subagent_type`. The -orchestrator only coordinates, reviews pushed diffs, and keeps memory. Independent reviewer per PR. - -## Locked decisions (2026-06-25, revised after owner clarification) - -1. Scope: gap-close on the existing `fundmoreai/hive` repo. The enterprise edge box is the primary - product. The cloud platform is a demo for now, improved later. -2. Fully self-hosted. Zero external SaaS dependency and zero external API keys at the edge. The - product is a sovereign alternative to OpenAI and Anthropic. -3. API surfaces: OpenAI-compatible (already shipped) plus an Anthropic-compatible `/v1/messages` - surface that translates to our own local and open models (issue #168). We never call real - Anthropic. The Anthropic API key idea is dropped. -4. Relay: self-hosted only. Default is LAN serve via the box's own Caddy. Remote access without - opening firewall ports is via self-hosted WireGuard, with Headscale as an optional coordination - server. No Tailscale SaaS and no external relay keys. -5. Voice and STT: NVIDIA Parakeet, self-hosted. Owner provides a Parakeet host or download. -6. Embeddings and RAG: PGVector on Postgres (Supabase provides the extension) plus local - embeddings. Add document upload and vector search endpoints to edge-api. -7. Cloud co-work workspace: build first as a self-contained isolated container component. Explore - online sandbox services (Daytona or similar) later. The OpenCode coding agent is deferred and is - not the main target. -8. Testing only: Grok (xAI) as a test LLM and free OpenAI-compatible local models. These are - test-time conveniences, never shipped runtime dependencies. - -## Gap priority (edge first) - -1. Anthropic-compatible `/v1/messages` surface (#168). Completes the dual-dialect promise. -2. RAG document upload and vector search endpoints on edge-api. -3. Parakeet voice and STT integration, exposed as an OpenAI-compatible audio endpoint. -4. Self-hosted relay (WireGuard, optional Headscale) for port-less edge remote access. -5. Containerized co-work workspace (demo), after the edge core is solid. - -## Resources provided by owner - -Parakeet voice-model host or download, free local OpenAI-compatible models, Grok for testing. -Not provided and not needed: Tailscale key, Daytona account, Anthropic API key. - -## Open clarifications (CTO defaults stand until owner vetoes) - -- Lead segment: finance and legal in Canada / Ontario (WEtech Alliance, OSFI B-10, Quebec Law 25). -- First runnable milestone: edge box installs via Carl.sh and serves both API dialects against a - local model selected by the hardware advisor, with RAG upload and query working. - -## Meeting 1 outcomes (2026-06-25) - -Leadership team aligned on the following binding decisions: - -### Web client and user-facing shell -The shipped web client shell is Lobe Chat (MIT license). Open WebUI is dropped as the standard -shipped shell due to a post-2025 branding retention clause that creates resale risk for regulated -government buyers. Lobe Chat is MIT-licensed, open-source, and has no external branding obligations. - -### Edge data plane and storage -The self-hosted edge data plane is a Supabase stack (Postgres with pgvector extension, GoTrue -auth, Storage on local filesystem). MinIO is rejected due to AGPL licensing; Supabase Storage -(S3-compatible) backed by the edge box's own filesystem is the standard object storage backend. -All data stays on-premise with zero external storage dependency. - -### Tenant feature flags and revocation -Tenant feature flags (RAG enabled, voice enabled, relay enabled, cowork enabled) are NOT -embedded in the JWT. Instead, they resolve lazily at the edge through a feature gate middleware -with a 30-second cache, enabling feature revocation to take effect in under 60 seconds without -redeployment. Issue #238 defines the gate.go middleware and control-plane settings endpoint. - -### SSO and Active Directory procurement blocker -Single Sign-On and Active Directory integration (SAML, OIDC, LDAP) are promoted to v1 MUST status. -Regulated buyers in finance and legal (OSFI B-10, Quebec Law 25) require enterprise authentication -to be deployable. This was blocking every pilot discussion. Issue #237 covers enterprise auth. - -### Sovereign egress hardening -The edge stack hardens egress to ensure no telemetry or audit leaks to external providers: - -- LiteLLM telemetry is disabled globally. -- All external audit sinks (if any) are gated per tenant, controlled via control-plane settings. -- A new `install --sovereign` flag in Carl.sh refuses any external LLM provider keys (OpenRouter, - Groq, etc.) and fails if attempted. Operator must provide a local model via Ollama or equivalent. - -### Audit taxonomy and regulatory compliance -RAG retrieval must emit a `RAG_CHUNK_RETRIEVED` audit event per chunk returned to the model, -as required by Quebec Law 25 and PHIPA audit trails. Issue #239 extends the audit schema with -new event types: `LLM_RESPONSE`, `RAG_DOCUMENT_UPLOAD`, `RAG_DOCUMENT_DELETE`, `RAG_SEARCH`, -`RAG_CHUNK_RETRIEVED`, `FILE_ACCESS`, `DATA_SUBJECT_REQUEST`. Issue #241 adds a cron job to -archive audit logs older than 90 days to cold storage with 10-year retention per PHIPA. - -### Pre-ship license clearance gate -Every shipped dependency must pass a pre-release license clearance check. Issue #242 defines an -SBOM generation and verification workflow that blocks any release containing AGPL or GPL -dependencies. This ensures customers can deploy Carl.sh without open-source licensing obligations -that would conflict with selling the edge as a proprietary product or integrating it into closed -systems. - -### Client phasing recommendation (pending owner ratification) -Based on the feature set and shipping timeline: - -- **Ship in v1 (Wave 1–2)**: Full-featured web console (Lobe Chat), thin Word plugin. -- **Defer to v1.x**: Mobile apps, LibreOffice/Google Docs integrations, desktop client, multi-user cowork workspace. - -Rationale: the web client covers the largest user population and enables all core features (chat, -RAG, voice, relay). Mobile and offline integrations are valuable but not blocking the first -regulated deployment. Cowork (multi-user collaboration) is a nice-to-have after the core sovereign -edge product is solid and proven in the field. - -### New GitHub issues tracking decisions -Five new issues created, labeled `carl` and added to milestone 7 (Carl.sh edge-first v1) and the -Hive Roadmap board: - -- **#237**: SSO and Active Directory enterprise auth (SAML/OIDC/LDAP), Wave 2. -- **#238**: Feature gate enforcement middleware (per-tenant flags at the edge), Wave 1. -- **#239**: Audit taxonomy extension for inference, RAG, and data subject events, Wave 1. -- **#241**: Audit retention and cold archive cron (PHIPA 10 year, Law 25), Wave 2. -- **#242**: Pre-ship license clearance gate (verified SBOM, block AGPL and GPL), Wave 3 release gate. - -Issue #232 (RAG) updated with new dependencies on #238 and #239, and now includes requirement -to emit `RAG_CHUNK_RETRIEVED` and use bge-m3 1024-dim embeddings with tenant RLS on the schema. diff --git a/.planning/carl/DESIGN.md b/.planning/carl/DESIGN.md deleted file mode 100644 index 0b3daa81a..000000000 --- a/.planning/carl/DESIGN.md +++ /dev/null @@ -1,565 +0,0 @@ -# Carl.sh — Sovereign Workspace Design Document - -Author: CTO (architect synthesis), 2026-06-25. Owner retains veto on every decision. - -Scope and authority: this document closes the gaps named in `.planning/carl/CHARTER.md` -against the existing `fundmoreai/hive` repository. It is gap-close, not greenfield. Every -technology choice below was validated against a live source on 2026-06-25; citations are inline. -The enterprise edge box is the primary product. The cloud co-work workspace is a later demo. - -## 0. Validated context and ground truth - -### 0.1 What already exists (verified in repo) - -| Surface | Location | Status | -|---------|----------|--------| -| Inference dispatch core | `apps/edge-api/internal/inference/handler.go` (`Handler.ServeHTTP`) | Live. Path-dispatched. | -| Chat orchestration | `apps/edge-api/internal/chat/dispatch.go` (`chat.NewDispatch`) | Live. Forwards to LiteLLM, SSE passthrough. | -| Embeddings handler | `apps/edge-api/internal/inference/embeddings.go` (`handleEmbeddings`) | Live. Upstream only (OpenRouter). | -| Auth selector | `apps/edge-api/internal/auth/selector.go` (`auth.Selector`), `middleware.go`, `jwt_supabase.go` | Live. `Bearer hk_*` to API-key path, else Supabase JWT. Context via `auth.UserFrom(ctx)`. | -| Audio handler | `apps/edge-api/internal/audio/handler.go` | Routes `/v1/audio/speech`, `/v1/audio/transcriptions`, `/v1/audio/translations` exist; resolve through `RoutingInterface.SelectRoute(RouteInput{NeedSTT,NeedTTS})`. No STT backend wired. | -| Files handler | `apps/edge-api/internal/files/handler.go` (`StorageBackend` interface, Supabase S3) | Live. Multipart upload to `hive-files` bucket; metadata registered via control-plane `filestore`. | -| Control-plane modules | `apps/control-plane/internal/{catalog,routing,providers,accounting,apikeys,audit,filestore,payments,identity,budgets}` | Live. | -| LiteLLM routing | `deploy/litellm/config.yaml` | Live. OpenRouter + Groq fanout, embedding routes, Ollama stubs (commented, installer uncomments). | -| Installer | `scripts/install.sh` | Live. Docker bootstrap, `.env` wizard, hardware advisor, `--with-ollama`, `--uninstall`, profiles. | -| Compose profiles | `deploy/docker/docker-compose.yml` | Live. `local`/`cloud`/`chat`/`enterprise`; services: edge-api, control-plane, litellm, redis, web-console, open-webui, caddy-owui, ollama, monitoring. | -| Migrations | `supabase/migrations/` | Live. **pgvector NOT enabled. No vector column anywhere.** Embeddings referenced by `alias_id` only. | - -The single most important seam: adding a new dialect is one new `case` in -`inference/handler.go` `ServeHTTP` plus one translator. It reuses the existing auth wrapper, -the existing LiteLLM forward, and the existing SSE plumbing. We never rebuild dispatch. - -### 0.2 Validated external facts (sources) - -- Anthropic Messages spec: content-block model, `tool_use`/`tool_result`, `stop_reason` enum - (`end_turn`, `max_tokens`, `stop_sequence`, `tool_use`, `pause_turn`, `refusal`), `usage` - with `input_tokens`/`output_tokens`, and the streaming event sequence. Source: Anthropic SDK - type definitions via Context7 (`/anthropics/anthropic-sdk-python` — `message.py`, - `helpers.md`, `examples/tools.py`) and the Anthropic streaming docs. -- pgvector: `vector` up to 2,000 dims, `halfvec` up to 4,000 dims; HNSW and IVFFlat indexes; - operators `<->` (L2), `<#>` (neg inner product), `<=>` (cosine); `CREATE EXTENSION vector`. - HNSW needs no training step and can be built on an empty table. Source: - `github.com/pgvector/pgvector` README (fetched 2026-06-25). -- Parakeet self-host, two paths both speaking OpenAI Whisper API natively: - (a) `achetronic/parakeet` — Go + ONNX Runtime 1.25.x, exposes `/v1/audio/transcriptions` - on port 5092, `Bearer` auth, `response_format`, `stream=true`, ffmpeg for non-WAV, CPU-capable. - (b) NVIDIA Riva/Speech NIM — exposes `/v1/audio/transcriptions` on port 9000, GPU. - Source: `achetronic/parakeet` README, NVIDIA NIM Speech docs (fetched 2026-06-25). -- Parakeet `parakeet-tdt-0.6b-v3`: 0.6B FastConformer-TDT, 25 languages, **24 evaluated, all - European; Bangla/Bengali NOT in the set.** Source: `huggingface.co/nvidia/parakeet-tdt-0.6b-v3`. -- LiteLLM audio: `model_info.mode: audio_transcription` registers a transcription model; the - generic OpenAI-compatible adapter (`model: openai/` + `api_base`) points LiteLLM at any - self-hosted Whisper-compatible server. Source: `docs.litellm.ai/docs/audio_transcription`. -- LiteLLM Ollama: `ollama_chat/` + `api_base` for chat; native provider. Source: - `docs.litellm.ai/docs/providers/ollama`. -- Headscale: open-source self-hosted Tailscale control server. Ships an **embedded DERP relay** - (enable in `config.yaml` `derp.server.enabled: true`), uses STUN udp/3478 plus HTTPS tcp/443 - for relay, gives port-less NAT traversal under your own control. Source: `headscale.net/stable` - and `/ref/derp/`. Raw WireGuard has no out-of-band coordinator, so it cannot hole-punch behind - symmetric/CGNAT without manual port-forward. Source: Tailscale NAT-traversal docs and comparison. - ---- - -## 1. Gap 1 — Anthropic-compatible `/v1/messages` (issue #168) - -### 1.1 Decision - -Add a thin translation layer that accepts Anthropic Messages requests, lowers them to the -internal OpenAI chat shape, drives the existing dispatch core, and lifts the OpenAI response (and -SSE stream) back into Anthropic shape. We never call real Anthropic. The model field maps to a -local or open model alias via the existing catalog. This is a pure adapter: zero new inference, -zero new provider integration. - -### 1.2 Why a translator, not a parallel path - -The chat dispatch core already forwards to LiteLLM and streams SSE. Anthropic and OpenAI differ -only in request and response envelope shape, not in the underlying token generation. A translator -keeps one inference path, one billing hook, one audit trail. Building a second native path would -double the surface that the security and billing reviewers must cover. - -### 1.3 Request mapping (Anthropic to OpenAI) - -| Anthropic Messages field | OpenAI chat field | Notes | -|--------------------------|-------------------|-------| -| `model` | `model` | Resolved through catalog alias to a local/open model. | -| `system` (string or `TextBlock[]`) | prepend a `{"role":"system"}` message | Concatenate text blocks. | -| `messages[].role` (`user`/`assistant`) | `messages[].role` | Direct. | -| `messages[].content` string | `content` string | Direct. | -| content block `{"type":"text"}` | text part | Direct. | -| content block `{"type":"image","source":{base64}}` | `image_url` with `data:` URI | Vision passthrough. | -| content block `{"type":"tool_use","id","name","input"}` | assistant `tool_calls[]` (`id`, `function.name`, `function.arguments` as JSON string) | `input` object becomes stringified `arguments`. | -| content block `{"type":"tool_result","tool_use_id","content"}` | `{"role":"tool","tool_call_id","content"}` | Map `tool_use_id` to `tool_call_id`. | -| `tools[]` (`name`,`description`,`input_schema`) | `tools[]` (`type:"function"`, `function.parameters`) | `input_schema` becomes `function.parameters`. | -| `tool_choice` (`auto`/`any`/`tool`) | `tool_choice` (`auto`/`required`/named) | `any` to `required`; `{type:"tool",name}` to named function. | -| `max_tokens` (required) | `max_tokens` | Anthropic requires it; default-guard if omitted. | -| `stop_sequences` | `stop` | Direct. | -| `temperature`,`top_p` | same | Direct. | -| `stream` | `stream` | Direct. | - -### 1.4 Response mapping (OpenAI to Anthropic, non-streaming) - -OpenAI `choices[0].message` becomes an Anthropic `message`: -- `id` to `id` (prefix `msg_`), `role:"assistant"`, `model` echoed. -- `message.content` text becomes a single `{"type":"text"}` block. -- each `tool_calls[]` becomes a `{"type":"tool_use","id","name","input"}` block (`arguments` - JSON-parsed back into `input`). -- `finish_reason` to `stop_reason`: `stop` to `end_turn`, `length` to `max_tokens`, - `tool_calls` to `tool_use`, `content_filter` to `refusal`. Validated against the Anthropic - `stop_reason` enum. -- `usage.prompt_tokens`/`completion_tokens` to `usage.input_tokens`/`output_tokens`. - -### 1.5 Streaming SSE event mapping - -OpenAI streams `chat.completion.chunk` deltas. Anthropic streams a structured event sequence. -The translator is a stateful SSE re-emitter sitting in front of the existing SSE passthrough: - -1. On first upstream chunk: emit `message_start` (with the message envelope, `usage.input_tokens`, - `stop_reason: null`), then `content_block_start` (index 0, `text` block). -2. For each text delta: emit `content_block_delta` with `{"type":"text_delta","text":...}`. -3. When the upstream emits a `tool_calls` delta: close any open text block with - `content_block_stop`, open a new `content_block_start` (`tool_use`, with `id`,`name`), then - emit `content_block_delta` with `{"type":"input_json_delta","partial_json":...}` for the - streamed `arguments` fragments. -4. On stream end: `content_block_stop` for the open block, then `message_delta` carrying the - final `stop_reason` and `usage.output_tokens`, then `message_stop`. -5. Periodic `ping` events are permitted and ignored by clients. - -This sequence matches the documented Anthropic order: `message_start`, `content_block_start`, -repeated `content_block_delta`, `content_block_stop`, `message_delta`, `message_stop`. - -### 1.6 Files and handlers to add - -| File (new) | Role | -|------------|------| -| `apps/edge-api/internal/anthropic/types.go` | Anthropic request/response/event structs (no `any`; structurally typed unions for content blocks via a tagged decoder). | -| `apps/edge-api/internal/anthropic/translate_request.go` | `ToOpenAIChat(req MessagesRequest) (chat.Request, error)`. | -| `apps/edge-api/internal/anthropic/translate_response.go` | `FromOpenAIChat(resp chat.Response) MessagesResponse`. | -| `apps/edge-api/internal/anthropic/stream.go` | `NewSSETranslator(w http.ResponseWriter) *SSETranslator` — the stateful re-emitter. | -| `apps/edge-api/internal/anthropic/handler.go` | `Handler` that decodes, translates, calls the shared chat dispatch, and writes the Anthropic envelope or stream. | - -Wiring: one line in `apps/edge-api/cmd/server/main.go` alongside the existing chat route: -`mux.Handle("/v1/messages", auth.Selector(anthropicJWTHandler, anthropicAPIKeyHandler))`, -reusing the same auth wrappers and the same `chat.Dispatch` instance. Also map -`/v1/messages/count_tokens` to a local estimator (tiktoken-equivalent) returning `{input_tokens}`, -since the Anthropic SDK probes it. - -### 1.7 Edge cases and risks - -- Interleaved text and multiple tool_use blocks in one assistant turn: the SSE translator must - track block indices. Covered by step 3 above. -- `tool_result` content can be a string or a block array (text or image): normalize to OpenAI - tool message content. Image-in-tool-result is rare; pass through as `image_url`. -- `pause_turn` and `refusal` stop reasons have no OpenAI equivalent inbound; only emitted outbound - on `content_filter`. Document as best-effort. -- The Anthropic SDK sends `anthropic-version` header; accept and ignore (we are version-agnostic). - ---- - -## 2. Gap 2 — RAG: upload, chunk, embed, store, search, ground - -### 2.1 Decision - -Add document upload, chunking, local embedding (routed through LiteLLM), PGVector storage, a -vector-search endpoint, and RAG-grounded chat. Reuse the existing files upload path and the -existing embeddings handler; add the vector layer underneath. Local embeddings ship via Ollama -(an embedding model) so the edge box has zero external dependency; OpenRouter stays a test/cloud -convenience only. - -### 2.2 Postgres schema (validated against pgvector) - -Default embedding dimension is **768** (a common open-model dimension, e.g. `nomic-embed-text` -on Ollama). pgvector `vector` supports up to 2,000 dims, so 768 and 1024 are both safe as native -`vector`. We make the dimension a deploy-time constant so the schema and the configured embedding -model agree. - -```sql --- supabase/migrations/_carl_rag.sql -CREATE EXTENSION IF NOT EXISTS vector; - -CREATE TABLE public.rag_documents ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - tenant_id uuid NOT NULL, - owner_sub text NOT NULL, - file_id text, -- links to existing filestore record - title text NOT NULL, - mime_type text NOT NULL, - status text NOT NULL DEFAULT 'pending', -- pending|chunking|embedded|failed - created_at timestamptz NOT NULL DEFAULT now() -); - -CREATE TABLE public.rag_chunks ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - document_id uuid NOT NULL REFERENCES public.rag_documents(id) ON DELETE CASCADE, - tenant_id uuid NOT NULL, - chunk_index int NOT NULL, - content text NOT NULL, - token_count int NOT NULL, - embedding vector(768) NOT NULL, - created_at timestamptz NOT NULL DEFAULT now() -); - --- HNSW: better speed-recall than IVFFlat, builds on empty table (no training step). --- Cosine distance matches normalized open-model embeddings. -CREATE INDEX rag_chunks_embedding_hnsw - ON public.rag_chunks USING hnsw (embedding vector_cosine_ops) - WITH (m = 16, ef_construction = 64); - -CREATE INDEX rag_chunks_tenant_doc ON public.rag_chunks (tenant_id, document_id); - --- Row-level security keyed on tenant_id, consistent with existing identity model. -ALTER TABLE public.rag_documents ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.rag_chunks ENABLE ROW LEVEL SECURITY; -``` - -Index choice rationale (validated): HNSW gives the better speed-recall tradeoff and needs no -training step, so it works from an empty table; IVFFlat would need data first and a tuned `lists`. -Cosine (`vector_cosine_ops`, `<=>`) is correct for normalized sentence embeddings. If a future -embedding model exceeds 2,000 dims, switch the column to `halfvec(<=4000)` with `halfvec_cosine_ops`. - -### 2.3 Pipeline - -1. Upload: reuse `files` handler to land the raw document in the `hive-files` bucket and register - filestore metadata. RAG ingestion takes the `file_id`. -2. Extract + chunk: control-plane worker pulls the file, extracts text (txt/md/pdf/docx), - chunks by ~512 tokens with ~64-token overlap, writes `rag_documents` + `rag_chunks` rows with - `embedding` left null and `status=chunking`. -3. Embed: for each chunk, call the edge embeddings path (LiteLLM `route-local-embedding`, an - Ollama embed model on the edge box; OpenRouter fallback only in cloud/test). Store the vector, - set `status=embedded`. -4. Search: `POST /v1/rag/search {query, top_k, document_ids?}` embeds the query and runs - `ORDER BY embedding <=> $queryvec LIMIT top_k`, RLS-scoped to the caller's tenant. -5. Ground: RAG-grounded chat is a server-side option (`{"rag":{"enabled":true,"top_k":N}}` on the - chat/messages request, or a dedicated `/v1/rag/chat`) that runs search, injects the retrieved - chunks as system context with citations, then calls the normal dispatch core. - -### 2.4 Endpoints and wiring - -| Endpoint (new) | Layer | Role | -|----------------|-------|------| -| `POST /v1/rag/documents` | edge-api `internal/rag/handler.go` | Register a file for ingestion (or accept inline upload, delegating to `files`). | -| `GET /v1/rag/documents`, `GET /v1/rag/documents/{id}` | edge-api | List/status. | -| `DELETE /v1/rag/documents/{id}` | edge-api | Cascade delete chunks. | -| `POST /v1/rag/search` | edge-api `internal/rag/search.go` | Vector search, returns chunks + scores. | -| `POST /v1/rag/chat` (or `rag` flag on chat) | edge-api | Grounded generation. | -| ingestion worker | control-plane `internal/rag/` | Chunk + embed + write, async. | -| embedding route | `deploy/litellm/config.yaml` add `route-local-embedding` (Ollama embed model) | Local-first embeddings. | - -New files: `apps/edge-api/internal/rag/{handler.go,search.go,types.go,repository.go}`, -`apps/control-plane/internal/rag/{ingest.go,chunk.go,repository.go}`, the migration above, and a -LiteLLM `route-local-embedding` entry. Database work owned by `database-reviewer`; vector queries -parameterized (no string interpolation of vectors). - ---- - -## 3. Gap 3 — Voice / STT with NVIDIA Parakeet, self-hosted - -### 3.1 Decision - -Serve Parakeet as a sidecar container that already speaks the OpenAI Whisper API, then point -LiteLLM at it and wire the existing `/v1/audio/transcriptions` route through to it. Default serving -path is the **`achetronic/parakeet` Go + ONNX server** because it is CPU-capable, single-binary, -ships ffmpeg in its image, and exposes `/v1/audio/transcriptions` natively. On GPU edge boxes, -NVIDIA Riva/Speech NIM is the optional higher-throughput path; both speak the same endpoint so the -wiring is identical. - -### 3.2 Why this serving path (validated tradeoff) - -- `achetronic/parakeet`: Go + ONNX Runtime 1.25.x, port 5092, `Bearer` auth, `response_format`, - `stream=true`, ffmpeg for MP3/OGG/etc. CPU-only inference is supported, so it runs on the - no-GPU edge tier. Model files downloaded separately (owner provides host or download per charter). -- NVIDIA Riva/Speech NIM: port 9000, GPU, higher throughput, official. Heavier footprint, requires - NGC access and a GPU. Reserve for the GPU edge tier. -- NeMo directly: a training/research toolkit, not a server. Rejected as the default serving path; - it is the source the ONNX models are exported from. - -Decision: ship the ONNX Go server as the default sidecar; allow swapping in Riva NIM by env var -because the endpoint contract is identical. - -### 3.3 Wiring - -1. Compose: add a `parakeet` service to `docker-compose.yml` (new profile tag `voice`, composable - with `enterprise`). Mount the model directory the owner provides; expose 5092 on the internal - network only. -2. LiteLLM: add a transcription model entry pointing at the sidecar via the generic adapter: - ```yaml - - model_name: route-local-transcription - litellm_params: - model: openai/parakeet # generic OpenAI-compatible adapter - api_base: http://parakeet:5092/v1 - api_key: os.environ/PARAKEET_API_KEY - model_info: - mode: audio_transcription - ``` -3. edge-api: the `/v1/audio/transcriptions` route already exists in `internal/audio/handler.go` - and resolves through `RoutingInterface.SelectRoute(RouteInput{NeedSTT:true})`. Add a catalog - alias + provider_route so STT resolves to `route-local-transcription`. No new edge handler; - only the routing seed and the LiteLLM entry. -4. Result: customers call OpenAI-standard `POST /v1/audio/transcriptions` on the Hive edge; the - audio never leaves the box. - -### 3.4 Bangla relevance (issue #178) - -`parakeet-tdt-0.6b-v3` covers 25 languages, all European; **Bangla is not in the set** (verified -on the model card). Consequences and plan: - -- STT (speech to text) in Bangla is NOT delivered by Parakeet v3. Do not claim Bangla ASR via - Parakeet. Options for Bangla STT, deferred and tracked under #178: a Bangla-capable Whisper - variant served through the same OpenAI-compatible sidecar contract (the wiring is identical), or - a future Bangla Parakeet/NeMo checkpoint. -- Bangla TEXT generation already has a path: the installer advisor flags `qwen3:8b` as - Bangla-capable on the 8 GB tier, so RAG and chat in Bangla work via Ollama today. The sidecar - contract makes adding Bangla STT later a config change, not a rebuild. - ---- - -## 4. Gap 4 — Self-hosted relay for port-less edge remote access - -### 4.1 Decision - -Default is LAN serve via the box's own Caddy (already in the chat/enterprise profiles). For remote -access without opening firewall ports, ship an **optional Headscale** coordination server with its -**embedded DERP relay enabled**, and have edge nodes plus clients join via the standard Tailscale -client against our own Headscale. No Tailscale SaaS, no external relay keys. Raw WireGuard is the -fallback for the simple case where the operator can do a static port-forward. - -### 4.2 Why Headscale over raw WireGuard (validated) - -- Raw WireGuard has no out-of-band coordinator, so two peers behind symmetric NAT or CGNAT cannot - hole-punch; the operator must port-forward or set a static public endpoint. That violates the - "port-less" goal on consumer/enterprise NAT. -- Headscale is the open-source Tailscale control server and ships an **embedded DERP relay** - (`derp.server.enabled: true`), using STUN udp/3478 for discovery and HTTPS tcp/443 for relayed - packets. DERP over 443 is effectively unblockable and needs no inbound port-forward on the edge. - All relays run on the customer's own hardware. This delivers port-less remote access while - staying fully self-hosted. -- Tradeoff: relayed mode (the ~5% of connections that cannot go direct) drops throughput (tens of - Mbps) and adds latency due to TCP head-of-line blocking; most connections still go direct - WireGuard. Acceptable for a control/console channel and light inference; document it. - -### 4.3 How Carl.sh sets it up (optional, seamless) - -- New installer flag `--with-relay`. When set, the installer: - 1. Adds a `headscale` service to compose (new profile `relay`). - 2. Generates a Headscale `config.yaml` with `derp.server.enabled: true` and the box's public - IPs (detected or prompted), all secrets auto-generated, nothing external. - 3. Creates a Headscale user and a preauthkey, registers the edge node, and prints a single - join command plus a QR for client devices. - 4. Leaves the Tailscale client install to the user's devices (one command), against our - Headscale URL. -- When `--with-relay` is absent, nothing changes: LAN-only via Caddy, zero new surface. -- Security note: Headscale ACLs restrict which client devices can reach the edge services; the - security reviewer owns the default-deny ACL and the preauthkey TTL. - ---- - -## 5. Gap 5 — Containerized co-work workspace (demo, after edge core) - -### 5.1 What "co-work ChatGPT" means here - -A multi-user shared AI workspace on the edge box: several authenticated users in one tenant share -(a) a common chat surface, (b) a shared RAG corpus (the tenant's documents), and (c) shared or -visible sessions/threads, all grounded on the tenant's own documents and served by the local model. -It is the team-facing front end over the same edge APIs, not a new inference engine. - -### 5.2 Decision: self-contained isolated container first - -Build it as one isolated container component layered on the existing chat front end (Open WebUI is -already in the `chat` profile behind Caddy). Concretely: - -- Reuse Open WebUI as the multi-user shell (it already does users, auth, chat threads), pointed at - the Hive edge `/v1` (and `/v1/messages`) instead of any external API. -- Add the RAG surface (Gap 2) as the shared corpus: documents uploaded by a tenant are searchable - by every user in that tenant via RLS scoping on `tenant_id`. -- Sessions: shared threads are a tenant-scoped table; "co-work" = thread visibility within a tenant. -- Everything stays in one compose component set so the demo is `docker compose --profile cowork up`. - -Online sandbox services (Daytona or similar) are explicitly **later**: they would host -per-user ephemeral dev environments, which is out of scope until the edge core is solid. The -OpenCode coding agent is deferred and is not the target. - -### 5.3 Why container-first - -The edge product's whole promise is "runs entirely on the customer server." A self-contained -container honors that and keeps the demo reproducible. Cloud sandboxing is a hosting optimization -that can come after the sovereign edge story is proven. - ---- - -## 6. The Carl.sh seamless installer plan - -### 6.1 Principle - -`scripts/install.sh` is the base and already does the hard parts (Docker bootstrap, `.env` wizard, -hardware-aware model advisor, profile selection, Ollama enablement, uninstall). Carl.sh is the same -installer extended with the new deltas behind opt-in flags, adding **no unnecessary complexity**: -one command brings up Docker, all deps, the model pull, and the hardware advisor. - -### 6.2 Extensions (additive, all opt-in) - -| Flag | Effect | -|------|--------| -| `--with-ollama` (exists) | Local inference + hardware advisor + model pull. | -| `--with-rag` (new) | Applies the RAG migration (`CREATE EXTENSION vector` + tables), seeds `route-local-embedding`, pulls the embed model. | -| `--with-voice` (new) | Adds the `parakeet` sidecar, seeds `route-local-transcription`, mounts the model dir the owner provides. | -| `--with-relay` (new) | Adds Headscale + embedded DERP, generates config + preauthkey, prints join command. | -| `--cowork` (new, demo) | Brings up the co-work profile (Open WebUI shell + shared RAG). | - -Default `curl ... | bash` with no flags: the existing enterprise edge stack serving OpenAI and -(after Gap 1 lands) Anthropic dialects against a hardware-advised local model. Each flag is a -self-contained step appended to `main()` in the existing piping-safe structure. The advisor already -recommends Bangla-capable `qwen3:8b` on the 8 GB tier, so the edge speaks Bangla text out of the box. - -### 6.3 Carl.sh naming - -`Carl.sh` is the public entrypoint name for this installer. Implementation stays in -`scripts/install.sh`; `Carl.sh` is the curl target alias and brand. No second installer is created. - ---- - -## 7. Build sequence, dependency graph, and parallelization - -### 7.1 Dependency graph - -``` - [Repo baseline: dispatch core, auth, files, LiteLLM, installer] - | | | | - +---------------+ +--------+ +-------+ +-----+ - v v v v - (A) Anthropic /v1/messages (B) RAG vector (C) Parakeet STT (D) Relay (Headscale) - - types/translate/stream - migration - sidecar+compose - compose+config - - reuses chat.Dispatch - rag endpoints - LiteLLM entry - installer flag - - reuses auth.Selector - ingest worker - routing seed - ACLs - | - LiteLLM embed - (audio route (fully independent) - | - installer flag already exists) - | | | - +-----------+------------------+------------------+ - v - (E) Installer flags wiring (--with-rag/--with-voice/--with-relay) - | - v - (F) Co-work workspace demo (needs B for shared RAG, A for Anthropic in shell) - | - v - (G) E2E: installer to dual-dialect + RAG + voice + relay green -``` - -### 7.2 What can run in parallel (separate builder teams) - -**Wave 1 (fully independent, four teams concurrently):** -- Team A — Anthropic `/v1/messages` translator (Go, edge-api `internal/anthropic/`). Depends only - on the existing dispatch core. Reviewer: `go-reviewer` + `security-reviewer` (input boundary). -- Team B — RAG: migration + `internal/rag` (edge + control-plane) + LiteLLM embed route. Reviewer: - `database-reviewer` (schema, vector queries, RLS) + `go-reviewer`. -- Team C — Parakeet sidecar + LiteLLM transcription entry + routing seed. Mostly compose/config; - small Go for the catalog seed. Reviewer: `go-reviewer` + `security-reviewer` (no audio egress). -- Team D — Headscale relay: compose service + config generator + installer `--with-relay`. - Independent of A/B/C. Reviewer: `security-reviewer` (ACLs, key TTL) + `go-reviewer` for installer. - -**Wave 2 (after its inputs):** -- Team E — installer flag wiring for `--with-rag`/`--with-voice`/`--with-relay`. Needs B, C, D - artifacts to exist (the migration, the sidecar, the Headscale config). Small, sequential after - Wave 1 merges. - -**Wave 3:** -- Team F — co-work workspace demo. Needs B (shared RAG) and A (Anthropic in the shell). After E. - -**Wave 4:** -- Team G — `e2e-runner` full-path verification: install via Carl.sh, hit `/v1/chat/completions`, - `/v1/messages`, `/v1/rag/search`, `/v1/audio/transcriptions`, and a relayed remote call. - -Critical path: A and B are the long poles (most code + review). C and D are short and should -finish first, de-risking the installer wiring early. F is gated on A+B. - -### 7.3 Per-team guardrails (from the orchestrator contract) - -Each builder works only in its own worktree, verifies `git status -sb` after checkout, pushes -`git push origin HEAD:`, confirms the remote ref, and never touches the shared checkout. -Builder self-reports are not verification; an independent reviewer reads each pushed diff. Merge -gate: all checks green plus zero unresolved threads, then squash merge with branch deletion. - ---- - -## 8. Test strategy (Grok and free local models as test-only) - -### 8.1 Principle - -Grok (xAI) and free OpenAI-compatible local models are **test-time conveniences, never shipped -runtime dependencies**. They exist to exercise the dialect translators and the RAG/voice paths -without burning a real provider, and they are wired only behind the `test` profile and test env. - -### 8.2 Layers - -- **Unit (Go):** table-driven tests for the Anthropic translator (request lowering, response - lifting, the full SSE event sequence including interleaved tool_use), and for RAG chunking + - vector query construction. No network. Run via the Docker toolchain per CLAUDE.md. -- **Contract:** drive `/v1/messages` with the **real Anthropic SDK** (the SDK is already a dev - dep pattern in `packages/sdk-tests`) pointed at the Hive edge `base_url`, asserting the SDK - parses our responses and streams. Pointed at a local free model as the backing engine, or Grok - as the upstream behind LiteLLM for a richer model during the test only. -- **RAG E2E:** upload a fixture doc, assert chunks + embeddings land, assert `/v1/rag/search` - returns the right chunk, assert grounded chat cites it. Embeddings via the local embed model. -- **Voice E2E:** post a fixture WAV to `/v1/audio/transcriptions`, assert transcript. Parakeet - sidecar with the owner-provided model in the test profile. -- **Relay E2E:** bring up Headscale + DERP in the test profile, register two nodes, assert a - relayed request to the edge succeeds with no inbound port-forward. -- **Installer E2E:** run Carl.sh in a clean container with each flag, assert the health endpoints - and the seeded routes. Owned by `e2e-runner`. - -### 8.3 Test config isolation - -Grok and free-model keys live only in `.env.test` and the `test`/`tools` compose profiles. The -shipped `enterprise` profile references neither. A guard test asserts no test-only provider key is -read on the enterprise path, protecting the "zero external keys at the edge" guarantee. - ---- - -## 9. Top technical risks - -1. **Anthropic SSE fidelity.** The streaming event sequence (block indices, interleaved text and - multiple tool_use, `input_json_delta`) is the highest-bug-density area; a malformed sequence - breaks the real Anthropic SDK silently. Mitigation: contract test against the real SDK in CI. -2. **Embedding dimension and model lock-in.** The `vector(768)` column must match the configured - embed model forever; changing the model orphans stored vectors. Mitigation: dimension is a - deploy constant, a re-embed migration path is documented, and the column can move to `halfvec` - if a bigger model is ever chosen. -3. **Parakeet has no Bangla.** Marketing or #178 could over-promise Bangla voice. Mitigation: this - doc states Bangla ASR is not delivered by Parakeet v3; only the sidecar contract is reusable for - a future Bangla STT model. Bangla text generation is the only Bangla claim today. -4. **Relay throughput and operability.** DERP-relayed connections are slow (TCP HOL blocking) and - Headscale is operationally heavier than a port-forward; a misconfigured ACL could expose edge - services. Mitigation: default-deny ACL owned by security review, relay strictly opt-in, document - it as control/light-inference channel not bulk transport. -5. **Self-hosted Supabase/pgvector at the edge.** The repo assumes Supabase-hosted Postgres, but - the sovereign edge must run Postgres+pgvector locally with zero external SaaS. Mitigation: - confirm the enterprise profile ships a local Postgres with the `vector` extension and that the - filestore S3 backend has a local/MinIO-free equivalent, or this breaks the zero-SaaS promise. - (Flagged for the owner in Section 11.) - ---- - -## 10. Recommended parallel build sequence (summary) - -- **Now, in parallel:** Team A (Anthropic), Team B (RAG), Team C (Parakeet), Team D (Relay). -- **Then:** Team E (installer flag wiring) once B/C/D artifacts exist. -- **Then:** Team F (co-work demo) once A+B merged. -- **Finally:** Team G (full E2E via Carl.sh). -- Short poles C and D land first to de-risk the installer; A and B are the critical path. - ---- - -## 11. Decisions that genuinely need the owner - -1. **Edge data plane sovereignty (highest priority).** The repo today depends on Supabase-hosted - Postgres and S3-protocol storage. The sovereign edge promise is "zero external SaaS." Confirm - the plan: ship a **local Postgres (with pgvector) and local object storage** inside the - enterprise box, with Supabase reserved for the cloud demo only. Without this, RAG vectors and - uploaded documents would leave the customer's server, contradicting the core pitch. -2. **Embedding model + dimension lock.** Approve the default local embed model and its dimension - (proposed `nomic-embed-text`, 768) so the migration and the LiteLLM route are fixed. This is a - one-way door once vectors are stored. -3. **Bangla voice expectation (#178).** Confirm it is acceptable that v1 ships Bangla **text** - only (via Ollama) and that Bangla **STT** is deferred behind the same sidecar contract, since - Parakeet v3 has no Bangla. -4. **Parakeet default serving tier.** Approve the CPU-capable `achetronic/parakeet` ONNX Go server - as the default sidecar, with NVIDIA Riva NIM as the GPU upgrade, given both speak the identical - endpoint. (CTO default: yes.) diff --git a/.planning/debug/flaky-usage-tokens-root-cause.md b/.planning/debug/flaky-usage-tokens-root-cause.md deleted file mode 100644 index d0ce19ffd..000000000 --- a/.planning/debug/flaky-usage-tokens-root-cause.md +++ /dev/null @@ -1,154 +0,0 @@ -# Flaky `usage.completion_tokens = 0` — root-cause investigation - -**Date**: 2026-04-24 -**Branch**: `debug/flaky-usage-tokens` -**Against**: `https://api-hive.scubed.co/v1/chat/completions`, model `hive-default` - -## TL;DR - -- Flake rate observed: **~6% (1/17)** on the live staging route. -- Zero-`completion_tokens` responses still carry a **non-empty assistant - message** — i.e. real output was produced, usage reported `0`. -- Upstream (OpenRouter via LiteLLM) is emitting the broken usage block; - edge-api passes it through unchanged (`normalizeChatCompletion` is a pure - Unmarshal/Remarshal — `apps/edge-api/internal/inference/chat_completions.go:49-64`). -- Orchestrator writes `OutputTokens = usage.CompletionTokens` directly into - the accounting ledger — `apps/edge-api/internal/inference/orchestrator.go:262`. - No clamp, no tokenizer fallback. -- **Billing impact**: every flaked request bills `$0` output → **revenue leak - proportional to flake rate** (~6% of chat completions against current - model mix). - -## Repro burst (17 samples, sequential, same prompt "reply with ok") - -| # | id prefix | pt | ct | tt | content | -|---|----------------------------|----|-----|-----|-----------| -| 1 | gen-1777055948-O4IsYgGT… | 19 | 24 | 43 | `ok` | -| 2 | gen-1777055949-ZvqMupMV… | 13 | 2 | 15 | `ok` | -| 3 | gen-1777055950-cUnptZ6n… | 19 | 54 | 73 | `ok` | -| 4 | gen-1777055950-uKXARwBq… | 19 | 27 | 46 | `ok` | -| 5 | gen-1777055951-enmGTJxv… | 13 | 2 | 15 | `ok` | -| 6 | gen-1777055952-a1pzB8df… | 16 | 219 | 235 | `ok` | -| 7 | gen-1777055958-eYj3DJXc… | 16 | 88 | 104 | `\n\nok\n`| -| 8 | gen-1777055961-cBD1fBao… | 21 | 2 | 23 | `ok` | -| 9 | gen-1777055962-DyFTCOzk… | 16 | 133 | 149 | `ok\n` | -|10 | gen-1777055966-SdlISKsX… | 41 | 27 | 68 | `ok` | -|11 | gen-1777055979-EouKo5Oz… | 19 | 18 | 37 | `ok` | -|12 | gen-1777055985-DVT7RV2S… | 13 | 2 | 15 | `Ok` | -|13 | gen-1777055986-lLVbHfw9… | 70 | 18 | 88 | `Ok.` | -|14 | gen-1777055987-KiZzwgfh… | 70 | 17 | 87 | `ok` | -|15 | **gen-1777055988-QQJOozdB…** | **4** | **0** | **4** | **`ok\n`** | -|16 | gen-1777055991-UWsPAlYs… | 13 | 442 | 455 | `ok` | -|17 | gen-1777055993-UdxALnUv… | 15 | 56 | 71 | `ok` | - -TOTAL=17 ct_zero=1 flake_rate=5.9% - -Observations: - -1. **ct=0 on non-empty output** (sample 15): `content='ok\n'` is a real - assistant message. The backing provider returned output but reported no - completion tokens. `prompt_tokens=4` is also the smallest in the batch, - suggesting a different backing tokenizer (shorter-count provider) on - that route attempt. -2. **Enormous ct variance for identical reply** — `ct` ranges 2 → 442 for - the exact same `'ok'` response. The `completion_tokens_details.reasoning_tokens` - field (not shown but present in raw) absorbs hidden "thinking" tokens - from reasoning-capable upstream models. This is NOT flake, but is a UX - surprise for customers comparing usage across requests. -3. **pt varies 4 → 70** for the same 3-word user message. OpenRouter routes - to different backing providers with different system-prompt injection + - different tokenizers. `hive-default` is not pinned. - -## Hypotheses — eliminated vs confirmed - -| # | Hypothesis | Status | -|---|-----------|--------| -| 1 | OpenRouter upstream reports `ct=0` on some routes/providers | **CONFIRMED** (upstream-origin `gen-*` id + non-empty content) | -| 2 | LiteLLM collapses usage on cached responses | Not applicable — no cache in LiteLLM config for this route | -| 3 | edge-api zeroes usage on error-sanitization path | Ruled out — `normalizeChatCompletion` is pure passthrough; no filter on zeros | -| 4 | Specific backing provider truncates usage for short outputs | **LIKELY** — ct=0 correlates with pt=4 (shortest tokenizer) | -| 5 | Stream-initiated non-stream calls drop usage | Not applicable — this is strict non-stream (`stream=false` default) | - -## Root cause - -OpenRouter serves `hive-default` through a multi-provider pool. When the -request lands on a backing provider that does not report -`completion_tokens` for its native response envelope (observed on at least -one provider in rotation for short outputs), the `usage` block arrives as -`{prompt_tokens: N, completion_tokens: 0, total_tokens: N}`. LiteLLM -forwards unchanged; edge-api passes through unchanged; orchestrator records -`OutputTokens = 0` and `HiveCreditDelta = prompt_tokens + 0`. - -**Secondary issue**: `hive-default` is not pinned to a single backing -provider, which causes the observed wild variance in both `pt` and `ct` -even for identical requests. - -## Fix plan - -Two independent pieces — recommended as separate PRs so revert can isolate -billing from routing policy. - -### PR 1 — edge-api: tokenizer fallback for zero-usage responses - -- In `normalizeChatCompletion` (and the matching `normalizeResponses` / - `normalizeCompletion` paths), after Unmarshal, inspect every choice's - message content. If `usage.completion_tokens == 0` **and any choice has - non-empty text content**, compute an estimated `completion_tokens` from a - lightweight tokenizer (approximate cl100k heuristic: `byte_len / 4`, - rounded up, minimum 1). -- Overwrite `usage.completion_tokens` with the estimate and recompute - `total_tokens = prompt_tokens + completion_tokens`. -- Emit a structured warning log: - `usage_estimated_from_tokenizer upstream_ct=0 estimated_ct= - model= upstream_id=` -- Leave the rest of the `completion_tokens_details` block alone — reasoning - tokens are a separate upstream-reported facet and clamping them would - mis-price reasoning models. -- Add a Go unit test that feeds a synthetic zero-ct response into - `normalizeChatCompletion` and asserts `ct > 0` on output. - -Constraint: do **NOT** silently hide upstream ct=0 in the accounting ledger -without logging. Billing team needs visibility into the flake rate. - -Files to touch: - -- `apps/edge-api/internal/inference/chat_completions.go` -- `apps/edge-api/internal/inference/completions.go` -- `apps/edge-api/internal/inference/responses.go` -- `apps/edge-api/internal/inference/usage_clamp.go` (new — shared helper) -- `apps/edge-api/internal/inference/handler_test.go` (or a new - `usage_clamp_test.go`) — table-driven cases for zero-ct + non-empty - content, zero-ct + empty content (legit), nil usage, reasoning tokens - preserved. - -Test acceptance: 6% burst-rerun against staging must show 0% ct=0 on -non-empty-content responses (post-clamp); reasoning-token variance still -allowed. - -### PR 2 — LiteLLM: pin `hive-default` backing providers (optional, routing) - -- `deploy/litellm/config.yaml` — add `allowed_fails` / `provider_order` - constraints to reduce the backing-provider set for `hive-default`, or add - a provider-level `usage_fallback` if supported by LiteLLM's latest OR - adapter. -- Lower scope; no billing change required if PR 1 ships. - -## Revenue impact estimate - -At **5.9% flake rate** with an average `ct ≈ 50` missed tokens per flaked -request, a service doing 10k chat completions/day leaks: - - 10,000 × 0.059 × 50 = 29,500 uncharged output tokens/day - -At current catalog pricing per million completion tokens (varies by alias), -the leak is bounded by output token price × 29.5k/M — order-of-magnitude: -pennies to dollars per day at staging scale, **material at production scale -above 1M requests/day**. - -## Out of scope for this investigation - -- Fixing the variance in `pt` across identical requests (requires LiteLLM - route pinning; separate PR) -- Auditing whether the catch-up ledger should retroactively reprice past - ct=0 transactions (business decision, not code) -- Per-provider quality-of-usage-reporting scorecard (nice-to-have) diff --git a/.planning/debug/resolved/apikey-cache-invalidation.md b/.planning/debug/resolved/apikey-cache-invalidation.md deleted file mode 100644 index 3ecac0945..000000000 --- a/.planning/debug/resolved/apikey-cache-invalidation.md +++ /dev/null @@ -1,79 +0,0 @@ -# Debug Session: API Key Cache Invalidation - -## Symptom - -Test 6 in Phase 05 UAT failed. - -Expected: -- Revoking an active key immediately blocks subsequent edge-api calls. -- Rotating a key returns a new raw secret and the old key stops working. - -Actual: -- The control-plane marks the old key revoked after rotate. -- The control-plane marks the replacement key revoked after explicit revoke. -- The edge still returns HTTP 200 for both revoked secrets on `/v1/models`. - -## Reproduction - -1. Create a key through `POST /api/v1/accounts/current/api-keys`. -2. Call `GET /v1/models` with that secret and confirm HTTP 200. -3. Rotate the key through `POST /api/v1/accounts/current/api-keys/{key_id}/rotate`. -4. Confirm the control-plane list endpoint shows the old key as `revoked`. -5. Call `GET /v1/models` with the rotated-away secret. -6. Revoke the replacement key through `POST /api/v1/accounts/current/api-keys/{key_id}/revoke`. -7. Call `GET /v1/models` with the revoked replacement secret. - -Observed on 2026-04-01: -- Both revoked secrets still returned HTTP 200. - -## Evidence - -- [apps/edge-api/internal/authz/client.go](/home/sakib/hive/apps/edge-api/internal/authz/client.go) - `Resolve` reads `auth:key:{tokenHash}` from Redis first and writes fetched snapshots back with a one-hour TTL. -- [apps/control-plane/internal/apikeys/service.go](/home/sakib/hive/apps/control-plane/internal/apikeys/service.go) - `RevokeKey` and `RotateKey` update durable key state, but they never invalidate or refresh Redis snapshots. -- [apps/control-plane/internal/apikeys/service.go](/home/sakib/hive/apps/control-plane/internal/apikeys/service.go) - `RefreshSnapshot` is an explicit no-op placeholder. - -## Root Cause - -The edge hot path trusts stale Redis snapshots for up to one hour. Revoke and rotate mutate Postgres state, but no code invalidates or rewrites the cached `auth:key:{tokenHash}` entry, so revoked secrets continue authorizing until TTL expiry. - -## Fix Direction - -- Add a real snapshot invalidation/refresh path in the control-plane API-key service. -- Trigger it from revoke, rotate, disable, enable, expiration-sensitive policy changes, and any mutation that changes authorization truth. -- Add end-to-end tests proving a revoked or rotated-away secret fails on the very next edge request. - -## Fix Applied - -**Date:** 2026-04-09 - -**Status:** Already implemented — fix was delivered in commit `506edfb` (feat(05-05): finish live key auth snapshot projections). No code changes were required in this session. - -**What was verified (by code reading):** - -- `apps/control-plane/internal/apikeys/service.go` - - `RevokeKey` (line 249): calls `s.invalidateSnapshot(ctx, updated.TokenHash)` after the Postgres mutation succeeds. - - `RotateKey` (line 303): calls `s.invalidateSnapshots(ctx, old.TokenHash, created.TokenHash)` — invalidates both the replaced key and the newly created replacement key. - - `RefreshSnapshot` (lines 407–413): real implementation — looks up key by ID, then calls `invalidateSnapshot`. Not a no-op. - - `DisableKey` and `EnableKey` also invalidate. - - `invalidateSnapshot` delegates to `SnapshotCache.InvalidateSnapshot` which calls `redis.Del("auth:key:{tokenHash}")`. - -- `apps/control-plane/cmd/server/main.go` (line 101): `apikeys.NewService(apikeysRepo, apikeys.NewRedisSnapshotCache(redisClient))` — the Redis cache is injected at startup. - -- `apps/edge-api/internal/authz/client.go` (line 90): uses key format `auth:key:{tokenHash}` — exactly matches the control-plane's `snapshotRedisKey` function (line 630 of service.go). - -- `apps/control-plane/internal/apikeys/service_test.go`: - - `TestRevokeKeyInvalidatesCachedSnapshot`: asserts revoke invalidates the correct token hash. - - `TestRevokeKeyReturnsErrorWhenSnapshotInvalidationFails`: asserts Postgres state is durable even if Redis fails. - - `TestRotateKeyInvalidatesOldAndNewSnapshots`: asserts both old and new hashes are invalidated in order. - - `TestUpdatePolicyInvalidatesCachedSnapshot`: asserts policy changes also flush the cache. - -**Root cause was correctly identified.** The fix was already applied before this debug session was continued. The debug session's state was stale relative to the codebase. - -## Resolution - -**Status:** resolved (2026-04-09) -**Fixed by:** Commit 506edfb (feat(05-05): finish live key auth snapshot projections) -**Verification:** Requires live Docker stack for E2E confirmation. diff --git a/.planning/debug/resolved/apikey-usage-attribution.md b/.planning/debug/resolved/apikey-usage-attribution.md deleted file mode 100644 index 6cf70f383..000000000 --- a/.planning/debug/resolved/apikey-usage-attribution.md +++ /dev/null @@ -1,60 +0,0 @@ -# Debug Session: API Key Usage Attribution - -## Symptom - -Test 5 in Phase 05 UAT failed. - -Expected: -- A successful API-key-backed request records `api_key_id` in usage data. -- `last_used_at` is updated on the key after settlement. - -Actual: -- The live reservation/finalize flow completed, but the request attempt had no `api_key_id`. -- The customer-visible usage-events response exposed no `api_key_id`. -- The active key still had no `last_used_at`. - -## Reproduction - -1. Create an active key through `POST /api/v1/accounts/current/api-keys`. -2. Create a reservation through `POST /api/v1/accounts/current/credits/reservations`. -3. Finalize it through `POST /api/v1/accounts/current/credits/reservations/finalize`. -4. Read attempts through `GET /api/v1/accounts/current/request-attempts?request_id=...`. -5. Read events through `GET /api/v1/accounts/current/usage-events?request_id=...`. -6. List keys through `GET /api/v1/accounts/current/api-keys`. - -Observed on 2026-04-01: -- The attempt was completed but had no `api_key_id`. -- The usage-events response only returned `reservation_created`. -- The active key still had no `last_used_at`. - -## Evidence - -- [apps/control-plane/internal/accounting/http.go](/home/sakib/hive/apps/control-plane/internal/accounting/http.go) - `createReservationRequest` has no `api_key_id` field and never populates `CreateReservationInput.APIKeyID`. -- [apps/control-plane/internal/accounting/service.go](/home/sakib/hive/apps/control-plane/internal/accounting/service.go) - `FinalizeReservation` only records a usage event when credits are released or reconciliation is needed, so the normal exact-charge success path emits no completed usage event. -- [apps/control-plane/internal/usage/http.go](/home/sakib/hive/apps/control-plane/internal/usage/http.go) - `handleListEvents` does not include `api_key_id` in the response payload even though `usage.UsageEvent` carries it. - -## Root Cause - -The public accounting entrypoint has no way to carry API-key identity into the accounting flow, so attempts and settlement logic cannot attribute work to a key. Even if attribution existed in storage, the usage-events response currently strips `api_key_id`, and the common successful finalize path emits no completed usage event for customers to inspect. - -## Fix Direction - -- Accept and validate `api_key_id` in the public accounting request path, or ensure the edge/internal caller injects it before the accounting flow begins. -- Always emit a completed usage event on successful finalize, not only on release/reconciliation branches. -- Surface `api_key_id` in usage-event responses and verify `MarkLastUsed` updates the key on attributed settlement. - -## Resolution - -**Status:** resolved (2026-04-09) -**Fixed by:** Prior Phase 05/06 implementation sessions - -All three gaps were addressed in the codebase before this debug session was revisited: - -1. `api_key_id` is accepted in `createReservationRequest` and wired into `CreateReservationInput` (accounting/http.go) -2. `FinalizeReservation` emits `UsageEventCompleted` on the normal charge path and calls `apiKeySvc.MarkLastUsed` (accounting/service.go) -3. `handleListEvents` includes `api_key_id` in the response when present (usage/http.go) - -**Verification:** Requires live Docker stack to run integration tests. diff --git a/.planning/demo/INVESTOR-DEMO.md b/.planning/demo/INVESTOR-DEMO.md deleted file mode 100644 index 038707877..000000000 --- a/.planning/demo/INVESTOR-DEMO.md +++ /dev/null @@ -1,248 +0,0 @@ -# Hive: 10-Minute Investor Demo Script - -> **Purpose:** A tight, 10-minute live walkthrough for investors. Every beat is grounded in what runs **today** unless explicitly marked `[pending: X]`. -> **Date:** 2026-06-11 -> **Presenter target time:** 10:00 total. Section timings are budgets, not floors. -> **Public surfaces only:** This script references public URLs (`api-hive.scubed.co`, planned `hive.scubed.com.bd`) and no private identifiers, keys, or account data. - ---- - -## Reality grounding (read before presenting) - -What is **live today** vs **pending** (source: `README.md`, `.planning/MVP.md`, `.planning/STATE.md`, git log): - -| Capability | State | Source of truth | -|---|---|---| -| OpenAI-compatible API (`/v1` chat, embeddings, files, images, audio) | **Live** (v1.0 shipped 2026-04-21) | MVP capability read | -| Prepaid BDT billing (bKash, SSLCommerz, Stripe), `math/big` FX | **Live** (v1.0) | README, MVP | -| Chat UI (Open WebUI behind Caddy) | **Live** (Phase 19 merged) | MVP capability read | -| File RAG (upload, doc Q&A) | **Live**, Open WebUI native | MVP | -| Bangla locale (bn-BD) in chat | **Staged for upstream** (PR #196); enablement plus staging deploy `[pending: Phase 25]` | git log, STATE | -| `cloud` plus `enterprise` compose profiles | **Live** (PR #194 merged) | docker-compose.yml | -| Optional Ollama backend (EnterpriseEdge) | **Live** (config-only, enterprise profile) | docker-compose.yml | -| Provider catalog schema, custom providers, tenant model visibility, tools capability flag | **Merged** (PRs #197, #199) | git log | -| Tool/`tool_choice` passthrough at edge-api | `[pending: Phase 20-05]`, capability flag merged but edge-api passthrough not yet wired | Phase 20 PLAN.md | -| Hardware advisor and one-line installer wizard | `[pending: Phase 30, v1.3]` | v1.3 device doc | -| DGX Spark / RTX Spark hardware in hand | `[pending: post-funding]`, demo runs on a dev machine | MVP owner answers | - -**Honesty rule for the room:** when a beat is pending, say so in one sentence and show the closest live proxy. Investors reward candor; a caught overclaim kills the round. - ---- - -## Pre-flight checklist (do 30 minutes before) - -- [ ] Laptop on a known-good network; phone hotspot ready as backup. -- [ ] `api-hive.scubed.co` health check returns 200: `curl -s https://api-hive.scubed.co/health`. -- [ ] A funded demo API key exported in the terminal env (never shown on screen): `export HIVE_KEY=`. -- [ ] Terminal font 18pt or larger, dark theme, prompt cleaned of any identifiers. -- [ ] Chat workstation tab pre-loaded `[pending: Phase 25 staging URL]`. **Fallback:** local `docker compose --profile enterprise --profile chat up` on `http://localhost:8090`. -- [ ] EnterpriseEdge: clean VM or local stack pre-pulled so `up` is fast. -- [ ] Recorded GIF/MP4 of every live beat saved locally (see per-beat fallback notes). If network drops, switch to the recording without breaking stride. -- [ ] Slides: market-numbers slide, roadmap board, ask slide loaded and on the right monitor. - ---- - -## 1. Cold open: the problem (60s) - -**On screen:** Single market-numbers slide. - -**Slide content (from `.planning/MVP.md` market read):** -- **170M people.** No Bangladesh-localized, ChatGPT-class product bills in BDT. -- Global AI products price in **USD and require foreign cards**, a hard barrier for BD consumers and SMEs. -- BD developers have **no OpenAI-compatible API billable in BDT**. -- A **near-term enterprise self-host window**: DGX Spark ships now ($4,699 retail), RTX Spark class lands fall 2026. No local player serves it. - -**Presenter script (verbatim, ~45s):** -> "A hundred and seventy million people. Not one AI platform takes bKash. A developer in Dhaka who wants to ship an AI feature is blocked at the checkout: every global API wants a US dollar card most of them cannot get. We built the gateway that takes local money and speaks the same API the whole world already codes against. Let me show you three things that work today, and one that's coming." - -**Presenter note:** Do not read the slide. Say the numbers once, then move. The slide stays up for the full 60s. - -**Fallback:** Slide is local; no network needed. No GIF required for the cold open. - ---- - -## 2. Chat workstation (2 min) - -**Goal:** Show a consumer-grade chat product with the Bangla angle and file RAG, all on Hive's own stack. - -**Beat A, open the chat (20s).** -- Live at the staging chat URL `[pending: Phase 25 deploy]`. -- **Fallback (works today):** local Open WebUI via `docker compose --env-file ../../.env --profile enterprise --profile chat up --build`, reachable at `http://localhost:8090` (Caddy in front of Open WebUI). -- Send one English prompt. Show streaming response. - -**Beat B, Bangla angle (40s).** -- Type a prompt in Bangla; the model answers in Bangla. -- Narrate: "The chat layer is Open WebUI on our stack. The Bangla locale is staged as an upstream contribution `[pending: Phase 25]`. Today I'll demo the model answering in Bangla, and the localized UI chrome lands with the staging deploy." -- **Presenter note:** Be precise. The *model* speaks Bangla today; the *UI locale* (bn-BD chrome) is the pending piece. Don't blur the two. - -**Beat C, file RAG (40s).** -- Drag a PDF into the chat (use a neutral public document, never a borrower, lender, or any confidential file). -- Ask a question only answerable from the document. Show the grounded answer. -- Narrate: "This is Open WebUI's native RAG, running entirely on our box. For EnterpriseEdge, that file never leaves the customer's server." - -**Fallback plan:** Pre-record a GIF of the full flow (English prompt, Bangla answer, PDF drop, grounded answer). If the staging URL is down or network fails, run the same flow on localhost; if the local stack is cold, play the GIF and narrate live. - ---- - -## 3. Developer API (2 min) - -**Goal:** Prove the core wedge. Switch from OpenAI to Hive with a base-URL and key change, billed in BDT. - -**Beat A, raw curl, free model (40s).** Real command against the live staging host: - -```bash -curl https://api-hive.scubed.co/v1/chat/completions \ - -H "Authorization: Bearer $HIVE_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "model": "llama-3.3-70b", - "messages": [{"role": "user", "content": "Say hello to our investors in one sentence."}] - }' -``` - -- Narrate: "Standard OpenAI chat-completions shape. Free model, routed provider-agnostically behind the gateway. Billed in BDT credits." -- **Presenter note:** The `model` value is a Hive alias resolved by the catalog; the free backing route is an OpenRouter `:free` model. Keep the key in an env var, never on screen. - -**Beat B, same code, OpenAI SDK pointed at Hive (40s).** The "one-line switch" moment: - -```python -from openai import OpenAI - -client = OpenAI( - base_url="https://api-hive.scubed.co/v1", - api_key="", -) - -resp = client.chat.completions.create( - model="llama-3.3-70b", - messages=[{"role": "user", "content": "Same call, official OpenAI SDK."}], -) -print(resp.choices[0].message.content) -``` - -- Narrate: "This is the official OpenAI SDK. The only change from a real OpenAI app is the base URL and the key. Every existing OpenAI codebase in Bangladesh is a drop-in customer." - -**Beat C, tools array (40s).** -- Show a request carrying a `tools` array against a tool-capable alias. -- **Honest framing:** "Provider capability routing, the catalog that knows which models support tools, just merged (PR #197). The edge-API passthrough that forwards the `tools` array to a capable route is the last wire `[pending: Phase 20-05]`. Here's the capability flag live in the catalog today; the passthrough lands this phase." -- **Presenter note:** Do NOT claim tool calls fully execute end-to-end today. Show the capability schema and flag (merged) and state the passthrough is the pending step. If it lands before the demo, swap this note and show a real tool round-trip. - -**Fallback plan:** Pre-record a GIF of the curl plus SDK calls returning real completions. If the network or staging host is down, play the GIF. For Beat C, a screen capture of the merged capability flag in the catalog (or the Phase 20 plan) carries the point honestly. - ---- - -## 4. EnterpriseEdge (2 min) - -**Goal:** The sovereign-AI pitch. Banks and government cannot use foreign cloud; this is their box. - -**Beat A, one-liner on a clean VM (50s).** -- On a clean VM (or local), bring up the self-hosted stack: - -```bash -cd deploy/docker -docker compose --env-file ../../.env --profile enterprise up --build -``` - -- Narrate: "One compose profile. Gateway, chat UI, model router, and an optional local Ollama backend, all on one box, no external dependency. Same codebase as our cloud; one flag flips it to self-hosted." -- **Presenter note:** The `enterprise` profile is live (PR #194). A polished single-command installer wizard (curl-pipe bootstrap) is `[pending: Phase 30, v1.3]`. - -**Beat B, hardware advisor moment (40s).** -- **This beat is `[pending: Phase 30, v1.3]`.** Present it as roadmap, shown via a mock or slide, not as a live feature. -- Narrate: "At install, the box inspects its own hardware (RAM, VRAM, NPU class) and recommends the right model and quantization tier so the operator never hits an out-of-memory wall on first run. On a DGX Spark, 128GB unified memory, it recommends a 70B-class model at Q4." -- **Presenter note:** Say "this is on our v1.3 roadmap" explicitly. Show the advisor as a wireframe or slide. Do not run it as if it exists. - -**Beat C, the pitch (30s).** -> "A bank in Dhaka cannot send customer data to a US cloud. A government ministry cannot either. Regulation and sovereignty rule out every foreign API. EnterpriseEdge is their AI, on their server, in their building: the same OpenAI-compatible API our cloud serves, with zero data egress. When RTX Spark workstations ship this fall, the hardware to run this sits on a desk for under five thousand dollars." - -**Fallback plan:** Pre-record a GIF of `docker compose --profile enterprise up` reaching healthy (`/health` 200 on edge plus control-plane). The hardware advisor is a slide regardless, with no live dependency, so no GIF is needed; just present the wireframe. - ---- - -## 5. Business (2 min) - -**Goal:** Show the money rails are real, the burn is tiny, and the path is mapped. - -**Beat A, prepaid BDT rails, live (40s).** -- Show the developer console billing page (or a recorded flow) with the three rails: **bKash, SSLCommerz, Stripe**. -- Narrate: "Prepaid credits. Three payment rails, all live and shipped in v1.0. FX is computed with arbitrary-precision math, not floats, so credit accounting never drifts." -- **Regulatory note (on stage):** For BD customers we never display FX rates or exchange language; `amount_usd` is omitted from BD payment responses. Mention this as a compliance strength, not a limitation. - -**Beat B, burn (20s).** -- Narrate: "We have no hardware bill and no idle cloud. The demo runs on a dev machine; the cloud stack is lean Go on managed infra. Burn is under fifty dollars a month today. Funding buys hardware and go-to-market, not survival." -- **Presenter note:** The under-$50/month figure reflects today's pre-funding posture (dev machine plus managed Supabase/Redis, free-tier models), per MVP owner answers. Frame it as capital efficiency. - -**Beat C, roadmap board (40s).** Show the roadmap slide: - -| Milestone | Theme | Highlights | -|---|---|---| -| **v1.0** (shipped) | Developer API core | OpenAI-compatible `/v1`, BDT billing, 3 payment rails | -| **v1.1** (in progress) | Chat app plus provider catalog | Open WebUI chat, Bangla locale, provider CRUD, tools capability | -| **v1.2** | Agentic surface | Anthropic Messages API, MCP connectors, router-LLM intelligent routing | -| **v1.3** | Device era | Hardware detection plus model advisor, mobile and desktop apps with on-device router-agent, **DGX Spark / RTX Spark class** self-host | - -- Narrate the arc: "Today, the developer API and chat. Next, the agentic surface so coding agents and MCP tools run on us. Then the device era: your AI on a workstation-class box you own." - -**Beat D, the ask (20s).** -- **Slide: ask placeholder.** `[Ask: $___ for ___ months runway, allocated to hardware (DGX/RTX Spark class), BD go-to-market, and EnterpriseEdge pilots with banks, telco, and government]`. -- Narrate the ask in one sentence, then stop talking. - -**Fallback plan:** Pre-record a GIF of the console billing page showing the three rails. Roadmap and ask are slides, with no network dependency. - ---- - -## 6. Q&A prep: 10 hard questions, honest answers - -1. **"OpenRouter already gives an OpenAI-compatible API. Why do you exist?"** - OpenRouter takes USD cards. We take bKash and SSLCommerz, settle in BDT, and run a sovereign self-host SKU OpenRouter has no answer for. The wedge is payments and data residency in a 170M-person market, not raw model access. - -2. **"What stops OpenAI or a global player from localizing payments tomorrow?"** - Nothing technical, which is exactly why we move now. Our moat compounds in the enterprise self-host story (EnterpriseEdge), local payment integrations, and Bangla product depth, none of which a global player prioritizes for one market. We're racing the localization window deliberately. - -3. **"What's the moat once the API is commoditized?"** - Three layers: (a) BDT payment rails and compliance that take months to replicate per-market; (b) EnterpriseEdge, banks and government buying a box, a sales motion with switching costs; (c) the device era, an on-device router-agent and model advisor tied to specific hardware we'll support first. - -4. **"Unit economics. You resell models you don't own. Where's the margin?"** - Prepaid credits with a spread on inference, plus EnterpriseEdge licensing (per-box, recurring) where there's no per-token COGS to us at all. Cloud is volume-thin-margin; EnterpriseEdge is the high-margin enterprise line. We make `math/big` FX precise specifically so the spread never leaks to rounding. - -5. **"Why now?"** - Two clocks. Global players haven't localized BD payments yet (consumer and developer window). And DGX Spark just hit retail at $4,699 with RTX Spark class shipping this fall (enterprise self-host window). Both windows are open today and close within 12 to 18 months. - -6. **"How much of this is real versus roadmap?"** - The developer API, BDT billing across three rails, and the chat app are shipped and live. Provider catalog and tools capability just merged. Pending and clearly marked: Bangla UI staging deploy (Phase 25), tool passthrough wire (Phase 20-05), and the hardware advisor (v1.3). We don't hide the line between shipped and planned. - -7. **"You have no hardware. Isn't EnterpriseEdge vaporware?"** - The software stack runs today via one compose profile: gateway, chat, router, optional local model. What we don't own yet is the DGX/RTX Spark box, which is a $4,699 purchase post-funding, not an R&D risk. The advisor and installer polish are scoped in v1.3. We're buying hardware with the round, not inventing it. - -8. **"bKash and SSLCommerz integrations. Are they certified and compliant?"** - The rails are integrated and shipped in v1.0. For regulated specifics (settlement, KYC, data protection under local law) we follow the relevant payment-provider and regulatory requirements and consult counsel before scaling volume. We treat compliance as a gating dependency, not an afterthought. - -9. **"Provider lock-in. What if OpenRouter or Groq cuts you off?"** - The gateway is provider-agnostic by design; the catalog supports custom providers (merged PR #197). We route across OpenRouter and Groq today with fallbacks, and can add any OpenAI-compatible provider, including a customer's own EnterpriseEdge models, without code changes. - -10. **"What happens to your cloud margin when on-device models get good enough to replace the API?"** - That's our v1.3 thesis, not a threat. We're building the on-device router-agent and model advisor ourselves. As local models improve, the router keeps the cheap and private calls on-device and sends only what needs a frontier model to the cloud. We monetize the routing and the box, so the trend we'd supposedly fear is the product we're shipping. - ---- - -## Appendix: exact commands reference - -```bash -# Health check (pre-flight) -curl -s https://api-hive.scubed.co/health - -# Raw chat completion, free model (Section 3, Beat A) -curl https://api-hive.scubed.co/v1/chat/completions \ - -H "Authorization: Bearer $HIVE_KEY" \ - -H "Content-Type: application/json" \ - -d '{"model":"llama-3.3-70b","messages":[{"role":"user","content":"Say hello to our investors in one sentence."}]}' - -# EnterpriseEdge self-host (Section 4, Beat A) -cd deploy/docker -docker compose --env-file ../../.env --profile enterprise up --build - -# Chat workstation local fallback (Section 2) -docker compose --env-file ../../.env --profile enterprise --profile chat up --build -# Open WebUI via Caddy at http://localhost:8090 -``` - -> **Placeholders:** `` and `$HIVE_KEY` are demo credentials injected at runtime, never committed and never shown on screen. No real keys, account ids, or customer data appear anywhere in this script or in the live demo. diff --git a/.planning/infra/vps-deployment-options.md b/.planning/infra/vps-deployment-options.md deleted file mode 100644 index f6da0f0a5..000000000 --- a/.planning/infra/vps-deployment-options.md +++ /dev/null @@ -1,133 +0,0 @@ -# VPS Deployment Options — Staging & EnterpriseEdge - -**Status:** Decision document — research only, no infra changes -**Date:** 2026-06-12 -**Author:** CTO research session - ---- - -## Context - -Current staging box: OCI VM.Standard.E2.1.Micro (AMD, 1 GB RAM, Always Free). -Pain point: 1 GB is too small to run the `chat` or `enterprise` profile alongside the API-only `cloud` profile. Open WebUI + Caddy requires ~2 GB on its own. - -The web-console (Next.js) is deployed to **Cloudflare Workers** via OpenNext — it does NOT run on the VM and does not factor into sizing. -Redis is **external Upstash** on the cloud/staging profile — also not on the VM. - ---- - -## Part 1 — Stack Sizing - -### Service RAM budgets (observed limits from `docker-compose.staging.yml` + upstream defaults) - -| Service | Profile(s) | Confirmed limit / estimate | -|---|---|---| -| edge-api (Go) | cloud, enterprise | 180 MB (staging mem_limit) | -| control-plane (Go) | cloud, enterprise | 180 MB (staging mem_limit) | -| litellm (Python) | cloud, enterprise | 420 MB (staging mem_limit); prod peak ~500 MB | -| redis:alpine | enterprise, local | ~30 MB | -| open-webui | enterprise, chat | **2 GB** (mem_limit in compose) | -| caddy:alpine | enterprise, chat | ~30 MB | -| OS + Docker daemon | all | ~250–350 MB | - -### RAM floor by deployment tier - -| Tier | Profile | Services on VM | RAM floor | Recommended box | -|---|---|---|---|---| -| **(a) Staging API-only** | `cloud` | edge-api + control-plane + litellm | **~1.1 GB** | 2 GB (current 1 GB is at the limit) | -| **(b) Staging + chat UI** | `cloud` + `chat` | above + open-webui + caddy | **~3.2 GB** | **4 GB minimum** | -| **(c) Enterprise demo + Ollama** | `enterprise` | all above + redis + ollama | **8 GB + model weights** | **16 GB+** (Ollama: 7B model ~4–5 GB, 13B ~8 GB) | - -Notes: -- Tier (a): current OCI Micro is technically surviving but has no headroom; any litellm warm-up spike OOMs. -- Tier (b): 4 GB is the practical floor; 8 GB is comfortable. -- Tier (c): Ollama model RAM is additive and not in the Docker mem_limit. A single 7B model at Q4 quantisation needs ~4.5 GB VRAM/RAM. Total for a comfortable demo: 16 GB. - ---- - -## Part 2 — VPS Market Survey (June 2026, live-verified prices) - -### Architecture note - -**CI builds `linux/amd64` only** (`platforms: linux/amd64` in `deploy-staging.yml`). -ARM VPS (Hetzner CAX, Oracle A1) requires adding `linux/arm64` to the build matrix — a small but real migration cost. Upstream images (open-webui, litellm, caddy, redis) all publish multi-arch manifests; only the Hive Go images need the pipeline change. - -### Option table - -| Provider / SKU | RAM | vCPU | Disk | Egress | Price/mo (USD) | Arch | Notes | -|---|---|---|---|---|---|---|---| -| **Oracle A1 Flex** (4 OCPU / 24 GB) | 24 GB | 4 | 200 GB block | 10 TB free | **$0** | ARM64 | Always Free — confirmed active June 2026 per Oracle docs. Reclaimed if CPU/net/mem all <20% for 7 days; keep a cron ping. New account required if not already on OCI. | -| **Hetzner CX32** | 8 GB | 4 | 80 GB | 20 TB | ~EUR 8.49 (~**$9.20**) | x86 (AMD) | Source: hetzner.com/cloud regular-performance tier. No pipeline change. Reliable EU infra. | -| **Contabo Cloud VPS 10** | 8 GB | 4 | 75 GB NVMe | Unlimited | **$4.40/mo** (12-mo term) | x86 | Source: contabo.com/en/vps, June 2026. Cheapest x86 8 GB on the market. Reputation for overselling; support is slower. | -| **Contabo Cloud VPS 20** | 12 GB | 6 | 100 GB NVMe | Unlimited | **$6.00/mo** (12-mo term) | x86 | Best-selling plan per Contabo. EUR 7.50 list, ~$8.15 month-to-month. | -| **OVH VPS-2 (2027 range)** | 8 GB | 4 | 75 GB NVMe | Unlimited | **$11.64/mo** (12-mo) | x86 | Source: ovhcloud.com/en-ca/vps. Canadian pricing. Daily backup included. | -| **OVH VPS-3** | 12 GB | 6 | 100 GB NVMe | Unlimited | **$16.83/mo** (12-mo) | x86 | 1 Gbps bandwidth, daily backup. | -| **DigitalOcean Basic 4 GB** | 4 GB | 2 | 80 GB | 4 TB | **$24/mo** | x86 | Source: digitalocean.com/pricing/droplets. Notably more expensive; good DX but poor value here. | -| **Hetzner CAX21** | 8 GB | 4 | 80 GB | 20 TB | ~EUR 6.90 (~**$7.50**) | ARM64 | Cost-optimized ARM. Requires adding arm64 to CI. | - -### Eliminated options - -- **DigitalOcean**: $24/mo for 4 GB does not fit budget. Their 8 GB is $48/mo. -- **Scaleway PLAY2-MICRO** (2 vCPU / 4 GB): ~EUR 4.99 — too small for chat tier. -- **AWS / GCP**: ruled out by owner preference. -- **Netcup**: good value but less mainstream; skip for now. - ---- - -## Part 3 — Recommendation - -### Primary: Oracle OCI A1 Flex 4 OCPU / 24 GB — $0/month - -**Reasoning:** - -1. **Confirmed Always Free as of June 2026.** Oracle's official documentation explicitly lists "Arm-based Ampere A1 Compute — 3,000 OCPU hours and 18,000 GB hours per month" (equivalent to one 4-OCPU / 24 GB instance) as an Always Free resource with no expiry. Source: docs.oracle.com/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm -2. **24 GB handles all three tiers,** including enterprise demo with a 7B Ollama model (~4.5 GB), with headroom. -3. **The current staging box is already OCI** — the SSH deploy workflow (`deploy@$STAGING_HOST`) transfers trivially: update one GitHub secret (`STAGING_HOST`), provision the new instance, copy `/opt/hive/` state. -4. **ARM pipeline cost is one PR:** add `linux/arm64` alongside `linux/amd64` in `deploy-staging.yml`. Docker Buildx + GitHub Actions cache handles this; build time increases ~2 min. -5. **Self-managed SSH deploy matches the EnterpriseEdge product goal.** The CI workflow already demonstrates the pattern: GitHub Actions pushes images to GHCR, SSHes to the box, and runs `docker compose up -d`. Customers buying EnterpriseEdge will use the same pattern. Running it yourselves is the best way to find friction. - -**Risks of Oracle A1 Free:** -- "Out of host capacity" errors when provisioning in popular regions — use Ashburn or Phoenix at off-peak hours, or provision in AP regions. -- Idle reclamation (CPU + net + mem all <20% for 7 days): mitigate with a lightweight cron health-check ping. -- Oracle could change the Always Free terms; this has not happened since A1 launched in 2021 but is a non-zero risk. -- ARM requires the CI pipeline change described above. - -### Fallback: Hetzner CX32 — ~$9.20/month (x86, no pipeline change) - -**Reasoning:** If Oracle provisioning fails, ARM migration is deferred, or the team wants a paid option with cleaner SLA, Hetzner CX32 (4 vCPU / 8 GB AMD / 80 GB SSD / 20 TB egress) at ~EUR 8.49/month is the best-value x86 option. Zero pipeline changes required. Hetzner has a strong reliability track record and GDPR-compliant EU hosting. 8 GB comfortably handles tier (b) (API + chat UI). For tier (c) with Ollama you'd need to upgrade to CX42 (16 GB, ~EUR 17/month) — still well within $50/month budget. - -### Budget summary - -| Scenario | Primary (OCI A1) | Fallback (Hetzner CX32) | -|---|---|---| -| Staging API-only | $0 | ~$9.20 | -| Staging + chat UI | $0 | ~$9.20 | -| Enterprise demo + 7B Ollama | $0 | $0 (upgrade to CX42 ~$18.50) | -| Remaining budget headroom | $50 | $40.80 | - -### Migration effort - -From current OCI Micro to new OCI A1 (same account / new instance): - -1. Provision new A1 Flex instance (4 OCPU / 24 GB) in OCI Console — ~10 min. -2. Add SSH public key, open ports 80/443 (Caddy), update `STAGING_HOST` secret — ~5 min. -3. Add `linux/arm64` to `build-push-action` matrix in `deploy-staging.yml` — ~15 min, one PR. -4. Trigger deploy workflow; smoke test — ~10 min. -5. Decommission old Micro instance. - -**Total estimated effort: 1–2 hours including review.** - ---- - -## Sources (live, June 2026) - -- Oracle Always Free specs: https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm -- Oracle Free Tier overview: https://www.oracle.com/cloud/free/ -- Hetzner Cloud pricing: https://www.hetzner.com/cloud/cost-optimized and https://www.hetzner.com/cloud/regular-performance -- Contabo VPS pricing: https://contabo.com/en/vps/ -- OVH VPS 2027 range (CA): https://www.ovhcloud.com/en-ca/vps/ -- DigitalOcean Droplets: https://www.digitalocean.com/pricing/droplets - ---- - -🤖 Generated with [Claude Code](https://claude.com/claude-code) diff --git a/.planning/milestones/v1.0-INTEGRATION-CHECK.md b/.planning/milestones/v1.0-INTEGRATION-CHECK.md deleted file mode 100644 index fb8ae4e09..000000000 --- a/.planning/milestones/v1.0-INTEGRATION-CHECK.md +++ /dev/null @@ -1,115 +0,0 @@ -# v1.0 Integration Check - -**Date:** 2026-04-21 -**Scope:** Phases 01–10, REQ-IDs: COMP-01/02/03, API-08, ROUT-01/03, API-01/02/03/04, ROUT-02, API-05/06/07, KEY-04 - ---- - -## Wiring Summary - -**Connected:** 22 exports/routes properly wired -**Orphaned:** 0 exports created but unused -**Missing:** 1 expected connection (getViewer slug field) - -## API Coverage - -**Consumed:** All 9 edge-api public routes registered and reachable -**Internal routes consumed:** /internal/apikeys/resolve, /internal/routing/select, /internal/accounting/reservations/*, /internal/usage/*, /internal/files/*, /internal/uploads/* -**Orphaned:** 0 internal routes without callers - -## Auth Protection - -**Protected:** All /api/v1/* console routes require Supabase JWT via auth.Middleware.Require() -**Internal routes:** /internal/* routes are service-to-service, no auth middleware (by design) -**Unprotected sensitive areas:** 0 - -## E2E Flows - -**Complete:** 5 flows work end-to-end -**Broken:** 0 hard breaks; 2 known deferred issues affect specific sub-paths - ---- - -## Detailed Findings - -### Connected Exports (key cross-phase wiring confirmed) - -**P02 → P05 (auth → apikeys):** `auth.ViewerFromContext` called in `apikeys/http.go:resolveViewerContext`. WIRED. - -**P05 → edge-api hot path (apikeys → authz):** `authz.Client.Resolve` calls `POST /internal/apikeys/resolve` → `apikeys.Handler.handleInternalResolve` → `apikeys.Service.ResolveSnapshot`. Redis cache layer in between. WIRED end-to-end. - -**P05 → P03 (apikeys snapshot → accounting):** `authz.AuthSnapshot.AccountID` and `.KeyID` passed into `accounting.CreateReservation` and `accounting.StartAttempt` in `orchestrator.go:100–123`. WIRED. - -**P04 → edge-api routing (catalog/routing → inference):** `inference.RoutingClient.SelectRoute` calls `POST /internal/routing/select` → `routing.Handler.handleSelectRoute` → `routing.Service.SelectRoute`. WIRED. - -**P04 → catalog → edge-api /v1/models:** `catalog.Client.FetchSnapshot` calls `GET /internal/catalog/snapshot` (registered in router.go:80); `/v1/models` and `/catalog/models` both wired in edge-api main.go:145-147. WIRED. - -**P03 → edge-api accounting (ledger/reservations):** `orchestrator.go` calls `CreateReservation`, `FinalizeReservation`, `ReleaseReservation`, `StartAttempt`, `RecordUsageEvent` — all hit control-plane internal accounting endpoints registered in router.go:89-98. WIRED. - -**P06 → /v1/embeddings:** `inference.Handler` dispatches `/v1/embeddings` to `handleEmbeddings(o.orchestrator, w, r)` using the same authorize→route→reserve→dispatch→finalize lifecycle. WIRED. - -**P07 → /v1/files, /v1/uploads, /v1/batches, /v1/images/*, /v1/audio/*:** All registered in `registerMediaFileBatchRoutes` in edge-api main.go:210-223. Images, audio, files, and batches handlers each carry their own `authorizerAdapter`, `routingAdapter`, and `accountingAdapter` wired from the same shared clients. WIRED. - -**P07 filestore cross-service (edge-api → control-plane):** `files.NewFilestoreClient(controlPlaneURL)` in edge-api main.go:130 calls `/internal/files/*` and `/internal/uploads/*`. These routes are registered via `filestore.RegisterRoutes(routerMux, filestoreSvc, batchSubmitter)` in control-plane main.go:324. WIRED. - -**P07 batchstore (control-plane):** `batchstore.NewSubmitter` and `batchstore.NewBatchWorker` constructed in control-plane main.go:282-319; Asynq worker goroutine launched; `batchstore.TypeBatchPoll` handler registered on asynqMux. WIRED (Redis-conditional). - -**P08 → payments (checkout → ledger → accounts):** `payments.NewService(paymentsRepo, ledgerSvc, profilesSvc, fxSvc, rails)` wired in control-plane main.go:214; `paymentsHandler` registered on router for authenticated `/api/v1/accounts/current/checkout/*` and unauthenticated webhook routes. WIRED. - -**P09 web-console → control-plane:** All console API calls go through `lib/control-plane/client.ts` which uses `CONTROL_PLANE_BASE_URL` + Supabase Bearer token. Endpoints called: /api/v1/viewer, /api/v1/accounts/current/profile, /api/v1/accounts/current/billing-profile, /api/v1/accounts/current/credits/balance, /api/v1/accounts/current/credits/ledger, /api/v1/accounts/current/invoices, /api/v1/accounts/current/checkout/rails, /api/v1/accounts/current/checkout/initiate, /api/v1/accounts/current/api-keys, /api/v1/accounts/current/budget, /api/v1/accounts/current/analytics/*. All routes confirmed registered in router.go. WIRED. - -**P10 → ROUT-02 (routing fixes + batch submitter):** Batch submitter wired with accountingSvc and routingSvc in main.go:281-291 (conditional on both being non-nil). Storage client passed to both filestore and batch subsystems. WIRED. - -### Missing Connections (partial/incomplete wiring) - -**getViewer → slug field empty:** `client.ts:getViewer()` maps `current_account.slug` to `""` (hardcoded, line 420) and membership `account_slug` also to `""` (line 427). The control-plane `/api/v1/viewer` response (`accounts/http.go`) does not return a `slug` field. If any console UI component displays or depends on `slug`, it will always be blank. Non-blocking for v1.0 (no console UI currently consumes slug), but a latent data gap. - -**Batch success-path (deferred):** `batchesHandler` in edge-api creates batch records via `batchClient` (POST /internal/batches). Settlement of `status=completed` requires LiteLLM-managed file upload which OpenRouter/Groq do not support. Submitter and failure-path terminal settlement confirmed wired. Success-path unexercisable with current provider set. Tracked as known deferred issue. - -**`ensureCapabilityColumns` wrong table (deferred):** `routing/repository.go` targets `route_capabilities` instead of `provider_capabilities`. Routing works in production because seed path populates required columns separately. Latent correctness bug, not a wiring break. - -**`amount_usd` in checkout response (deferred, regulatory):** `payments/http.go:110` — `initiateResponse` struct includes `AmountUSD int64 json:"amount_usd"`. This field is serialized into the checkout initiate response. For BD customers using bKash/SSLCommerz rails this exposes USD rate information, violating the regulatory rule. The web-console `client.ts:CheckoutInitiateResponse` and `Invoice` types also carry `amount_usd`. Tracked as known deferred issue. - -### Broken Flows - -None. All 5 primary E2E flows complete without hard breaks: - -1. **Chat/completion flow:** API key → authz (Redis+fallback) → routing select → reserve → LiteLLM dispatch → normalize → finalize → usage event. COMPLETE. -2. **Embeddings flow:** Same lifecycle via `/v1/embeddings`. COMPLETE. -3. **Image generation flow:** Images handler with authorizerAdapter/routingAdapter/accountingAdapter + S3 storage. COMPLETE. -4. **File upload flow:** edge-api FilestoreClient → control-plane filestore.RegisterRoutes → S3. COMPLETE. -5. **Batch create flow:** Batch handler → batchClient → control-plane batch routes → Asynq queue. COMPLETE (create/failure-path; success-path deferred per known issue). - -### Unprotected Routes - -None found in sensitive areas. All `/api/v1/*` routes behind `auth.Middleware.Require()`. `/internal/*` routes intentionally unauthenticated (service-to-service on private network). - ---- - -## Requirements Integration Map - -| Requirement | Integration Path | Status | Issue | -|-------------|-----------------|--------|-------| -| COMP-01 (OpenAI contract compatibility) | P01 support-matrix → edge-api middleware.UnsupportedEndpointMiddleware → all /v1/* routes | WIRED | — | -| COMP-02 (CompatHeaders middleware) | P01 middleware.CompatHeaders applied as outermost wrapper in edge-api main.go:150 | WIRED | — | -| COMP-03 (Swagger/OpenAPI spec served) | P01 docs.SwaggerHandler(specPath) registered at /docs/ in edge-api main.go:78 | WIRED | — | -| API-08 (provider-blind error sanitization) | P01/P10 apierrors.WriteProviderBlindUpstreamError used in orchestrator.go:158,174 for all upstream errors | WIRED | — | -| ROUT-01 (model catalog + routing) | P04 catalog.Handler → /internal/catalog/snapshot; routing.Handler → /internal/routing/select; both consumed by edge-api | WIRED | — | -| ROUT-03 (capability-based route selection) | P04 routing.Service.SelectRoute with NeedFlags struct; capability mismatch returns 400 in orchestrator.go:87-92 | WIRED | — | -| API-01 (chat completions) | P06 /v1/chat/completions → handleChatCompletions → orchestrator.executeSync | WIRED | — | -| API-02 (completions) | P06 /v1/completions → handleCompletions → orchestrator.executeSync | WIRED | — | -| API-03 (responses) | P06 /v1/responses → handleResponses → orchestrator.executeSync | WIRED | — | -| API-04 (embeddings) | P06 /v1/embeddings → handleEmbeddings → orchestrator.executeSync | WIRED | — | -| ROUT-02 (storage-wired routing fixes) | P10 batchstore.NewSubmitter wired with routingSvc+accountingSvc; filestore.RegisterRoutes called | WIRED | Batch success-path deferred (upstream provider gap) | -| API-05 (image generation) | P10 /v1/images/* → images.Handler with full accounting adapter chain + S3 | WIRED | — | -| API-06 (audio) | P10 /v1/audio/* → audio.Handler with full accounting adapter chain | WIRED | — | -| API-07 (files/batches) | P10 /v1/files, /v1/uploads, /v1/batches → files.Handler + batches.Handler; filestore cross-service wired | WIRED | — | -| KEY-04 (API key management console UI) | P05+P09 web-console client.ts getApiKeys/createApiKey/revokeApiKey/rotateApiKey → /api/v1/accounts/current/api-keys → apikeys.Handler | WIRED | — | - -**Requirements with no cross-phase wiring (self-contained):** None. All 15 REQ-IDs in v1.0 scope have confirmed cross-phase wiring touchpoints. - ---- - -## Summary for Auditor - -All 15 v1.0 requirements are WIRED. The three deferred items (batch success-path, ensureCapabilityColumns, amount_usd BD checkout) are pre-documented in `.planning/v1.1-DEFERRED-SCOPE.md` and do not break any primary developer API path. The `getViewer` slug gap is latent and non-blocking. No orphaned routes or exports found. Auth protection is correctly applied across all sensitive surfaces. diff --git a/.planning/milestones/v1.0-MILESTONE-AUDIT.md b/.planning/milestones/v1.0-MILESTONE-AUDIT.md deleted file mode 100644 index fc0db3ef1..000000000 --- a/.planning/milestones/v1.0-MILESTONE-AUDIT.md +++ /dev/null @@ -1,179 +0,0 @@ -# Milestone v1.0 Audit Report - -**Milestone:** Hive API Platform v1.0 — developer-api-core -**Audited:** 2026-04-21T22:40:00Z -**Status:** tech_debt -**Auditor:** `/gsd:audit-milestone` (re-audit supersedes 2026-04-15 audit) - -```yaml ---- -milestone: v1.0 -audited: 2026-04-21T22:40:00Z -status: tech_debt -scores: - requirements: "13/15 satisfied, 2/15 partial (deferred)" - phases: "9/10 verification passed or equivalent UAT, 1/10 gaps_found (deferred)" - integration: "core flows E2E-passed via Phase 10 UAT" - flows: "10/12 UAT pass, 1 partial, 2 skipped" -gaps: [] -tech_debt: - - phase: 10-routing-storage-critical-fixes - items: - - "API-07 partial: batch success-path terminal settlement not exercisable with current provider mix (OpenRouter/Groq have no native batch API; LiteLLM file-upload supports only openai/azure/vertex_ai/manus/anthropic). Failure-path settlement verified live. Deferred to v1.1 with local batch-executor design." - - "KEY-04 partial: batch final settlement does not yet record per-key/per-model usage attribution on success-path (unexercisable). Edge-level reservation attribution works. Deferred to v1.1 alongside batch executor." - - "ensureCapabilityColumns targets route_capabilities instead of provider_capabilities (latent; seed path currently populates required columns). Deferred to v1.1." - - "amount_usd exposed on BD checkout surface (regulatory risk on BD-visible field). Deferred to v1.1." - - phase: 02-identity-account-foundation - items: - - "No formal VERIFICATION.md artifact — 02-UAT.md (12 tests all passed, Gaps: none) stands as verification evidence." - - phase: 03-credits-ledger-usage-accounting - items: - - "No VERIFICATION.md or UAT.md — only 03-VALIDATION.md draft. Ledger correctness exercised transitively by Phase 05/06/07/10 UAT (reservations + finalization live-verified)." - - phase: 05-api-keys-hot-path-enforcement - items: - - "VERIFICATION.md status: gaps_found (3/4) — KEY-05 hot-path rate limiter, Lua scripts, budget snapshot totals absent. Scope owned by Phase 12 (deferred v1.1). KEY-01/KEY-02/KEY-03 traceability moved to Phase 12/13 (deferred v1.1)." - - phase: all-v1.0 - items: - - "All 10 VALIDATION.md files carry nyquist_compliant: false, wave_0_complete: false (draft). Formal Nyquist audit deferred per user workflow preference — test coverage live-validated via UAT instead." -nyquist: - compliant_phases: 0 - partial_phases: 0 - missing_phases: 10 - overall: missing - note: "Project does not use formal Nyquist validation — live UAT is treated as verification equivalent." ---- -``` - -## Executive Summary - -v1.0 scope = phases 1–10 (developer-api-core). All 10 phases report `completed_plans: 49/49`. Core developer surface (chat, completions, responses, embeddings, images, audio, files, batches failure-path, catalog, provider-blind errors) is E2E-verified via Phase 10 UAT (2026-04-21). Four tech-debt items deferred to v1.1 with explicit documentation in `.planning/v1.1-DEFERRED-SCOPE.md`. No v1.0-blocking requirement is unsatisfied. Status is **tech_debt** — ship-ready with known deferred work. - -## Scope - -Milestone v1.0 covers 14 roadmap phases; only phases 1–10 are v1.0 scope. Phases 11–14 are v1.1 (`REQUIREMENTS.md` traceability shows all non-v1.0 REQ-IDs as `Pending / Phase 11/12/13/14`). v1.1 directory shells exist with `.gitkeep` only. - -## Phase Verification Summary - -| Phase | Artifact | Status | Score | Notes | -|-------|----------|--------|-------|-------| -| 01 Contract & Compatibility Harness | VERIFICATION.md | passed | 7/7 | COMP-01/02/03, API-08 verified; SDK harness live (JS/Python/Java). | -| 02 Identity & Account Foundation | 02-UAT.md | passed | 12/12 UAT | No formal VERIFICATION.md; all v1.0-scope REQ-IDs for this surface are in Phase 11 (deferred v1.1). | -| 03 Credits Ledger & Usage Accounting | 03-VALIDATION.md (draft) | n/a | n/a | No formal verification. Ledger correctness validated transitively by Phase 05/06/07/10 UAT reservation+finalization flows. No v1.0 REQ-IDs directly assigned. | -| 04 Model Catalog & Provider Routing | VERIFICATION.md | passed | 4/4 | ROUT-01, ROUT-03 verified; fresh SDK/model-catalog tests pass. | -| 05 API Keys & Hot-Path Enforcement | VERIFICATION.md + 05-UAT.md | gaps_found (static) / complete (UAT) | 3/4 | KEY-05 hot-path rate limiter missing — scope owned by deferred Phase 12. Key lifecycle flows UAT-verified. | -| 06 Core Text & Embeddings API | VERIFICATION.md | passed | 16/16 | API-01/02/03/04 verified; SDK tests live. | -| 07 Media, File, Async API Surface | VERIFICATION.md | passed | 24/24 | Re-verified 2026-04-10 after earlier gap closure. | -| 08 Payments, FX, Compliance Checkout | VERIFICATION.md | passed | 8/8 | Backend primitives verified; BD checkout `amount_usd` leakage flagged as tech-debt. | -| 09 Developer Console & Operational Hardening | VERIFICATION.md | passed (static) | 15/15 static | Live console/monitoring verification deferred per v1.1 scope decisions. | -| 10 Routing & Storage Critical Fixes | VERIFICATION.md + 10-UAT.md | gaps_found (static) / complete (UAT) | 4/6 static / 10 pass + 1 partial + 2 skip UAT | See tech-debt block above. 10-UAT runs after VERIFICATION.md and settles API-05/06 BLOCKED → PASS after PolicyMode strict fix. | - -## Requirements Coverage (v1.0 scope) - -15 REQ-IDs assigned to phases 1–10. 3-source cross-reference: REQUIREMENTS.md traceability + phase VERIFICATION.md + SUMMARY frontmatter. - -| REQ | Phase | Traceability | Verification | Summary Frontmatter | Final | -|-----|-------|--------------|--------------|---------------------|-------| -| COMP-01 | 01 | Complete | passed | 01-03 | satisfied | -| COMP-02 | 01 | Complete | passed | 01-02, 01-03 | satisfied | -| COMP-03 | 01 | Complete | passed | 01-02, 01-04 | satisfied | -| API-08 | 01 | Complete | passed | 01-01, 01-02 | satisfied | -| ROUT-01 | 04 | Complete | passed | 04-* frontmatter empty | satisfied (frontmatter doc gap) | -| ROUT-03 | 04 | Complete | passed | 04-* frontmatter empty | satisfied (frontmatter doc gap) | -| API-01 | 06 | Complete | passed | 06-01, 06-02 | satisfied | -| API-02 | 06 | Complete | passed | frontmatter missing | satisfied (frontmatter doc gap) | -| API-03 | 06 | Complete | passed | frontmatter missing | satisfied (frontmatter doc gap) | -| API-04 | 06 | Complete | passed | frontmatter missing | satisfied (frontmatter doc gap) | -| ROUT-02 | 10 | Complete | SATISFIED (VERIFICATION) | 10-01..10-09 | satisfied | -| API-05 | 10 | Complete | BLOCKED (static) → PASS (UAT Test 7) | 10-01..10-11 | satisfied (post-static evidence via 10-UAT) | -| API-06 | 10 | Complete | BLOCKED (static) → PASS (UAT Test 8) | 10-01..10-11 | satisfied (post-static evidence via 10-UAT) | -| API-07 | 10 | Complete | PARTIAL | 10-01..10-11 | **partial — batch success-path deferred v1.1 (upstream provider blocker)** | -| KEY-04 | 10 | Complete | FAILED (success-path attribution) | 10-10, 10-11 | **partial — unexercisable until batch success-path exists; deferred v1.1** | - -**Summary:** 13 satisfied, 2 partial. No orphaned requirements — every v1.0 REQ-ID appears in at least one SUMMARY frontmatter and the assigned phase VERIFICATION.md. Both partials are deferred to v1.1 with written design in `.planning/v1.1-DEFERRED-SCOPE.md` and `KNOWN-ISSUE-batch-upstream.md`. - -## Deferred Requirements (v1.1 scope — out of audit) - -20 REQ-IDs assigned to phases 11–14 in REQUIREMENTS.md: AUTH-01..04, BILL-01..07, KEY-01/02/03/05, CONS-01..03, PRIV-01, OPS-01. All marked `Pending`. Not in v1.0 scope. - -## Cross-Phase Integration & E2E Flows - -Integration evidence drawn from `.planning/phases/10-routing-storage-critical-fixes/10-UAT.md` (live smoke under rebuilt Docker project `hive`, 2026-04-21): - -| # | Flow | Result | Covers | -|---|------|--------|--------| -| 1 | Cold-start smoke (redis, litellm, control-plane, edge-api → healthy) | pass | infra | -| 2 | Model catalog endpoint (`/v1/models` + `/api/v1/catalog/models`) | pass | COMP-01/02, ROUT-01 | -| 3 | Internal route resolution for media + batch | pass | ROUT-02 | -| 4 | Chat completions non-streaming, provider-blind | pass | API-01, ROUT-02 | -| 5 | Edge-API fail-fast on missing S3 env | pass | API-07 gating | -| 6 | File upload round-trip through Supabase Storage | pass | API-07 | -| 7 | Image generation reservation (strict policy) | pass | API-05, KEY-04 edge-level | -| 8 | Audio speech + transcription reservations (strict policy) | pass | API-06, KEY-04 edge-level | -| 9 | Batch create with attribution (failure-path settlement) | pass | API-07 failure-path | -| 10 | Terminal batch settlement (success-path) | **partial** | API-07 success-path, KEY-04 — deferred v1.1 | -| 11 | Provider-blind error sanitization | pass | COMP-02, all API-* | -| 12 | No FX / USD on BD-visible surfaces | **skipped** | regulatory — `amount_usd` tech-debt deferred v1.1 | - -Auxiliary UAT: -- 02-UAT.md: 12 tests passed, Gaps: none (auth, onboarding, invitations, workspace switching). -- 05-UAT.md: key lifecycle, issuance, authorization, rotation, revocation — complete. - -Hot-path edge→control-plane wiring: auth middleware → routing.SelectRoute → accounting.CreateReservation → upstream dispatch → accounting.FinalizeReservation → usage.RecordEvent. Verified end-to-end via Tests 4/7/8 (PolicyMode=strict accepted after 2026-04-21 fix). - -Cross-phase integration checker report: `.planning/v1.0-INTEGRATION-CHECK.md` (completed 2026-04-21). Result: **22/22 exports wired, 0 orphaned, 0 broken E2E flows**. Confirmed wiring: - -- P02 → P05: `auth.ViewerFromContext` consumed in `apikeys/http.go`. -- P05 → edge hot path: `authz.Client.Resolve` → Redis → `/internal/apikeys/resolve` → `apikeys.Service.ResolveSnapshot`. -- P05 snapshot → P03: `AccountID`/`KeyID` from `AuthSnapshot` fed into every `accounting.CreateReservation` in `orchestrator.go`. -- P04 → inference: `RoutingClient.SelectRoute` → `/internal/routing/select` → `routing.Service`. -- P10 filestore: `files.NewFilestoreClient` (edge) → `/internal/files/*` via `filestore.RegisterRoutes` (`main.go:324`). -- P10 batchstore: `batchstore.NewSubmitter` + `NewBatchWorker` wired with `routingSvc`+`accountingSvc`+`storageClient`; Asynq worker launched. -- P09 web-console: all 13 console API functions in `lib/control-plane/client.ts` resolve to registered routes in `router.go`. - -All 15 v1.0 REQ-IDs wired end-to-end. Integration checker surfaced no new gaps beyond the four tech-debt items already documented above. One latent finding (`viewer.slug` always empty) flagged as non-blocking. - -## Tech Debt (accepted, deferred to v1.1) - -Per `.planning/v1.1-DEFERRED-SCOPE.md`: - -### v1.0 Deferred Items - -1. **Batch success-path terminal settlement** — LiteLLM's managed file upload (`POST /v1/files purpose=batch`) supports only `openai|azure|vertex_ai|manus|anthropic`. OpenRouter + Groq have no native batch API. Failure-path settlement works (reservation release + attribution). Unblocking options: add supported provider key OR implement local batch executor in control-plane. Full design in `.planning/phases/10-routing-storage-critical-fixes/KNOWN-ISSUE-batch-upstream.md`. Recommended: Option B (local executor) — matches BD market reality. - -2. **KEY-04 success-path attribution** — Blocked downstream of (1). Edge-level reservation attribution already works. Will settle alongside batch executor. - -3. **`ensureCapabilityColumns` wrong-table** — `apps/control-plane/internal/routing/repository.go` targets `route_capabilities` instead of `provider_capabilities`. Latent (seed path currently populates columns). No runtime impact today. - -4. **`amount_usd` on BD checkout** — `apps/control-plane/internal/payments/http.go:105–115`. Violates BD regulatory rule (no FX display). v1.0 BD checkout treated as preview, not public launch. v1.1 fix must strip from API response AND any frontend surface. - -### v1.1 Phases (carried from v1.0 roadmap) - -- **Phase 11** — Compliance, Verification & Artifact Cleanup (AUTH-01/02 + API-01..04 frontmatter + orphaned VERIFICATION fills for P02, P03). -- **Phase 12** — KEY-05 Hot-Path Rate Limiting (per-key RPM/TPM Lua limiter). -- **Phase 13** — Console Integration Fixes (CONS-01..03, KEY-01/03, BILL-03/07). -- **Phase 14** — Payments, Invoicing & Budget Integration (BILL-05/06, end-to-end budget/invoice/spend-alert). - -## Nyquist Compliance - -| Phase | VALIDATION.md | Compliant | Action | -|-------|---------------|-----------|--------| -| 01–10 (all) | exists (draft) | false | None — project does not use formal Nyquist. Live UAT is treated as verification equivalent. Re-evaluate if formal coverage gate is adopted for v1.1. | - -All 10 VALIDATION.md files identical state: `nyquist_compliant: false, wave_0_complete: false, status: draft`. User-directed workflow relies on UAT, not formal Nyquist audit. - -## Runtime UAT Cross-Reference - -- `.planning/UAT-REPORT.md` — initial runtime UAT (2026-04-15). -- `.planning/phases/10-routing-storage-critical-fixes/10-UAT.md` — Phase 10 closure (2026-04-21) supersedes initial UAT for phases 10, 7, 6 runtime paths. -- `.planning/phases/02-identity-account-foundation/02-UAT.md` — auth + onboarding. -- `.planning/phases/05-api-keys-hot-path-enforcement/05-UAT.md` — API-key lifecycle. - -## Comparison vs. Prior Audit (2026-04-15) - -Prior audit flagged `gaps_found` with 6/35 integration-ready, 5/12 phases passed, and blockers in Phase 10 routing/storage. Those blockers closed 2026-04-15 → 2026-04-21 via Phase 10 plans 10-01 through 10-11. Remaining Phase 10 static `gaps_found` status reflects the VERIFICATION.md dated 2026-04-20T19:56:23Z, which predates the 2026-04-21 PolicyMode strict fix, batch submitter wiring, and cold-start healthcheck fixes. 10-UAT.md (2026-04-21T22:00:00Z) captures the post-fix state. - -## Required Next Step - -v1.0 accepts the four documented tech-debt items. Ready to complete milestone and archive: - -`/gsd:complete-milestone v1.0` diff --git a/.planning/milestones/v1.0-REQUIREMENTS.md b/.planning/milestones/v1.0-REQUIREMENTS.md deleted file mode 100644 index 5b08bdbb0..000000000 --- a/.planning/milestones/v1.0-REQUIREMENTS.md +++ /dev/null @@ -1,141 +0,0 @@ -# Requirements: Hive API Platform — v1.0 Archive - -**Archived:** 2026-04-21 on milestone v1.0 completion. -**Milestone:** v1.0 developer-api-core. -**Defined:** 2026-03-28. -**Core Value:** Developers can switch from OpenAI to Hive with only a base URL and API key change, while keeping predictable prepaid billing and provider-agnostic operations. - -This file preserves the requirement state at the moment v1.0 shipped. Live requirements for v1.1 start fresh via `/gsd:new-milestone`. - ---- - -## v1.0 Requirements — Final Status - -### Compatibility & Contract - -- [x] **COMP-01**: Developer can use the official OpenAI JavaScript/TypeScript, Python, and Java SDKs against Hive by changing only base URL and API key for supported endpoints. — **satisfied** (Phase 1). -- [x] **COMP-02**: Hive returns OpenAI-style HTTP status codes, error objects, and compatibility headers for both supported requests and explicit unsupported-feature responses. — **satisfied** (Phase 1). -- [x] **COMP-03**: Developer can browse Swagger/OpenAPI documentation that matches the Hive public API contract and supported launch surface. — **satisfied** (Phase 1). - -### Inference Surface - -- [x] **API-01**: Developer can call `responses`, `chat/completions`, and `completions` with OpenAI-compatible request and response shapes. — **satisfied** (Phase 6). -- [x] **API-02**: Developer can stream supported text-generation endpoints with OpenAI-compatible SSE event ordering, chunk formats, and terminal events. — **satisfied** (Phase 6). -- [x] **API-03**: Developer can call `embeddings` with OpenAI-compatible request and response behavior. — **satisfied** (Phase 6). -- [x] **API-04**: Developer can use reasoning or thinking-related request parameters, and Hive returns translated reasoning outputs and usage details when upstream support exists. — **satisfied** (Phase 6). -- [x] **API-05**: Developer can call image-generation and image-processing endpoints with OpenAI-compatible behavior for supported operations. — **satisfied** (Phase 10 UAT Test 7; VERIFICATION static→PASS). -- [x] **API-06**: Developer can call speech, transcription, and translation endpoints with OpenAI-compatible behavior for supported operations. — **satisfied** (Phase 10 UAT Test 8; VERIFICATION static→PASS). -- [~] **API-07**: Developer can use `files`, `uploads`, and `batches` flows required by official SDK integrations. — **partial** — files + uploads + batch failure-path terminal settlement verified; batch success-path blocked by upstream provider capability. Deferred to v1.1 with design in `KNOWN-ISSUE-batch-upstream.md`. -- [x] **API-08**: Public non-org/admin endpoints outside the initial launch subset are explicitly classified and return OpenAI-style unsupported responses until implemented. — **satisfied** (Phase 1). - -### Model Catalog & Routing - -- [x] **ROUT-01**: Developer can list Hive-owned public model aliases, capabilities, and prices without seeing upstream provider identities. — **satisfied** (Phase 4). -- [x] **ROUT-02**: Requests route only to internally approved providers and models that satisfy the alias capability matrix, fallback policy, and account or key allowlists. — **satisfied** (Phase 10 VERIFICATION). -- [x] **ROUT-03**: When an upstream provider supports cache-aware billing semantics, Hive tracks and itemizes the related token categories without exposing the provider name. — **satisfied** (Phase 4). - -### API Keys & Attribution (v1.0 subset) - -- [~] **KEY-04**: Hive tracks usage and spend per API key and per model. — **partial** — edge-level reservation attribution works; batch success-path per-key attribution unexercisable until batch success-path exists. Deferred to v1.1. - ---- - -## Deferred to v1.1 - -These requirements were scoped to v1.0 in the original REQUIREMENTS.md but reassigned to v1.1 during execution. Status remains **Pending**. - -### Authentication & Accounts -- **AUTH-01** → Phase 11 — signup/signin via Supabase. -- **AUTH-02** → Phase 11 — email verification + password reset. -- **AUTH-03** → Phase 11 — session persists across refresh. -- **AUTH-04** → Phase 11 — billing contact, legal entity, country, VAT profile. - -### Billing & Payments -- **BILL-01** → Phase 11 — immutable credit ledger (exercised transitively by Phases 5/6/7/10 UAT; formal verification deferred). -- **BILL-02** → Phase 11 — reservation/finalize/refund correctness (exercised transitively; formal verification deferred). -- **BILL-03** → Phase 13 — 1,000-credit-increment top-ups via Stripe/bKash/SSLCommerz (rails live; console checkout modal wiring deferred). -- **BILL-04** → Phase 11 — 100k credits per USD + BDT FX snapshot + 3% fee (math shipped Phase 8; 5% figure in REQUIREMENTS.md corrected to 3% in Phase 8 implementation). -- **BILL-05** → Phase 14 — invoices, receipts, itemized spend (console reads shipped Phase 9; invoice-row creation deferred). -- **BILL-06** → Phase 14 — budget thresholds + notifications (UI shipped Phase 9; threshold enforcement deferred). -- **BILL-07** → Phase 13 — country/business/tax/surcharge checkout data (math shipped Phase 8; console integration deferred). - -### API Keys & Rate Limits -- **KEY-01** → Phase 13 — multi-key per account, one-time-secret display (lifecycle shipped Phase 5; console integration deferred). -- **KEY-02** → Phase 12 — nickname/expiration/allowlist/budget (schema shipped Phase 5; hot-path enforcement deferred). -- **KEY-03** → Phase 13 — revoke/rotate per key (lifecycle shipped Phase 5; console rotate page deferred). -- **KEY-05** → Phase 12 — account-tier + per-key rate limits on hot path. - -### Developer Console -- **CONS-01** → Phase 13 — balance/top-ups/ledger/invoices/tax via console. -- **CONS-02** → Phase 13 — API key + allowlist + catalog visibility via console. -- **CONS-03** → Phase 11 — analytics + error history + spend trends (chart UIs shipped Phase 9; live-data verification deferred). - -### Privacy & Operations -- **PRIV-01** → Phase 11 — no prompt/response storage at rest (policy enforced in code; formal VERIFICATION.md deferred). -- **OPS-01** → Phase 11 — operator health/latency/upstream/billing/rate-limit monitoring (Prometheus/Grafana/Alertmanager shipped Phase 9; live-stack verification deferred). - ---- - -## Traceability — Final State - -| Requirement | Phase | v1.0 Status | Notes | -|-------------|-------|-------------|-------| -| COMP-01 | 1 | Satisfied | | -| COMP-02 | 1 | Satisfied | | -| COMP-03 | 1 | Satisfied | | -| API-01 | 6 | Satisfied | | -| API-02 | 6 | Satisfied | | -| API-03 | 6 | Satisfied | | -| API-04 | 6 | Satisfied | | -| API-05 | 10 | Satisfied | Static VERIFICATION → UAT Test 7 PASS. | -| API-06 | 10 | Satisfied | Static VERIFICATION → UAT Test 8 PASS. | -| API-07 | 10 | **Partial** | Batch success-path upstream-blocked; deferred v1.1. | -| API-08 | 1 | Satisfied | | -| ROUT-01 | 4 | Satisfied | | -| ROUT-02 | 10 | Satisfied | | -| ROUT-03 | 4 | Satisfied | | -| KEY-04 | 10 | **Partial** | Success-path attribution unexercisable; deferred v1.1. | -| AUTH-01..04 | 11 | Pending | Deferred v1.1. | -| BILL-01..02 | 11 | Pending | Deferred v1.1 (transitively exercised). | -| BILL-03 | 13 | Pending | Deferred v1.1. | -| BILL-04 | 11 | Pending | Deferred v1.1. | -| BILL-05..06 | 14 | Pending | Deferred v1.1. | -| BILL-07 | 13 | Pending | Deferred v1.1. | -| KEY-01 | 13 | Pending | Deferred v1.1. | -| KEY-02 | 12 | Pending | Deferred v1.1. | -| KEY-03 | 13 | Pending | Deferred v1.1. | -| KEY-05 | 12 | Pending | Deferred v1.1. | -| CONS-01..02 | 13 | Pending | Deferred v1.1. | -| CONS-03 | 11 | Pending | Deferred v1.1. | -| PRIV-01 | 11 | Pending | Deferred v1.1. | -| OPS-01 | 11 | Pending | Deferred v1.1. | - -**v1.0 coverage:** 13 satisfied + 2 partial out of 15 v1.0-scoped requirements (87% satisfied, 13% partial-deferred). -**Total mapped:** 35/35 v1 requirements mapped to phases across v1.0 + v1.1. - ---- - -## Out of Scope - -| Feature | Reason | -|---------|--------| -| End-user chat web application | Launch is strictly a developer API and control-plane product. | -| RAG projects or workspaces | Requires separate retrieval, workspace, and content-governance semantics. | -| Hosted code runner or dev environment | Separate isolation and cost model from the API launch. | -| Credit subscriptions at launch | Commercial model is prepaid only for v1. | -| Customer-supplied upstream provider keys | Hive manages provider credentials internally and hides provider identity. | -| OpenAI org/admin management endpoints | Not part of the drop-in developer value proposition for the launch product. | -| Storing prompt or completion bodies by default | Conflicts with the launch privacy requirement. | - -## v2 Requirements (Out of v1.0 + v1.1) - -- **SDK-01**: First-party branded SDK wrappers for JS/TS, Python, Java. -- **SUBS-01**: Subscription-like credit bundles resolving to Hive Credits. -- **ENT-01**: Org hierarchies, procurement controls, approval workflows. -- **ANAL-01**: Warehouse-backed deep analytics. - ---- - -*Archived: 2026-04-21 on v1.0 milestone completion.* -*Prior update: 2026-04-15 after gap-closure phases 13–14 created and stale Complete statuses reset.* -*Original definition: 2026-03-28.* diff --git a/.planning/milestones/v1.0-ROADMAP.md b/.planning/milestones/v1.0-ROADMAP.md deleted file mode 100644 index ebbf1afc5..000000000 --- a/.planning/milestones/v1.0-ROADMAP.md +++ /dev/null @@ -1,204 +0,0 @@ -# Milestone v1.0: developer-api-core - -**Status:** ✅ SHIPPED 2026-04-21 -**Phases:** 1–10 -**Total Plans:** 49/49 -**Timeline:** 2026-02-23 → 2026-04-21 (58 days, 580 commits, 126 feat commits) - -## Overview - -Hive v1.0 developer-api-core is a **full Go rewrite of the prior Hive v1.0 implementation** -(control-plane + edge-api in Go 1.24), undertaken for efficiency and operational control: -lean hot-path latency, precise `math/big` FX, and full source-level control over routing, -sanitization, and billing that the prior stack could not guarantee. Delivers three -guarantees simultaneously on the new Go foundation: OpenAI contract fidelity, prepaid -billing correctness, and provider abstraction. v1.0 scope covers the full developer-API -core — chat/completions/responses/embeddings/images/audio/files/batches — plus prepaid -credits, multi-rail BDT/USD checkout, and a developer console. Ships ready for chat-app -and CLI-coding-agent integrators; phases 11–14 plus four documented tech-debt items are -deferred to v1.1. - -## Phases - -### Phase 1: Contract & Compatibility Harness - -**Goal**: Make Hive's public API a verified compatibility product instead of an approximation, on top of a Docker-only developer workflow. -**Depends on**: Nothing (first phase) -**Requirements**: [COMP-01, COMP-02, COMP-03, API-08] -**Completed**: 2026-03-29 -**Plans**: 4/4 - -- [x] 01-01: Docker-only developer stack with Go edge-api, toolchain, and SDK test containers -- [x] 01-02: Import OpenAI contract, build support matrix, error envelope, unsupported middleware, compat headers, and Swagger docs -- [x] 01-03: SDK compatibility harness: JS, Python, and Java tests with golden fixtures -- [x] 01-04: Close the `COMP-03` docs gap by generating a Hive-specific OpenAPI contract from the support matrix and serving it at `/docs` - -### Phase 2: Identity & Account Foundation - -**Goal**: Establish authenticated accounts, tenant identity, and customer profile data required by billing and console flows. -**Depends on**: Phase 1 -**Requirements**: [AUTH-01, AUTH-02, AUTH-03, AUTH-04] (deferred to Phase 11 for formal VERIFICATION.md) -**Completed**: 2026-03-29 -**Plans**: 7/7 - -- [x] 02-01: Create the control-plane module, Docker wiring, shared env contract, and initial identity schema. -- [x] 02-02: Implement viewer bootstrap, invitation APIs, invitation acceptance, and explicit current-account selection semantics. -- [x] 02-03: Create the web-console app, hosted Supabase auth routes, and SSR session middleware. -- [x] 02-04: Build the verification-aware console shell, members roster, invitation acceptance UX, and workspace switcher persistence. -- [x] 02-05: Add the current-account core profile API for minimal pre-billing identity data. -- [x] 02-06: Build the short setup flow plus profile settings UI for the core profile. -- [x] 02-07: Add optional durable billing-profile storage and billing settings without making billing completeness a Phase 2 gate. - -### Phase 3: Credits Ledger & Usage Accounting - -**Goal**: Make prepaid credits and request metering financially correct without storing prompts or responses at rest. -**Depends on**: Phase 2 -**Requirements**: [BILL-01, BILL-02, PRIV-01] (deferred to Phase 11 for formal VERIFICATION.md; exercised transitively by phases 5/6/7/10 UAT) -**Completed**: 2026-03-30 -**Plans**: 3/3 - -- [x] 03-01: Implement the Hive Credit ledger, idempotency model, and balance calculations. -- [x] 03-02: Add privacy-safe usage events and request accounting primitives. -- [x] 03-03: Build reservation, finalization, and refund paths for streaming and retry scenarios. - -### Phase 4: Model Catalog & Provider Routing - -**Goal**: Expose Hive-owned model aliases while keeping provider selection internal, policy-driven, and cost-aware. -**Depends on**: Phase 3 -**Requirements**: [ROUT-01, ROUT-02, ROUT-03] -**Completed**: 2026-03-31 -**Plans**: 3/3 - -- [x] 04-01: Create the Hive model catalog, alias schema, and pricing metadata. -- [x] 04-02: Build provider capability matrices and routing policies over LiteLLM-backed adapters. -- [x] 04-03: Add cache-aware usage attribution and sanitized provider error translation. - -### Phase 5: API Keys & Hot-Path Enforcement - -**Goal**: Give customers safe multi-key management while keeping authorization, budgets, and rate limits cheap on the hot path. -**Depends on**: Phase 4 -**Requirements**: [KEY-04] in v1.0 scope; [KEY-01, KEY-02, KEY-03, KEY-05] reassigned to phases 12/13 (v1.1) -**Completed**: 2026-04-05 (lifecycle + KEY-04 attribution) -**Plans**: 6 shipped (2 green in v1.0; 05-02/05-03/05-05/05-06 policy + limiter work carried to v1.1 Phase 12) - -- [x] 05-01: Implement API key issuance, hashing, rotation, revocation, and customer-visible key summaries. -- [x] 05-02: Durable key policy storage and control-plane snapshot projection. -- [x] 05-03: Edge snapshot resolution, alias enforcement, projected-cost budget admission. -- [x] 05-04: Close the two diagnosed Phase 05 UAT gaps around snapshot invalidation and end-to-end API-key attribution. -- [x] 05-05: Per-key usage rollups and live budget-window projection. -- [x] 05-06: Initial Redis Lua rate limiting foundation (full enforcement deferred to Phase 12). - -### Phase 6: Core Text & Embeddings API - -**Goal**: Deliver the main OpenAI-compatible inference endpoints used by agents and developer workflows. -**Depends on**: Phase 5 -**Requirements**: [API-01, API-02, API-03, API-04] -**Completed**: 2026-04-09 -**Plans**: 4/4 - -- [x] 06-01: Internal control-plane accounting/usage endpoints for edge-to-control-plane service calls -- [x] 06-02: Inference types, LiteLLM client, orchestrator, and non-streaming chat/completions + completions handlers -- [x] 06-03: SSE streaming relay, Responses API event translation, and reasoning field normalization -- [x] 06-04: Embeddings endpoint and SDK integration tests for all Phase 6 endpoints - -### Phase 7: Media, File, and Async API Surface - -**Goal**: Extend compatibility to the file and media workflows needed by real OpenAI-integrated applications. -**Depends on**: Phase 6 -**Requirements**: [API-05, API-06, API-07] -**Completed**: 2026-04-10 -**Plans**: 4/4 - -- [x] 07-01: Storage infrastructure (S3 client, file/upload/batch schemas), control-plane filestore service, and routing capability flags -- [x] 07-02: Image generation/edits and audio speech/transcription/translation handlers with LiteLLM dispatch -- [x] 07-03: Files API, Uploads API, Batches API edge handlers, and Asynq batch polling worker -- [x] 07-04: Gap closure — add auth, routing, and accounting to images and audio handlers - -### Phase 8: Payments, FX, and Compliance Checkout - -**Goal**: Let customers buy credits safely across global and Bangladesh-local rails with reproducible FX and tax math. -**Depends on**: Phases 2-3 -**Requirements**: [BILL-03, BILL-04, BILL-07] — BILL-03/04/07 reassigned to phases 13/14 (v1.1) for final console integration; Phase 8 shipped rails + FX + tax core. -**Completed**: 2026-04-11 -**Plans**: 3/3 - -- [x] 08-01: Payment types, DB migrations, PaymentRail interface, FX service, tax calculation, repository, and intent service -- [x] 08-02: Stripe, bKash, and SSLCommerz rail implementations with webhook signature verification -- [x] 08-03: HTTP handler, router registration, and main.go wiring for checkout and webhook endpoints - -### Phase 9: Developer Console & Operational Hardening - -**Goal**: Ship the customer-facing control plane and the operator-facing telemetry needed for launch. -**Depends on**: Phases 5-8 -**Requirements**: [BILL-05, BILL-06, CONS-01, CONS-02, CONS-03, OPS-01] — reassigned to phases 11/13/14 (v1.1). Phase 9 shipped console analytics + observability stack; budget-threshold and invoice-row integration carried to v1.1 Phase 14. -**Completed**: 2026-04-11 -**Plans**: 4/4 - -- [x] 09-04: Prometheus instrumentation, Grafana dashboards, Alertmanager, and Docker Compose monitoring profile (Wave 1) -- [x] 09-01: Control-plane backend — analytics aggregation endpoints, invoice/budget migrations with email notification, cursor pagination, public catalog (Wave 2) -- [x] 09-02: Console billing, invoices, checkout modal with BDT compliance test, API key management, and model catalog pages (Wave 3) -- [x] 09-03: Console analytics tabs with Recharts, time-window filtering, budget alert form and banner (Wave 4) - -### Phase 10: Routing & Storage Critical Fixes - -**Goal:** Fix the three infrastructure bugs that break all inference and media endpoints, and fully remove the legacy local object-storage implementation from the codebase. -**Depends on**: Phases 4, 7 -**Requirements**: [ROUT-02, API-05, API-06, API-07, KEY-04] -**Completed**: 2026-04-21 (UAT closure) -**Plans**: 11/11 - -- [x] 10-01: Wave 0 red validation for shared storage, edge storage config, and status-aware live smoke probes -- [x] 10-02: Wave 0 red validation for routing schema, media/batch route eligibility, filestore internal contracts, and batch output persistence -- [x] 10-03: Supabase migrations and backfill for provider media columns, plus filestore tables; remove runtime DDL -- [x] 10-04: Shared path-style S3-over-HTTP storage package using SigV4 signing -- [x] 10-05: Edge media/file/batch route wiring with required shared storage config -- [x] 10-06: Control-plane filestore response fields, batch status persistence, and StorageUploader wiring -- [x] 10-07: Env documentation and repository-wide legacy storage reference purge -- [x] 10-08: Final route/media checks, full suite, live smoke, and purge verification -- [x] 10-09: Gap closure — accepted accounting policy modes and batch model alias reservation propagation -- [x] 10-10: Gap closure — batch attribution persistence and edge-to-control-plane propagation -- [x] 10-11: Gap closure — terminal reservation settlement, KEY-04, full suite, purge, and live smoke gate - -**Phase 10 UAT (2026-04-21):** 10 pass / 1 partial (batch success-path — upstream provider blocker) / 2 skipped. -See `.planning/phases/10-routing-storage-critical-fixes/10-UAT.md`. - ---- - -## Milestone Summary - -**Key Accomplishments:** - -1. **OpenAI contract fidelity** — Full public API mirror (except org/admin) with JS/Python/Java SDK smoke tests, golden fixtures, Swagger docs, and consistent OpenAI-style errors for unsupported endpoints (Phase 1). -2. **Money-safe prepaid ledger** — Immutable Postgres ledger, reservations, finalization, and refunds for success/failure/cancel/retry/stream-interrupt paths (Phase 3). Live-validated transitively via Phases 5/6/7/10 UAT. -3. **Provider-agnostic routing** — Hive-owned aliases, capability matrix, fallback policy, cache-aware usage attribution, and sanitized provider-blind errors (Phase 4). -4. **Full inference surface** — chat/completions, completions, responses, embeddings, streaming SSE, reasoning-field normalization, image generation/edits, audio speech/STT/translation, files, uploads, batches (Phases 6–7). -5. **Multi-rail BDT/USD checkout** — Stripe + bKash + SSLCommerz with reproducible FX snapshots, BD VAT 15% tax math, `math/big` FX precision, and payment-intent state machine (Phase 8). -6. **Developer console + observability** — Billing, invoices, API key management, analytics tabs with Recharts, Prometheus/Grafana/Alertmanager monitoring profile (Phase 9). -7. **Infrastructure stabilization** — Supabase Storage migration from legacy object-store, S3-over-HTTP SigV4 client, batch output persistence, KEY-04 per-key attribution, provider-blind errors, terminal settlement, cold-start healthcheck stabilization (Phase 10). - -**Key Decisions (outcome-annotated):** - -- ✓ Mirror full public OpenAI API surface except org/admin — validated via SDK smoke tests. -- ✓ Hide upstream provider identity behind Hive aliases — provider-blind errors enforced at edge + control-plane boundaries. -- ✓ Launch with prepaid credits only — ledger + reservation + attribution verified end-to-end. -- ✓ Hosted Supabase for auth + primary Postgres + object storage — single backend, no local MinIO. -- ✓ Run entire local dev in Docker — 580 commits delivered without host-installed Go or Node. -- ✓ `math/big` for all FX calculations — prevents float64 corruption on BDT rails. -- ✓ No FX display to BD customers — regulatory requirement; `amount_usd` removal carried to v1.1 (Phase 11). - -**Issues Deferred to v1.1 (tech debt, documented):** - -1. **Batch success-path terminal settlement** — blocked upstream; OpenRouter + Groq have no native batch API, LiteLLM's managed file upload supports only openai/azure/vertex_ai/manus/anthropic. Failure-path settlement verified live. See `.planning/phases/10-routing-storage-critical-fixes/KNOWN-ISSUE-batch-upstream.md`. -2. **`ensureCapabilityColumns` wrong-table fix** — targets `route_capabilities` instead of `provider_capabilities`. Latent; seed path populates required columns. -3. **`amount_usd` on BD checkout** — regulatory risk on BD-visible payment response field. -4. **Formal VERIFICATION.md for phases 2 & 3** — 02-UAT.md (12/12 pass) + 03-VALIDATION.md draft stand as evidence; formal artifact carried to Phase 11. -5. **KEY-05 hot-path rate limiting** — account-tier + per-key rate enforcement on hot path. Carried to v1.1 Phase 12. -6. **Phases 11–14** — compliance cleanup, hot-path rate limits, console integration, invoicing + budget integration. Scope in `.planning/v1.1-DEFERRED-SCOPE.md`. - -**Nyquist:** Project does not use formal Nyquist validation — live UAT treated as verification equivalent. All 10 VALIDATION.md files carry `nyquist_compliant: false` as draft status. - ---- - -_For current project status, see .planning/ROADMAP.md_ -_For deferred scope, see .planning/v1.1-DEFERRED-SCOPE.md_ -_For audit detail, see .planning/milestones/v1.0-MILESTONE-AUDIT.md_ diff --git a/.planning/milestones/v1.1-REQUIREMENTS.md b/.planning/milestones/v1.1-REQUIREMENTS.md deleted file mode 100644 index 0b3abb405..000000000 --- a/.planning/milestones/v1.1-REQUIREMENTS.md +++ /dev/null @@ -1,71 +0,0 @@ -# Requirements: Hive v1.1 — Live Tracking - -**Defined:** 2026-04-25. -**Milestone:** v1.1 (deferred scope + chat-app track). -**Status:** in progress. - -This file tracks live requirements for v1.1. Source: per-phase `PLAN.md` `requirements:` frontmatter. As each phase ships, status flips and Evidence column points at that phase's VERIFICATION artifact. - -When v1.1 ships, this file is archived to `.planning/milestones/v1.1-REQUIREMENTS.md` (in place; this IS the archive path) and v1.2 starts fresh via `/gsd:new-milestone`. - ---- - -## Track A — Deferred from v1.0 - -(populated as Phases 11–14 land; placeholders here for traceability) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| API-07-followup | 11 | Planned | — | -| KEY-04-followup | 11 | Planned | — | -| ENSURE-CAP-COL-FIX | 11 | Planned | — | -| AMOUNT-USD-BD-FIX | 11 | Planned | — | - ---- - -## Track B — chat-app (LibreChat fork) - -### Phase 19 — Fork & Strip LibreChat (pinned tag) + Language Picker - -| ID | Description | Status | Evidence | -|----|-------------|--------|----------| -| CHATAPP-19-01 | LibreChat v0.7.9 forked into `apps/chat-app/` with pinned commit SHA recorded | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` + `v1.1-chatapp/LIBRECHAT-VERSION.md` | -| CHATAPP-19-02 | `librechat.yaml` strips non-Hive providers; single `endpoints.custom[]` points at `edge-api:8080/v1` | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` | -| CHATAPP-19-03 | FX zero-leak: `interface.showCost=false`, `interface.showTokens=false` (Phase 17 mandate enforcement point) | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` | -| CHATAPP-19-04 | MongoDB connection env-driven; Atlas M0 wired for staging, local Mongo for `--profile local` dev | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` | -| CHATAPP-19-05 | `bn-BD` locale verified-or-scaffolded (skeleton only; Phase 23 owns translations) | Satisfied (scaffolded from `en`; upstream had no Bengali at v0.7.9) | `v1.1-chatapp/LIBRECHAT-VERSION.md` | -| CHATAPP-19-06 | First-run language-picker modal (localStorage-gated) persists locale via prefs hook (Phase 20 wires to DB) | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` | -| CHATAPP-19-07 | Docker Compose service `chat-app` boots locally with healthcheck, `depends_on` edge-api+control-plane | Satisfied | `phases/19-chat-app-fork-strip/19-VERIFICATION.md` | -| CHATAPP-19-08 | `LIBRECHAT-UPGRADE-PLAYBOOK.md` documents upstream-track + re-strip procedure | Satisfied | `v1.1-chatapp/LIBRECHAT-UPGRADE-PLAYBOOK.md` | -| CHATAPP-19-09 | CI workflow runs lint + typecheck + build green for chat-app workspace | Satisfied | `.github/workflows/chat-app-ci.yml` | - -### Phases 20–25 (placeholders) - -(populated as plans land; one-line each with phase + ID) - -| ID | Phase | Status | Evidence | -|----|-------|--------|----------| -| CHATAPP-20-* | 20 — Supabase auth swap | Planned | — | -| CHATAPP-21-* | 21 — Tier limits + invite/referral | Planned | — | -| CHATAPP-22-* | 22 — File upload + RAG sidecar | Planned | — | -| CHATAPP-23-* | 23 — Bengali translations + default model | Planned | — | -| CHATAPP-24-* | 24 — OCI deploy + CF DNS | Planned | — | -| CHATAPP-25-* | 25 — UAT + soft launch | Planned | — | - ---- - -## Status legend - -- `Satisfied` — Phase shipped with evidence link. -- `Partial` — Phase shipped, some sub-items deferred. Evidence + deferral note required. -- `Planned` — Phase not yet executed. -- `Blocked` — Awaiting upstream/external/decision; tracked in phase Blockers. - -## v1.1.0 Ship-Gate Mapping - -Track B Phase 19 contributes to: - -- **FX/USD audit clean** — chat-app surface enforces `showCost`/`showTokens` off via `librechat.yaml`; CI guards against regression. Closes a fragment of the cross-phase v1.1.0 ship-gate. -- **Track B chat-app local boot** — `docker compose --profile local up chat-app` reports healthy. Sets the floor Phases 20–25 build on. - -Does NOT close: Supabase auth swap (20), tier limits (21), RAG (22), Bengali translations + default model (23), OCI deploy (24), UAT soft-launch (25). diff --git a/.planning/outreach/susana-martins-email.md b/.planning/outreach/susana-martins-email.md deleted file mode 100644 index 4a985086a..000000000 --- a/.planning/outreach/susana-martins-email.md +++ /dev/null @@ -1,34 +0,0 @@ -# Email: Sakib to Susana Martins (WEtech Alliance) - -**To:** Susana Martins, Client Advisor, WEtech Alliance -**From:** Sakib Sadman Shajib -**Subject:** Reconnecting, and a product I would love your help launching - ---- - - - -Hi Susana, - -It has been a while since we last connected, when you kindly invited me to [event name / approx date]. Thank you again for that. I went heads-down building ever since, and I am reaching out now because the work has reached a point where your help could make a real difference. - -I have been building Hive: a self-hosted, OpenAI-compatible AI gateway that lets an organisation run modern AI on its own hardware, so sensitive data never leaves its control. - -Along the way I found a real market. Regulated and data-sensitive organisations (banks, insurers, FinTech, healthcare providers, and law firms) and any company that will not put its data in someone else's cloud. Even "zero data retention" promises do not satisfy them, for two structural reasons. - -First, the US CLOUD Act means data held on US-owned clouds can be compelled regardless of where it physically sits. Second, prompts sent to public AI services can be retained and later become discoverable in litigation; a US court has already ordered a major provider to preserve chat logs. On-premises Hive removes both exposures, because the data never leaves the building. - -Verticals I am focused on are law, finance, and healthcare. A small firm can start with a single box, and the hardware scales as it grows. - -I would value your help with four things: - -1. A meeting as soon as possible. -2. Cloud credits to run staging and a hosted demo. For a sovereign-grade hosted option, OVH or DAIR fit best, while US-owned credits suit dev and demo. -3. Help registering and structuring the business in Ontario. -4. Help with marketing and go-to-market now that the product is built. - -Could we find a short call in the next week or two? I would be glad to walk you through it. - -Warm regards, -Sakib Sadman Shajib -[phone] | [email] diff --git a/.planning/phases/01-contract-compatibility-harness/01-01-PLAN.md b/.planning/phases/01-contract-compatibility-harness/01-01-PLAN.md deleted file mode 100644 index 526063095..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-01-PLAN.md +++ /dev/null @@ -1,467 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 01 -type: execute -wave: 1 -depends_on: [] -files_modified: - - deploy/docker/docker-compose.yml - - deploy/docker/docker-compose.override.yml - - deploy/docker/Dockerfile.edge-api - - deploy/docker/Dockerfile.toolchain - - deploy/docker/Dockerfile.sdk-tests-js - - deploy/docker/Dockerfile.sdk-tests-py - - deploy/docker/Dockerfile.sdk-tests-java - - apps/edge-api/go.mod - - apps/edge-api/go.sum - - apps/edge-api/cmd/server/main.go - - apps/edge-api/.air.toml - - go.work - - go.work.sum - - packages/sdk-tests/js/package.json - - packages/sdk-tests/js/tsconfig.json - - packages/sdk-tests/js/vitest.config.ts - - packages/sdk-tests/python/pyproject.toml - - packages/sdk-tests/java/build.gradle - - packages/sdk-tests/java/settings.gradle - - .gitignore -autonomous: true -requirements: - - API-08 -must_haves: - truths: - - "docker compose up starts the edge-api service and it responds on port 8080" - - "docker compose run --rm edge-api go test ./... -short exits 0" - - "docker compose --profile tools run --rm toolchain ogen --version prints a version string" - - "Go source file changes trigger automatic recompilation via air inside the edge-api container" - - "Go module cache persists across container restarts via named volumes" - artifacts: - - path: "deploy/docker/docker-compose.yml" - provides: "Full dev stack orchestration with edge-api, toolchain, and SDK test services" - contains: "edge-api" - - path: "deploy/docker/Dockerfile.edge-api" - provides: "Go dev image with air hot-reload" - contains: "air" - - path: "deploy/docker/Dockerfile.toolchain" - provides: "Codegen tools container with ogen and oapi-codegen" - contains: "ogen" - - path: "deploy/docker/Dockerfile.sdk-tests-js" - provides: "Node + OpenAI SDK + Vitest container" - contains: "vitest" - - path: "deploy/docker/Dockerfile.sdk-tests-py" - provides: "Python + OpenAI SDK + pytest container" - contains: "pytest" - - path: "deploy/docker/Dockerfile.sdk-tests-java" - provides: "Java + OpenAI SDK + JUnit/Gradle container" - contains: "gradle" - - path: "apps/edge-api/cmd/server/main.go" - provides: "Minimal Go HTTP server entry point" - contains: "ListenAndServe" - - path: "go.work" - provides: "Go workspace root for multi-module monorepo" - contains: "apps/edge-api" - key_links: - - from: "deploy/docker/docker-compose.yml" - to: "deploy/docker/Dockerfile.edge-api" - via: "build context reference" - pattern: "Dockerfile\\.edge-api" - - from: "deploy/docker/docker-compose.yml" - to: "apps/edge-api/.air.toml" - via: "air config mounted into container" - pattern: "air" ---- - - -Create the Docker-only developer stack so that all builds, code generation, tests, and hot-reload run inside containers without host-installed Go, Node, Python, or Java. - -Purpose: Every subsequent plan depends on this infrastructure. No code can be built, generated, or tested without the containerized dev environment. -Output: Working Docker Compose stack with edge-api (Go + air), toolchain (ogen + oapi-codegen), and SDK test containers (JS/Python/Java). - - - -@/home/sakib/.claude/get-shit-done/workflows/execute-plan.md -@/home/sakib/.claude/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md - - - - - - Task 1: Create Go workspace, edge-api module, and minimal HTTP server - - go.work, - apps/edge-api/go.mod, - apps/edge-api/cmd/server/main.go, - apps/edge-api/.air.toml, - .gitignore - - - .planning/phases/01-contract-compatibility-harness/01-RESEARCH.md - - -1. Create the root Go workspace file `go.work` with Go 1.24 and a single workspace member `./apps/edge-api`. - -2. Initialize the Go module at `apps/edge-api/go.mod` with module path `github.com/sakibsadmanshajib/hive/apps/edge-api` and Go 1.24. - -3. Create `apps/edge-api/cmd/server/main.go` with a minimal HTTP server: - - Import `net/http`, `log`, `os`, `encoding/json`. - - Listen on port from `PORT` env var, defaulting to `8080`. - - Register two routes: - - `GET /health` returning `{"status":"ok"}` with 200 and `Content-Type: application/json`. - - `GET /v1/models` returning `{"object":"list","data":[]}` with 200 and `Content-Type: application/json` (placeholder for future model listing). - - Log `"edge-api listening on :PORT"` on startup. - - Use `http.DefaultServeMux` (standard library only, no external router yet). - -4. Create `apps/edge-api/.air.toml` with: - ```toml - root = "/app/apps/edge-api" - tmp_dir = "tmp" - - [build] - cmd = "go build -o ./tmp/main ./cmd/server" - bin = "./tmp/main" - include_ext = ["go", "toml"] - exclude_dir = ["tmp", "vendor", "testdata"] - delay = 1000 - - [log] - time = false - - [misc] - clean_on_exit = true - ``` - -5. Create `.gitignore` at repository root with entries: - ``` - # Go - /apps/edge-api/tmp/ - *.exe - *.exe~ - *.dll - *.so - *.dylib - - # Dependencies - /vendor/ - - # IDE - .idea/ - .vscode/ - *.swp - *.swo - - # OS - .DS_Store - Thumbs.db - - # Docker - .docker/ - - # Generated - /packages/openai-contract/generated/ - /apps/edge-api/internal/generated/ - ``` - - - cd /home/sakib/hive && test -f go.work && test -f apps/edge-api/go.mod && test -f apps/edge-api/cmd/server/main.go && test -f apps/edge-api/.air.toml && test -f .gitignore && echo "PASS" - - - - go.work contains `go 1.24` and `use ./apps/edge-api` - - apps/edge-api/go.mod contains `module github.com/sakibsadmanshajib/hive/apps/edge-api` - - apps/edge-api/cmd/server/main.go contains `ListenAndServe` and `health` and `v1/models` - - apps/edge-api/.air.toml contains `cmd = "go build -o ./tmp/main ./cmd/server"` - - .gitignore contains `/apps/edge-api/tmp/` and `/packages/openai-contract/generated/` - - Go workspace initialized, minimal edge-api server compiles and registers /health and /v1/models endpoints, air config ready for hot-reload - - - - Task 2: Create all Dockerfiles, SDK test project scaffolds, and Docker Compose orchestration - - deploy/docker/Dockerfile.edge-api, - deploy/docker/Dockerfile.toolchain, - deploy/docker/Dockerfile.sdk-tests-js, - deploy/docker/Dockerfile.sdk-tests-py, - deploy/docker/Dockerfile.sdk-tests-java, - deploy/docker/docker-compose.yml, - deploy/docker/docker-compose.override.yml, - packages/sdk-tests/js/package.json, - packages/sdk-tests/js/tsconfig.json, - packages/sdk-tests/js/vitest.config.ts, - packages/sdk-tests/python/pyproject.toml, - packages/sdk-tests/java/build.gradle, - packages/sdk-tests/java/settings.gradle - - - .planning/phases/01-contract-compatibility-harness/01-RESEARCH.md, - apps/edge-api/go.mod, - apps/edge-api/.air.toml, - go.work - - -1. Create `deploy/docker/Dockerfile.edge-api`: - ```dockerfile - FROM golang:1.24-alpine AS base - RUN apk add --no-cache git - RUN go install github.com/air-verse/air@v1.64.5 - WORKDIR /app - COPY go.work go.work.sum* ./ - COPY apps/edge-api/go.mod apps/edge-api/go.sum* ./apps/edge-api/ - RUN cd apps/edge-api && go mod download - COPY apps/edge-api/ ./apps/edge-api/ - EXPOSE 8080 - CMD ["air", "-c", "apps/edge-api/.air.toml"] - ``` - -2. Create `deploy/docker/Dockerfile.toolchain`: - ```dockerfile - FROM golang:1.24-alpine AS base - RUN apk add --no-cache git curl nodejs npm python3 py3-pip - RUN go install github.com/ogen-go/ogen/cmd/ogen@v1.20.2 - RUN go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.6.0 - RUN npm install -g @redocly/cli@latest - WORKDIR /workspace - ENTRYPOINT ["/bin/sh", "-c"] - ``` - -3. Create `deploy/docker/Dockerfile.sdk-tests-js`: - ```dockerfile - FROM node:22-alpine - WORKDIR /tests - COPY packages/sdk-tests/js/package.json packages/sdk-tests/js/package-lock.json* ./ - RUN npm install - COPY packages/sdk-tests/js/ ./ - CMD ["npx", "vitest", "run"] - ``` - -4. Create `deploy/docker/Dockerfile.sdk-tests-py`: - ```dockerfile - FROM python:3.13-slim - WORKDIR /tests - COPY packages/sdk-tests/python/pyproject.toml ./ - RUN pip install --no-cache-dir -e ".[dev]" - COPY packages/sdk-tests/python/ ./ - CMD ["pytest", "-v"] - ``` - -5. Create `deploy/docker/Dockerfile.sdk-tests-java`: - ```dockerfile - FROM gradle:8-jdk21-alpine - WORKDIR /tests - COPY packages/sdk-tests/java/build.gradle packages/sdk-tests/java/settings.gradle ./ - RUN gradle dependencies --no-daemon || true - COPY packages/sdk-tests/java/ ./ - CMD ["gradle", "test", "--no-daemon"] - ``` - -6. Create `packages/sdk-tests/js/package.json`: - ```json - { - "name": "@hive/sdk-tests-js", - "version": "0.0.1", - "private": true, - "type": "module", - "scripts": { - "test": "vitest run", - "test:watch": "vitest" - }, - "dependencies": { - "openai": "^6.33.0" - }, - "devDependencies": { - "vitest": "^3.0.0", - "typescript": "^5.7.0" - } - } - ``` - -7. Create `packages/sdk-tests/js/tsconfig.json`: - ```json - { - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "outDir": "dist", - "rootDir": "tests" - }, - "include": ["tests/**/*.ts"] - } - ``` - -8. Create `packages/sdk-tests/js/vitest.config.ts`: - ```typescript - import { defineConfig } from "vitest/config"; - - export default defineConfig({ - test: { - testTimeout: 30000, - hookTimeout: 10000, - }, - }); - ``` - -9. Create `packages/sdk-tests/python/pyproject.toml`: - ```toml - [project] - name = "hive-sdk-tests-python" - version = "0.0.1" - requires-python = ">=3.11" - dependencies = [ - "openai>=2.30.0", - ] - - [project.optional-dependencies] - dev = [ - "pytest>=8.0.0", - "pytest-asyncio>=0.24.0", - ] - - [tool.pytest.ini_options] - testpaths = ["tests"] - asyncio_mode = "auto" - ``` - -10. Create `packages/sdk-tests/java/build.gradle`: - ```groovy - plugins { - id 'java' - } - - java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } - } - - repositories { - mavenCentral() - } - - dependencies { - implementation 'com.openai:openai-java:4.30.0' - testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - } - - test { - useJUnitPlatform() - systemProperty 'HIVE_BASE_URL', System.getenv('HIVE_BASE_URL') ?: 'http://edge-api:8080/v1' - } - ``` - -11. Create `packages/sdk-tests/java/settings.gradle`: - ```groovy - rootProject.name = 'hive-sdk-tests-java' - ``` - -12. Create `deploy/docker/docker-compose.yml`: - - `edge-api` service: builds from `Dockerfile.edge-api` with context `../../`, ports `8080:8080`, named volumes `gomodcache:/go/pkg/mod` and `gobuildcache:/root/.cache/go-build`, healthcheck `wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1` with interval 5s, timeout 3s, retries 3. - - `toolchain` service: builds from `Dockerfile.toolchain` with context `../../`, profiles `["tools"]`, volumes `../../:/workspace` and `gomodcache:/go/pkg/mod`. - - `sdk-tests-js` service: builds from `Dockerfile.sdk-tests-js` with context `../../`, profiles `["test"]`, depends_on `edge-api` (condition service_healthy), environment `HIVE_BASE_URL=http://edge-api:8080/v1`. - - `sdk-tests-py` service: builds from `Dockerfile.sdk-tests-py` with context `../../`, profiles `["test"]`, depends_on `edge-api` (condition service_healthy), environment `HIVE_BASE_URL=http://edge-api:8080/v1`. - - `sdk-tests-java` service: builds from `Dockerfile.sdk-tests-java` with context `../../`, profiles `["test"]`, depends_on `edge-api` (condition service_healthy), environment `HIVE_BASE_URL=http://edge-api:8080/v1`. - - Declare named volumes: `gomodcache`, `gobuildcache`. - -13. Create `deploy/docker/docker-compose.override.yml` with develop.watch config for edge-api: - ```yaml - services: - edge-api: - develop: - watch: - - action: sync - path: ../../apps/edge-api - target: /app/apps/edge-api - ignore: - - tmp/ - - action: rebuild - path: ../../apps/edge-api/go.mod - ``` - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml config --quiet 2>&1 && echo "COMPOSE_VALID" || echo "COMPOSE_INVALID" - - - - deploy/docker/docker-compose.yml contains services `edge-api`, `toolchain`, `sdk-tests-js`, `sdk-tests-py`, `sdk-tests-java` - - deploy/docker/docker-compose.yml contains `healthcheck` under edge-api service - - deploy/docker/docker-compose.yml contains `HIVE_BASE_URL=http://edge-api:8080/v1` for all SDK test services - - deploy/docker/Dockerfile.edge-api contains `air-verse/air@v1.64.5` and `golang:1.24-alpine` - - deploy/docker/Dockerfile.toolchain contains `ogen@v1.20.2` and `oapi-codegen@v2.6.0` - - deploy/docker/Dockerfile.sdk-tests-js contains `node:22-alpine` and `vitest` - - deploy/docker/Dockerfile.sdk-tests-py contains `python:3.13-slim` and `pytest` - - deploy/docker/Dockerfile.sdk-tests-java contains `gradle:8-jdk21-alpine` - - packages/sdk-tests/js/package.json contains `"openai": "^6.33.0"` and `"vitest"` - - packages/sdk-tests/python/pyproject.toml contains `openai>=2.30.0` and `pytest>=8.0.0` - - packages/sdk-tests/java/build.gradle contains `com.openai:openai-java:4.30.0` and `junit-jupiter` - - docker compose -f deploy/docker/docker-compose.yml config exits 0 - - All Dockerfiles build, SDK test project scaffolds have correct dependency versions, Docker Compose validates and defines all services with healthchecks and named volumes - - - - Task 3: Verify full Docker stack boots and health endpoint responds - - apps/edge-api/cmd/server/main.go - - - deploy/docker/docker-compose.yml, - deploy/docker/Dockerfile.edge-api, - apps/edge-api/cmd/server/main.go - - -1. Run `docker compose -f deploy/docker/docker-compose.yml build edge-api` to build the edge-api image. - -2. Run `docker compose -f deploy/docker/docker-compose.yml up -d edge-api` to start the edge-api service. - -3. Wait for the healthcheck to pass (max 30 seconds): `docker compose -f deploy/docker/docker-compose.yml ps --format json` and verify edge-api shows as healthy. - -4. Run `curl -sf http://localhost:8080/health` and verify the response is `{"status":"ok"}`. - -5. Run `curl -sf http://localhost:8080/v1/models` and verify the response is `{"object":"list","data":[]}`. - -6. Run `docker compose -f deploy/docker/docker-compose.yml down` to clean up. - -7. If any step fails, diagnose and fix the issue in the relevant files (Dockerfile, compose, or main.go) before proceeding. Common issues: go.work.sum missing (run `go work sync`), COPY paths wrong in Dockerfile, port not exposed. - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml build edge-api && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 8 && curl -sf http://localhost:8080/health | grep -q '"status":"ok"' && curl -sf http://localhost:8080/v1/models | grep -q '"object":"list"' && docker compose -f deploy/docker/docker-compose.yml down && echo "PASS" - - - - docker compose build edge-api exits 0 - - curl http://localhost:8080/health returns JSON containing `"status":"ok"` - - curl http://localhost:8080/v1/models returns JSON containing `"object":"list"` - - docker compose down cleans up without errors - - Docker stack boots, edge-api health endpoint responds with 200, placeholder /v1/models responds with empty list, all containers start and stop cleanly - - - - - -- `docker compose -f deploy/docker/docker-compose.yml config` validates without errors -- `docker compose -f deploy/docker/docker-compose.yml build` builds all images -- `curl -sf http://localhost:8080/health` returns `{"status":"ok"}` -- Go workspace resolves: `go work sync` exits 0 -- All SDK test project dependency files exist with correct versions - - - -- Contributors can run `docker compose -f deploy/docker/docker-compose.yml up` and get a working edge-api on port 8080 -- No host-installed Go, Node, Python, or Java is required -- The toolchain container has ogen v1.20.2 and oapi-codegen v2.6.0 -- All SDK test containers have pinned OpenAI SDK versions (JS 6.33.0, Python 2.30.0, Java 4.30.0) -- Named volumes persist Go module and build caches across restarts - - - -After completion, create `.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md` - diff --git a/.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md b/.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md deleted file mode 100644 index 059c01bc3..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 01 -subsystem: infra -tags: [docker, go, air, ogen, oapi-codegen, vitest, pytest, junit, openai-sdk] - -# Dependency graph -requires: [] -provides: - - Docker Compose dev stack with edge-api, toolchain, and SDK test containers - - Go workspace with minimal HTTP server (/health, /v1/models) - - SDK test scaffolds for JS (Vitest), Python (pytest), Java (JUnit/Gradle) - - Hot-reload via air inside containerized Go development -affects: [01-02, 01-03, 02, 03, 04] - -# Tech tracking -tech-stack: - added: [go-1.24, air-1.64.5, ogen-1.20.2, oapi-codegen-2.6.0, openai-js-6.33, openai-py-2.30, openai-java-4.30, vitest-3, pytest-8, junit-5.11, gradle-8, docker-compose] - patterns: [containerized-dev-workflow, go-workspace-monorepo, profile-based-compose-services, healthcheck-based-dependency] - -key-files: - created: - - go.work - - apps/edge-api/go.mod - - apps/edge-api/cmd/server/main.go - - apps/edge-api/.air.toml - - deploy/docker/docker-compose.yml - - deploy/docker/docker-compose.override.yml - - deploy/docker/Dockerfile.edge-api - - deploy/docker/Dockerfile.toolchain - - deploy/docker/Dockerfile.sdk-tests-js - - deploy/docker/Dockerfile.sdk-tests-py - - deploy/docker/Dockerfile.sdk-tests-java - - packages/sdk-tests/js/package.json - - packages/sdk-tests/js/tsconfig.json - - packages/sdk-tests/js/vitest.config.ts - - packages/sdk-tests/python/pyproject.toml - - packages/sdk-tests/java/build.gradle - - packages/sdk-tests/java/settings.gradle - - .gitignore - modified: [] - -key-decisions: - - "Used GOTOOLCHAIN=auto to install air v1.64.5 (requires Go 1.25) on Go 1.24 base image" - - "Air build command uses absolute paths from /app workspace root for go.work compatibility" - - "SDK test services use Docker Compose profiles (test) so they only run on demand" - -patterns-established: - - "Containerized dev: all builds, codegen, and tests run inside Docker, no host toolchains required" - - "Go workspace monorepo: go.work at repo root with module members under apps/" - - "Profile-based services: tools and test profiles keep docker compose up lightweight" - - "Healthcheck gating: SDK test containers depend on edge-api service_healthy condition" - -requirements-completed: [API-08] - -# Metrics -duration: 8min -completed: 2026-03-28 ---- - -# Phase 01 Plan 01: Docker Dev Stack Summary - -**Containerized Go edge-api with air hot-reload, ogen/oapi-codegen toolchain, and JS/Python/Java SDK test scaffolds via Docker Compose** - -## Performance - -- **Duration:** 8 min -- **Started:** 2026-03-28T07:22:18Z -- **Completed:** 2026-03-28T07:30:27Z -- **Tasks:** 3 -- **Files modified:** 18 - -## Accomplishments -- Go workspace with minimal HTTP server exposing /health and /v1/models endpoints -- Docker Compose stack with edge-api (Go+air), toolchain (ogen+oapi-codegen), and 3 SDK test containers -- All SDK test projects scaffolded with pinned OpenAI SDK versions (JS 6.33, Python 2.30, Java 4.30) -- Named volumes for Go module and build caches persist across container restarts - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Create Go workspace, edge-api module, and minimal HTTP server** - `d21d687` (feat) -2. **Task 2: Create all Dockerfiles, SDK test project scaffolds, and Docker Compose orchestration** - `4813ef1` (feat) -3. **Task 3: Verify full Docker stack boots and health endpoint responds** - `210254e` (fix) - -## Files Created/Modified -- `go.work` - Go workspace root for multi-module monorepo -- `apps/edge-api/go.mod` - Edge API Go module definition -- `apps/edge-api/cmd/server/main.go` - Minimal HTTP server with /health and /v1/models -- `apps/edge-api/.air.toml` - Air hot-reload config for containerized development -- `deploy/docker/docker-compose.yml` - Full dev stack orchestration -- `deploy/docker/docker-compose.override.yml` - File sync watch config for development -- `deploy/docker/Dockerfile.edge-api` - Go dev image with air hot-reload -- `deploy/docker/Dockerfile.toolchain` - Codegen tools (ogen, oapi-codegen, redocly) -- `deploy/docker/Dockerfile.sdk-tests-js` - Node + OpenAI SDK + Vitest -- `deploy/docker/Dockerfile.sdk-tests-py` - Python + OpenAI SDK + pytest -- `deploy/docker/Dockerfile.sdk-tests-java` - Java + OpenAI SDK + JUnit/Gradle -- `packages/sdk-tests/js/package.json` - JS test project with openai ^6.33.0 -- `packages/sdk-tests/js/tsconfig.json` - TypeScript config for test project -- `packages/sdk-tests/js/vitest.config.ts` - Vitest configuration -- `packages/sdk-tests/python/pyproject.toml` - Python test project with openai >=2.30.0 -- `packages/sdk-tests/java/build.gradle` - Java test project with openai-java 4.30.0 -- `packages/sdk-tests/java/settings.gradle` - Gradle project settings -- `.gitignore` - Repository gitignore for Go, IDE, Docker, and generated files - -## Decisions Made -- Used `GOTOOLCHAIN=auto` for air installation since air v1.64.5 requires Go 1.25 but our base image is Go 1.24 -- Air config uses absolute paths from `/app` workspace root to ensure go.work is visible during builds -- SDK test services placed behind Docker Compose `test` profile to keep default `docker compose up` lightweight - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Air v1.64.5 requires Go 1.25, incompatible with Go 1.24 base image** -- **Found during:** Task 3 (Docker stack verification) -- **Issue:** `go install github.com/air-verse/air@v1.64.5` failed because air requires Go >= 1.25 -- **Fix:** Added `GOTOOLCHAIN=auto` env var to the install command, allowing Go to download the required toolchain -- **Files modified:** deploy/docker/Dockerfile.edge-api -- **Verification:** Docker build succeeds, air starts correctly inside container -- **Committed in:** 210254e (Task 3 commit) - -**2. [Rule 1 - Bug] Air build command resolved paths relative to WORKDIR not root setting** -- **Found during:** Task 3 (Docker stack verification) -- **Issue:** Air resolved `./cmd/server` relative to WORKDIR `/app` instead of air root `/app/apps/edge-api`, causing `stat /app/cmd/server: directory not found` -- **Fix:** Changed air.toml to use absolute paths: `cd /app && go build -o /app/apps/edge-api/tmp/main ./apps/edge-api/cmd/server` and `full_bin` instead of deprecated `bin` -- **Files modified:** apps/edge-api/.air.toml -- **Verification:** Container starts, builds successfully, server responds on port 8080 -- **Committed in:** 210254e (Task 3 commit) - ---- - -**Total deviations:** 2 auto-fixed (1 bug, 1 blocking) -**Impact on plan:** Both fixes necessary for the Docker stack to function. No scope creep. - -## Issues Encountered -None beyond the auto-fixed deviations above. - -## User Setup Required -None - no external service configuration required. - -## Next Phase Readiness -- Docker dev stack fully operational, ready for OpenAPI spec download and code generation (Plan 02) -- Toolchain container has ogen and oapi-codegen installed for contract generation -- SDK test containers ready to receive test files once endpoints are implemented - -## Self-Check: PASSED - -All 18 key files verified present. All 3 task commits verified in git log. - ---- -*Phase: 01-contract-compatibility-harness* -*Completed: 2026-03-28* diff --git a/.planning/phases/01-contract-compatibility-harness/01-02-PLAN.md b/.planning/phases/01-contract-compatibility-harness/01-02-PLAN.md deleted file mode 100644 index 8725912b9..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-02-PLAN.md +++ /dev/null @@ -1,385 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 02 -type: execute -wave: 2 -depends_on: - - 01-01 -files_modified: - - packages/openai-contract/upstream/openapi.yaml - - packages/openai-contract/upstream/SPEC_VERSION - - packages/openai-contract/scripts/import-spec.sh - - packages/openai-contract/overlays/hive-support-status.yaml - - packages/openai-contract/scripts/apply-overlays.sh - - packages/openai-contract/matrix/support-matrix.json - - packages/openai-contract/scripts/generate-matrix.sh - - apps/edge-api/internal/errors/openai.go - - apps/edge-api/internal/errors/openai_test.go - - apps/edge-api/internal/middleware/compat_headers.go - - apps/edge-api/internal/middleware/compat_headers_test.go - - apps/edge-api/internal/middleware/unsupported.go - - apps/edge-api/internal/middleware/unsupported_test.go - - apps/edge-api/internal/matrix/loader.go - - apps/edge-api/internal/matrix/loader_test.go - - apps/edge-api/internal/matrix/types.go - - apps/edge-api/cmd/server/main.go - - apps/edge-api/docs/swagger.go - - docs/support-matrix.md -autonomous: true -requirements: - - COMP-02 - - COMP-03 - - API-08 -must_haves: - truths: - - "Hive returns OpenAI-style error JSON with {error: {message, type, param, code}} for all error responses" - - "Any request to a public non-org/admin endpoint not marked supported_now gets a 404 with error type unsupported_endpoint" - - "Every response includes x-request-id, openai-version, and openai-processing-ms headers" - - "The support matrix classifies every public non-org/admin endpoint with one of four statuses" - - "Swagger UI at /docs/ serves the Hive OpenAPI spec and is browsable" - - "The support matrix JSON is the single source of truth for both runtime middleware and published docs" - artifacts: - - path: "packages/openai-contract/upstream/openapi.yaml" - provides: "Pinned copy of the OpenAI OpenAPI spec" - contains: "openapi:" - - path: "packages/openai-contract/matrix/support-matrix.json" - provides: "Machine-readable endpoint classification with four statuses" - contains: "supported_now" - - path: "apps/edge-api/internal/errors/openai.go" - provides: "OpenAI error envelope builder" - exports: ["WriteError", "OpenAIError", "OpenAIErrorBody"] - - path: "apps/edge-api/internal/middleware/unsupported.go" - provides: "Matrix-driven middleware rejecting non-supported endpoints" - exports: ["UnsupportedEndpointMiddleware"] - - path: "apps/edge-api/internal/middleware/compat_headers.go" - provides: "OpenAI compatibility response headers" - exports: ["CompatHeaders"] - - path: "apps/edge-api/internal/matrix/loader.go" - provides: "Support matrix JSON loader and lookup" - exports: ["LoadMatrix", "SupportMatrix", "Lookup"] - - path: "apps/edge-api/docs/swagger.go" - provides: "Embedded Swagger UI handler" - exports: ["SwaggerHandler"] - - path: "docs/support-matrix.md" - provides: "Human-readable endpoint reference table" - contains: "supported now" - key_links: - - from: "apps/edge-api/internal/middleware/unsupported.go" - to: "packages/openai-contract/matrix/support-matrix.json" - via: "matrix loader reads JSON at startup" - pattern: "support-matrix\\.json" - - from: "apps/edge-api/internal/middleware/unsupported.go" - to: "apps/edge-api/internal/errors/openai.go" - via: "calls WriteError for unsupported responses" - pattern: "errors\\.WriteError" - - from: "apps/edge-api/cmd/server/main.go" - to: "apps/edge-api/internal/middleware/unsupported.go" - via: "wraps handler with UnsupportedEndpointMiddleware" - pattern: "UnsupportedEndpointMiddleware" - - from: "apps/edge-api/cmd/server/main.go" - to: "apps/edge-api/internal/middleware/compat_headers.go" - via: "wraps handler with CompatHeaders middleware" - pattern: "CompatHeaders" - - from: "apps/edge-api/docs/swagger.go" - to: "packages/openai-contract/upstream/openapi.yaml" - via: "serves spec file via embedded or file path" - pattern: "openapi\\.yaml" ---- - - -Import the OpenAI contract, build the endpoint support matrix, create the error envelope and compatibility middleware, and publish Swagger docs -- making Hive's public API a classified, testable contract surface. - -Purpose: This plan establishes the contract layer that all future endpoint implementations build on. The support matrix drives both runtime behavior (unsupported endpoint rejection) and documentation (Swagger, support-matrix.md). Without this, Hive cannot prove or enforce compatibility. -Output: Imported OpenAI spec, support matrix JSON, OpenAI error helpers, unsupported endpoint middleware, compatibility headers middleware, Swagger UI, and human-readable support matrix. - - - -@/home/sakib/.claude/get-shit-done/workflows/execute-plan.md -@/home/sakib/.claude/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md -@.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md -@.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md - - - - - -From apps/edge-api/cmd/server/main.go (Plan 01): -```go -// Minimal HTTP server on :8080 -// GET /health -> {"status":"ok"} -// GET /v1/models -> {"object":"list","data":[]} -// This plan will restructure main.go to wire in middleware -``` - -From deploy/docker/docker-compose.yml (Plan 01): -```yaml -# edge-api service on port 8080 with healthcheck -# toolchain service in "tools" profile with ogen + oapi-codegen -``` - - - - - - Task 1: Import OpenAI spec, build support matrix, and create OpenAI error envelope with tests - - packages/openai-contract/upstream/openapi.yaml, - packages/openai-contract/upstream/SPEC_VERSION, - packages/openai-contract/scripts/import-spec.sh, - packages/openai-contract/overlays/hive-support-status.yaml, - packages/openai-contract/matrix/support-matrix.json, - packages/openai-contract/scripts/generate-matrix.sh, - apps/edge-api/internal/errors/openai.go, - apps/edge-api/internal/errors/openai_test.go, - apps/edge-api/internal/matrix/types.go, - apps/edge-api/internal/matrix/loader.go, - apps/edge-api/internal/matrix/loader_test.go, - docs/support-matrix.md - - - .planning/phases/01-contract-compatibility-harness/01-CONTEXT.md, - .planning/phases/01-contract-compatibility-harness/01-RESEARCH.md, - apps/edge-api/go.mod, - apps/edge-api/cmd/server/main.go - - - - Test: WriteError(w, 404, "unsupported_endpoint", "msg", nil) produces JSON {"error":{"message":"msg","type":"unsupported_endpoint","param":null,"code":null}} with status 404 and Content-Type application/json - - Test: WriteError(w, 400, "invalid_request_error", "bad param", ptrStr("invalid_api_key")) includes code field as string - - Test: WriteError sets Content-Type header to "application/json" before writing body - - Test: LoadMatrix parses a support-matrix.json fixture and returns correct status for known endpoints - - Test: Lookup returns "unknown" for paths not in the matrix - - Test: Lookup matches both method and path correctly (GET /v1/models vs POST /v1/chat/completions) - - -1. Create `packages/openai-contract/scripts/import-spec.sh`: - - Download the OpenAI spec from `https://raw.githubusercontent.com/openai/openai-openapi/refs/heads/master/openapi.yaml` - - Save to `packages/openai-contract/upstream/openapi.yaml` - - Record the download date and source URL in `packages/openai-contract/upstream/SPEC_VERSION` as a text file with format: - ``` - source: https://github.com/openai/openai-openapi - branch: master - downloaded: YYYY-MM-DD - ``` - - Make the script executable. - - **Run the script** to actually download the spec. - -2. Create `packages/openai-contract/overlays/hive-support-status.yaml` using OpenAPI Overlay v1.1.0 format. Extract ALL paths from the downloaded spec and classify each endpoint. The classification rules are: - - `supported_now`: Only `/v1/models` GET (the placeholder we have now). Nothing else is implemented yet. - - `planned_for_launch`: Endpoints that map to Phase 2-8 requirements: `/v1/chat/completions` POST, `/v1/completions` POST, `/v1/embeddings` POST, `/v1/responses` POST, `/v1/images/generations` POST, `/v1/images/edits` POST, `/v1/audio/speech` POST, `/v1/audio/transcriptions` POST, `/v1/audio/translations` POST, `/v1/files` GET/POST, `/v1/files/{file_id}` GET/DELETE, `/v1/files/{file_id}/content` GET, `/v1/uploads` POST, `/v1/uploads/{upload_id}/parts` POST, `/v1/uploads/{upload_id}/complete` POST, `/v1/uploads/{upload_id}/cancel` POST, `/v1/batches` GET/POST, `/v1/batches/{batch_id}` GET, `/v1/batches/{batch_id}/cancel` POST, `/v1/models/{model}` GET/DELETE. - - `explicitly_unsupported_at_launch`: All other public non-org/admin endpoints (fine-tuning, assistants, threads, runs, vector stores, realtime, etc.). - - `out_of_scope`: Any endpoint under `/v1/organization/` or admin paths. - Each overlay action should set `x-hive-status` and `x-hive-phase` (integer, the phase where it will be implemented, or null for unsupported). - -3. Create `packages/openai-contract/matrix/support-matrix.json` -- a machine-readable JSON file derived from the overlay classifications. Structure: - ```json - { - "version": "0.1.0", - "generated": "2026-03-28", - "endpoints": [ - { - "method": "GET", - "path": "/v1/models", - "status": "supported_now", - "phase": 1, - "notes": "Lists available models" - }, - { - "method": "POST", - "path": "/v1/chat/completions", - "status": "planned_for_launch", - "phase": 6, - "notes": "Chat completion inference" - } - ] - } - ``` - Include ALL endpoints extracted from the spec. Every public path+method combination must have exactly one entry. - -4. Create `packages/openai-contract/scripts/generate-matrix.sh` that reads the overlay YAML and produces the matrix JSON. This can be a simple script that echoes a reminder to regenerate, since the initial matrix is hand-authored from the spec. - -5. Create `docs/support-matrix.md` -- a human-readable Markdown table generated from the matrix JSON. Columns: Method, Path, Status, Phase, Notes. Group by status (supported_now first, then planned_for_launch, then explicitly_unsupported_at_launch, then out_of_scope). - -6. Create `apps/edge-api/internal/errors/openai.go`: - - Define `OpenAIError` struct with field `Error OpenAIErrorBody` tagged `json:"error"`. - - Define `OpenAIErrorBody` struct with fields: `Message string` (`json:"message"`), `Type string` (`json:"type"`), `Param *string` (`json:"param"`), `Code *string` (`json:"code"`). - - Implement `func WriteError(w http.ResponseWriter, httpStatus int, errType string, message string, code *string)` that sets `Content-Type: application/json`, writes the status code, and JSON-encodes the OpenAIError with Param as nil. - - Implement `func NewError(errType string, message string, code *string) OpenAIError` as a constructor. - -7. Create `apps/edge-api/internal/errors/openai_test.go` with table-driven tests: - - Test 404 unsupported_endpoint error produces correct JSON shape - - Test 400 invalid_request_error with code string - - Test Content-Type header is set to application/json - - Test that param is null and code is null when not provided - - Use `httptest.NewRecorder()` for response capture. - -8. Create `apps/edge-api/internal/matrix/types.go`: - - Define `EndpointStatus` as a string type with constants: `StatusSupportedNow = "supported_now"`, `StatusPlannedForLaunch = "planned_for_launch"`, `StatusExplicitlyUnsupported = "explicitly_unsupported_at_launch"`, `StatusOutOfScope = "out_of_scope"`, `StatusUnknown = "unknown"`. - - Define `MatrixEntry` struct: Method, Path, Status (EndpointStatus), Phase (*int), Notes (string). - - Define `SupportMatrix` struct with a field `Endpoints []MatrixEntry` and a `lookup map[string]EndpointStatus` (unexported, built on load). - - Method `func (m *SupportMatrix) Lookup(method, path string) EndpointStatus` returns the status or `StatusUnknown`. - -9. Create `apps/edge-api/internal/matrix/loader.go`: - - Implement `func LoadMatrix(path string) (*SupportMatrix, error)` that reads the JSON file, unmarshals it, and builds the internal lookup map keyed by `"METHOD /path"`. - - Implement `func LoadMatrixFromBytes(data []byte) (*SupportMatrix, error)` for testing. - -10. Create `apps/edge-api/internal/matrix/loader_test.go` with table-driven tests: - - Test loading a valid matrix fixture returns correct endpoint count - - Test Lookup("GET", "/v1/models") returns StatusSupportedNow - - Test Lookup("POST", "/v1/chat/completions") returns StatusPlannedForLaunch - - Test Lookup("GET", "/v1/unknown") returns StatusUnknown - - Test Lookup matches method correctly (GET vs POST on same path) - - - cd /home/sakib/hive/apps/edge-api && go test ./internal/errors/... ./internal/matrix/... -v -count=1 2>&1 | tail -20 - - - - packages/openai-contract/upstream/openapi.yaml exists and contains `openapi:` - - packages/openai-contract/upstream/SPEC_VERSION exists and contains `source:` - - packages/openai-contract/matrix/support-matrix.json contains `"supported_now"` and `"planned_for_launch"` and `"explicitly_unsupported_at_launch"` - - packages/openai-contract/matrix/support-matrix.json contains entries for `/v1/models`, `/v1/chat/completions`, `/v1/embeddings` - - apps/edge-api/internal/errors/openai.go contains `func WriteError(` and `OpenAIErrorBody` - - apps/edge-api/internal/errors/openai_test.go contains `TestWriteError` or `Test.*Error` - - apps/edge-api/internal/matrix/types.go contains `StatusSupportedNow` and `StatusUnknown` - - apps/edge-api/internal/matrix/loader.go contains `func LoadMatrix(` - - apps/edge-api/internal/matrix/loader_test.go contains `TestLoad` or `Test.*Matrix` - - go test ./internal/errors/... exits 0 - - go test ./internal/matrix/... exits 0 - - docs/support-matrix.md contains table rows with `supported now` and `planned for launch` - - OpenAI spec imported and pinned, support matrix classifies all public endpoints with four statuses, error envelope and matrix loader have passing unit tests, human-readable support matrix doc exists - - - - Task 2: Create unsupported endpoint middleware, compat headers middleware, Swagger handler, and wire into server - - apps/edge-api/internal/middleware/unsupported.go, - apps/edge-api/internal/middleware/unsupported_test.go, - apps/edge-api/internal/middleware/compat_headers.go, - apps/edge-api/internal/middleware/compat_headers_test.go, - apps/edge-api/docs/swagger.go, - apps/edge-api/cmd/server/main.go, - apps/edge-api/go.mod, - apps/edge-api/go.sum, - packages/openai-contract/overlays/hive-support-status.yaml - - - apps/edge-api/internal/errors/openai.go, - apps/edge-api/internal/matrix/types.go, - apps/edge-api/internal/matrix/loader.go, - apps/edge-api/cmd/server/main.go, - packages/openai-contract/matrix/support-matrix.json, - .planning/phases/01-contract-compatibility-harness/01-CONTEXT.md - - - - Test: Request to supported_now endpoint passes through to next handler - - Test: Request to planned_for_launch endpoint returns 404 with {"error":{"type":"unsupported_endpoint",...}} - - Test: Request to explicitly_unsupported_at_launch endpoint returns 404 with {"error":{"type":"unsupported_endpoint",...}} - - Test: Request to unknown path (not in matrix at all) returns 404 with {"error":{"type":"unsupported_endpoint",...}} - - Test: Error message for unsupported endpoint is customer-clear and provider-blind (contains endpoint path, does NOT contain "provider" or "upstream") - - Test: CompatHeaders adds x-request-id header (non-empty UUID-like string) - - Test: CompatHeaders adds openai-version header with value "2020-10-01" - - Test: CompatHeaders adds openai-processing-ms header with numeric value >= 0 - - -1. Create `apps/edge-api/internal/middleware/unsupported.go`: - - Import the matrix and errors packages from this module. - - Implement `func UnsupportedEndpointMiddleware(m *matrix.SupportMatrix) func(http.Handler) http.Handler`. - - The middleware checks `m.Lookup(r.Method, r.URL.Path)`. - - If status is `StatusSupportedNow`, call `next.ServeHTTP(w, r)`. - - If status is `StatusPlannedForLaunch`, return 404 with error type `"unsupported_endpoint"` and message `"The endpoint %s %s is planned but not yet available. Check the Hive support matrix for current status."` (formatted with method and path). Code pointer to string `"endpoint_not_available"`. - - If status is `StatusExplicitlyUnsupported`, return 404 with error type `"unsupported_endpoint"` and message `"The endpoint %s %s is not supported. This endpoint is outside the current Hive launch scope."`. Code pointer to string `"endpoint_unsupported"`. - - If status is `StatusOutOfScope`, return 404 with error type `"unsupported_endpoint"` and message `"The endpoint %s %s is not part of the Hive API."`. Code pointer to string `"endpoint_out_of_scope"`. - - If status is `StatusUnknown`, return 404 with error type `"invalid_request_error"` and message `"Unknown endpoint: %s %s"`. Code pointer to string `"unknown_endpoint"`. - - CRITICAL: Error messages must NEVER mention "provider", "upstream", "OpenAI", "routing", or any internal details. They are customer-clear and provider-blind. - -2. Create `apps/edge-api/internal/middleware/unsupported_test.go`: - - Build a small in-memory SupportMatrix fixture with 4 endpoints covering all statuses. - - Table-driven tests verifying each status returns the correct HTTP code and error type. - - Verify `supported_now` endpoints pass through to the next handler. - - Verify error messages do NOT contain "provider", "upstream", or "OpenAI". - - Verify error JSON shape matches `{"error":{"message":"...","type":"...","param":null,"code":"..."}}`. - -3. Create `apps/edge-api/internal/middleware/compat_headers.go`: - - Implement `func CompatHeaders() func(http.Handler) http.Handler`. - - Generate a UUID v4 for `x-request-id` using `crypto/rand` (do NOT add a uuid library dependency -- implement a simple UUID v4 with `crypto/rand` and `fmt.Sprintf`). - - Set `openai-version` to `"2020-10-01"` on every response. - - Use a `responseRecorder` wrapper (unexported struct embedding `http.ResponseWriter`) to capture timing. - - After `next.ServeHTTP`, set `openai-processing-ms` to the elapsed milliseconds as a string. - - IMPORTANT: Set `x-request-id` and `openai-version` BEFORE calling next (so they appear even on error responses). Set `openai-processing-ms` AFTER (needs elapsed time). Use the `responseRecorder` to buffer headers so `openai-processing-ms` can be added before the actual write. Alternatively, set `x-request-id` and `openai-version` before calling next, and accept that `openai-processing-ms` will be set after the response status is written (this is acceptable since Go allows setting headers after WriteHeader for trailers, but for simplicity just set all headers before calling next, using 0 for processing-ms initially and patching it -- OR use the simpler approach: set `x-request-id` and `openai-version` before, and use a wrapper that intercepts `WriteHeader` to inject `openai-processing-ms`). - -4. Create `apps/edge-api/internal/middleware/compat_headers_test.go`: - - Test that x-request-id is present and non-empty on response. - - Test that openai-version is "2020-10-01". - - Test that openai-processing-ms is present and is a non-negative integer string. - - Test that headers appear on both success and error responses. - -5. Create `apps/edge-api/docs/swagger.go`: - - Use `embed` to embed the OpenAPI spec file. The embed path should reference `../../packages/openai-contract/upstream/openapi.yaml` relative to the edge-api module. NOTE: Go embed only works for files within the module directory. Since the spec is outside the module, use an alternative approach: - - Instead of embed, implement `func SwaggerHandler(specPath string) http.Handler` that: - a. Serves the spec file at `/docs/openapi.yaml` by reading from the file system path. - b. Serves a minimal HTML page at `/docs/` that loads Swagger UI from CDN (`https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/`) and points it at `./openapi.yaml`. - - Add dependency `github.com/swaggest/swgui` to go.mod if needed, OR use the simpler CDN approach (preferred to avoid adding a dependency for Phase 1). - - The CDN approach: serve an HTML page with `
` and scripts from `swagger-ui-dist@5`. - -6. Update `apps/edge-api/cmd/server/main.go` to wire everything together: - - Import the `internal/errors`, `internal/matrix`, `internal/middleware`, and `docs` packages. - - On startup, load the support matrix from a path specified by env var `SUPPORT_MATRIX_PATH`, defaulting to `/app/packages/openai-contract/matrix/support-matrix.json`. - - Load the spec path from env var `OPENAPI_SPEC_PATH`, defaulting to `/app/packages/openai-contract/upstream/openapi.yaml`. - - Register route handlers: `/health`, `/v1/models`, `/docs/` (SwaggerHandler). - - Wrap the mux with middleware in order (outermost first): `CompatHeaders()`, then `UnsupportedEndpointMiddleware(matrix)`. - - The unsupported middleware should only apply to `/v1/` paths (so `/health` and `/docs/` are not blocked). Implement this by either: having the middleware skip non-`/v1/` paths, or by creating two muxes (one for v1 with middleware, one for infra without). - - Log startup message with port and matrix entry count. - -7. Update `apps/edge-api/go.mod` with any new dependencies (likely none if using standard library + CDN Swagger). -
- - cd /home/sakib/hive/apps/edge-api && go test ./internal/middleware/... -v -count=1 2>&1 | tail -20 && go build ./cmd/server/ && echo "BUILD_OK" - - - - apps/edge-api/internal/middleware/unsupported.go contains `func UnsupportedEndpointMiddleware(` and `unsupported_endpoint` - - apps/edge-api/internal/middleware/unsupported.go does NOT contain the strings "provider" or "upstream" or "OpenAI" in any error message - - apps/edge-api/internal/middleware/unsupported_test.go contains at least 4 test cases covering all statuses - - apps/edge-api/internal/middleware/compat_headers.go contains `x-request-id` and `openai-version` and `openai-processing-ms` - - apps/edge-api/internal/middleware/compat_headers_test.go contains tests for all three headers - - apps/edge-api/docs/swagger.go contains `swagger-ui` and `openapi.yaml` - - apps/edge-api/cmd/server/main.go contains `UnsupportedEndpointMiddleware` and `CompatHeaders` and `SwaggerHandler` - - apps/edge-api/cmd/server/main.go contains `SUPPORT_MATRIX_PATH` - - go test ./internal/middleware/... exits 0 - - go build ./cmd/server/ exits 0 - - Unsupported endpoint middleware rejects non-supported requests with provider-blind OpenAI-style errors, compat headers middleware adds x-request-id/openai-version/openai-processing-ms to all responses, Swagger UI serves at /docs/, server main.go wires everything together and loads matrix at startup, all middleware tests pass -
- -
- - -- `go test ./internal/errors/... ./internal/matrix/... ./internal/middleware/... -v` all pass -- `go build ./cmd/server/` compiles without errors -- `docker compose -f deploy/docker/docker-compose.yml build edge-api` succeeds -- `curl http://localhost:8080/v1/models` returns 200 (supported_now) -- `curl http://localhost:8080/v1/chat/completions` returns 404 with `{"error":{"type":"unsupported_endpoint",...}}` -- `curl -I http://localhost:8080/v1/models` includes `x-request-id`, `openai-version`, `openai-processing-ms` headers -- `curl http://localhost:8080/docs/` returns HTML containing `swagger-ui` -- support-matrix.json contains entries for all public non-org/admin endpoints with correct statuses - - - -- Every public non-org/admin OpenAI endpoint is classified in the support matrix with one of four statuses -- The runtime middleware enforces the matrix: only supported_now endpoints pass through -- Error responses use the exact OpenAI JSON envelope shape that SDKs expect to parse -- All error messages are customer-clear and provider-blind -- Swagger UI renders the OpenAI spec at /docs/ -- docs/support-matrix.md provides a human-readable endpoint reference table -- All Go unit tests pass with go test - - - -After completion, create `.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md` - diff --git a/.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md b/.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md deleted file mode 100644 index b60c08a5c..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md +++ /dev/null @@ -1,156 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 02 -subsystem: api -tags: [openai, openapi, swagger, middleware, error-handling, support-matrix] - -# Dependency graph -requires: - - phase: 01-contract-compatibility-harness (plan 01) - provides: "Go module, Docker dev env, health/models endpoints, go.work" -provides: - - "Pinned OpenAI OpenAPI spec (openapi.yaml)" - - "Support matrix JSON classifying 148 endpoints into four statuses" - - "OpenAI error envelope (WriteError, NewError, OpenAIError types)" - - "Matrix loader with Lookup by method+path" - - "UnsupportedEndpointMiddleware rejecting non-supported /v1/ endpoints" - - "CompatHeaders middleware (x-request-id, openai-version, openai-processing-ms)" - - "Swagger UI at /docs/ serving the OpenAI spec" - - "Human-readable support-matrix.md documentation" -affects: [02-auth, 03-billing, 04-provider-abstraction, 06-inference-surface] - -# Tech tracking -tech-stack: - added: [swagger-ui-dist CDN v5, OpenAI OpenAPI spec] - patterns: [openai-error-envelope, support-matrix-driven-middleware, compat-headers, provider-blind-errors] - -key-files: - created: - - packages/openai-contract/upstream/openapi.yaml - - packages/openai-contract/matrix/support-matrix.json - - packages/openai-contract/overlays/hive-support-status.yaml - - packages/openai-contract/scripts/import-spec.sh - - packages/openai-contract/scripts/generate-matrix.sh - - apps/edge-api/internal/errors/openai.go - - apps/edge-api/internal/errors/openai_test.go - - apps/edge-api/internal/matrix/types.go - - apps/edge-api/internal/matrix/loader.go - - apps/edge-api/internal/matrix/loader_test.go - - apps/edge-api/internal/middleware/unsupported.go - - apps/edge-api/internal/middleware/unsupported_test.go - - apps/edge-api/internal/middleware/compat_headers.go - - apps/edge-api/internal/middleware/compat_headers_test.go - - apps/edge-api/docs/swagger.go - - docs/support-matrix.md - modified: - - apps/edge-api/cmd/server/main.go - -key-decisions: - - "Used manual_spec branch for OpenAI spec (master branch returns 404)" - - "UUID v4 via crypto/rand instead of adding uuid dependency" - - "Swagger UI loaded from CDN instead of Go embed (spec outside module boundary)" - - "responseRecorder wrapper intercepts WriteHeader to inject openai-processing-ms timing" - -patterns-established: - - "OpenAI error envelope: all API errors use {error:{message,type,param,code}} JSON shape" - - "Provider-blind messaging: error messages never mention provider, upstream, or OpenAI" - - "Support matrix as single source of truth: runtime middleware and docs both derive from support-matrix.json" - - "Middleware chain order: CompatHeaders (outermost) -> UnsupportedEndpoint (inner) -> handler" - -requirements-completed: [COMP-02, COMP-03, API-08] - -# Metrics -duration: 6min -completed: 2026-03-28 ---- - -# Phase 01 Plan 02: Contract & Compatibility Layer Summary - -**OpenAI contract imported with 148-endpoint support matrix driving runtime middleware, error envelope, compat headers, and Swagger UI** - -## Performance - -- **Duration:** 6 min -- **Started:** 2026-03-28T07:33:15Z -- **Completed:** 2026-03-28T07:39:00Z -- **Tasks:** 2 -- **Files modified:** 17 - -## Accomplishments -- Imported and pinned the full OpenAI OpenAPI spec from the manual_spec branch (148 endpoints) -- Classified all endpoints into four statuses: 1 supported_now, 24 planned_for_launch, 72 explicitly_unsupported, 51 out_of_scope -- Built OpenAI error envelope (WriteError/NewError) producing exact `{error:{message,type,param,code}}` JSON shape -- Created matrix loader with Lookup by method+path, used by runtime middleware -- UnsupportedEndpointMiddleware rejects non-supported /v1/ endpoints with provider-blind error messages -- CompatHeaders middleware adds x-request-id (UUID v4), openai-version, openai-processing-ms to every response -- Swagger UI serves at /docs/ loading swagger-ui-dist@5 from CDN -- Human-readable support-matrix.md groups all 148 endpoints by status - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Import OpenAI spec, build support matrix, and create error envelope** - `b1a5d12` (feat) -2. **Task 2: Create middleware, Swagger handler, and wire server** - `6f38f1b` (feat) - -## Files Created/Modified -- `packages/openai-contract/upstream/openapi.yaml` - Pinned copy of OpenAI OpenAPI spec -- `packages/openai-contract/upstream/SPEC_VERSION` - Spec version metadata -- `packages/openai-contract/matrix/support-matrix.json` - Machine-readable endpoint classification -- `packages/openai-contract/overlays/hive-support-status.yaml` - OpenAPI Overlay with x-hive-status per endpoint -- `packages/openai-contract/scripts/import-spec.sh` - Spec download script -- `packages/openai-contract/scripts/generate-matrix.sh` - Matrix regeneration reminder script -- `apps/edge-api/internal/errors/openai.go` - OpenAI error envelope builder (WriteError, NewError) -- `apps/edge-api/internal/errors/openai_test.go` - Error envelope table-driven tests -- `apps/edge-api/internal/matrix/types.go` - EndpointStatus type, MatrixEntry, SupportMatrix with Lookup -- `apps/edge-api/internal/matrix/loader.go` - LoadMatrix and LoadMatrixFromBytes -- `apps/edge-api/internal/matrix/loader_test.go` - Matrix loader and lookup tests -- `apps/edge-api/internal/middleware/unsupported.go` - Matrix-driven unsupported endpoint rejection -- `apps/edge-api/internal/middleware/unsupported_test.go` - Middleware tests covering all statuses + provider-blind check -- `apps/edge-api/internal/middleware/compat_headers.go` - OpenAI compatibility response headers -- `apps/edge-api/internal/middleware/compat_headers_test.go` - Header presence, uniqueness, and error response tests -- `apps/edge-api/docs/swagger.go` - Swagger UI handler serving spec from disk -- `apps/edge-api/cmd/server/main.go` - Wired middleware chain, matrix loading, and Swagger route -- `docs/support-matrix.md` - Human-readable endpoint reference table - -## Decisions Made -- Used `manual_spec` branch for OpenAI spec download (the `master` and `main` branches return 404) -- Implemented UUID v4 with `crypto/rand` + `fmt.Sprintf` to avoid adding a uuid library dependency -- Used CDN-loaded Swagger UI (swagger-ui-dist@5) instead of Go embed since the spec lives outside the Go module boundary -- Used a responseRecorder wrapper to intercept WriteHeader and inject openai-processing-ms timing header - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Fixed OpenAI spec download URL** -- **Found during:** Task 1 (Import spec) -- **Issue:** The spec URL using `refs/heads/master` returned 404. The research doc noted the spec is on the `manual_spec` branch. -- **Fix:** Changed import-spec.sh to use `refs/heads/manual_spec` branch URL -- **Files modified:** packages/openai-contract/scripts/import-spec.sh -- **Verification:** Script downloads successfully, openapi.yaml contains `openapi: 3.0.0` -- **Committed in:** b1a5d12 (Task 1 commit) - ---- - -**Total deviations:** 1 auto-fixed (1 blocking) -**Impact on plan:** Necessary URL correction. No scope creep. - -## Issues Encountered -- Go is not installed on the host (Docker-only dev workflow); all tests and builds run via `docker run golang:1.24-alpine` - -## User Setup Required -None - no external service configuration required. - -## Next Phase Readiness -- Contract layer complete: support matrix, error envelope, and middleware are ready for all future endpoint implementations -- Plan 03 (SDK compatibility harness) can now test against the middleware and error responses -- Phase 2+ endpoint implementations will update support-matrix.json to move endpoints from planned_for_launch to supported_now - -## Self-Check: PASSED - -All 14 key files verified present. Both task commits (b1a5d12, 6f38f1b) verified in git log. - ---- -*Phase: 01-contract-compatibility-harness* -*Completed: 2026-03-28* diff --git a/.planning/phases/01-contract-compatibility-harness/01-03-PLAN.md b/.planning/phases/01-contract-compatibility-harness/01-03-PLAN.md deleted file mode 100644 index 29d608534..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-03-PLAN.md +++ /dev/null @@ -1,413 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 03 -type: execute -wave: 3 -depends_on: - - 01-01 - - 01-02 -files_modified: - - packages/sdk-tests/js/tests/health/health.test.ts - - packages/sdk-tests/js/tests/models/list-models.test.ts - - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts - - packages/sdk-tests/js/tests/errors/error-shape.test.ts - - packages/sdk-tests/js/tests/headers/compat-headers.test.ts - - packages/sdk-tests/js/tests/streaming/streaming-error.test.ts - - packages/sdk-tests/python/tests/__init__.py - - packages/sdk-tests/python/tests/conftest.py - - packages/sdk-tests/python/tests/test_health.py - - packages/sdk-tests/python/tests/test_models.py - - packages/sdk-tests/python/tests/test_unsupported.py - - packages/sdk-tests/python/tests/test_error_shape.py - - packages/sdk-tests/python/tests/test_headers.py - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HealthTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ModelsTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ErrorShapeTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HeadersTest.java - - packages/sdk-tests/fixtures/golden/models-list.json - - packages/sdk-tests/fixtures/golden/error-unsupported.json - - packages/sdk-tests/fixtures/golden/error-unknown.json -autonomous: false -requirements: - - COMP-01 - - COMP-02 -must_haves: - truths: - - "Official OpenAI Node SDK pointed at Hive can list models and receives a valid response" - - "Official OpenAI Python SDK pointed at Hive can list models and receives a valid response" - - "Official OpenAI Java SDK pointed at Hive can list models and receives a valid response" - - "All three SDKs receive OpenAI-style error objects when calling unsupported endpoints" - - "All three SDKs see x-request-id in response headers" - - "Error responses from unsupported endpoints are parsed correctly by SDK error classes" - - "Golden fixtures capture the expected response shapes for regression testing" - artifacts: - - path: "packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts" - provides: "JS SDK test proving unsupported endpoints return OpenAI-style errors" - contains: "NotFoundError" - - path: "packages/sdk-tests/python/tests/test_unsupported.py" - provides: "Python SDK test proving unsupported endpoints return OpenAI-style errors" - contains: "NotFoundError" - - path: "packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java" - provides: "Java SDK test proving unsupported endpoints return OpenAI-style errors" - contains: "404" - - path: "packages/sdk-tests/fixtures/golden/models-list.json" - provides: "Golden fixture for /v1/models response shape" - contains: "object" - - path: "packages/sdk-tests/fixtures/golden/error-unsupported.json" - provides: "Golden fixture for unsupported endpoint error shape" - contains: "unsupported_endpoint" - key_links: - - from: "packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts" - to: "apps/edge-api (via HTTP)" - via: "OpenAI SDK with baseURL override to http://edge-api:8080/v1" - pattern: "HIVE_BASE_URL" - - from: "packages/sdk-tests/python/tests/conftest.py" - to: "apps/edge-api (via HTTP)" - via: "OpenAI SDK with base_url override" - pattern: "HIVE_BASE_URL" - - from: "packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java" - to: "apps/edge-api (via HTTP)" - via: "OpenAI SDK with baseUrl override" - pattern: "HIVE_BASE_URL" ---- - - -Build the SDK compatibility harness that proves official OpenAI JavaScript, Python, and Java SDKs work against Hive for supported endpoints and receive correct errors for unsupported ones. - -Purpose: This is the proof that Hive is a genuine compatibility product. Without these tests, any future endpoint implementation could silently break SDK compatibility. A failing harness blocks Hive from claiming an endpoint as supported. -Output: Comprehensive SDK test suites in JS (Vitest), Python (pytest), and Java (JUnit), plus golden response fixtures for regression testing. - - - -@/home/sakib/.claude/get-shit-done/workflows/execute-plan.md -@/home/sakib/.claude/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md -@.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md -@.planning/phases/01-contract-compatibility-harness/01-01-SUMMARY.md -@.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md - - - - - -From apps/edge-api/internal/errors/openai.go: -```go -// Error response shape: -// {"error":{"message":"...","type":"...","param":null,"code":"..."}} -// HTTP status codes: 404 for unsupported, 400 for invalid -``` - -From apps/edge-api/internal/middleware/compat_headers.go: -```go -// Response headers on every request: -// x-request-id: UUID v4 -// openai-version: "2020-10-01" -// openai-processing-ms: "N" (milliseconds as string) -``` - -From apps/edge-api/internal/middleware/unsupported.go: -```go -// Unsupported endpoints return 404 with: -// type: "unsupported_endpoint" -// code: "endpoint_not_available" (planned) or "endpoint_unsupported" (explicit) -``` - -From apps/edge-api/cmd/server/main.go: -```go -// Supported endpoints: -// GET /v1/models -> {"object":"list","data":[]} -// All other /v1/* -> 404 unsupported error -// GET /health -> {"status":"ok"} -// GET /docs/ -> Swagger UI -``` - -From deploy/docker/docker-compose.yml: -```yaml -# SDK test services connect to edge-api via: -# HIVE_BASE_URL=http://edge-api:8080/v1 -# Services: sdk-tests-js, sdk-tests-py, sdk-tests-java -``` - - - - - - Task 1: Create JS SDK compatibility tests with golden fixtures - - packages/sdk-tests/js/tests/health/health.test.ts, - packages/sdk-tests/js/tests/models/list-models.test.ts, - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts, - packages/sdk-tests/js/tests/errors/error-shape.test.ts, - packages/sdk-tests/js/tests/headers/compat-headers.test.ts, - packages/sdk-tests/js/tests/streaming/streaming-error.test.ts, - packages/sdk-tests/fixtures/golden/models-list.json, - packages/sdk-tests/fixtures/golden/error-unsupported.json, - packages/sdk-tests/fixtures/golden/error-unknown.json - - - packages/sdk-tests/js/package.json, - packages/sdk-tests/js/vitest.config.ts, - apps/edge-api/internal/errors/openai.go, - apps/edge-api/internal/middleware/unsupported.go, - apps/edge-api/internal/middleware/compat_headers.go, - packages/openai-contract/matrix/support-matrix.json - - -1. Create golden fixture files: - - `packages/sdk-tests/fixtures/golden/models-list.json`: - ```json - {"object": "list", "data": []} - ``` - - `packages/sdk-tests/fixtures/golden/error-unsupported.json`: - ```json - {"error": {"message": "The endpoint POST /v1/chat/completions is planned but not yet available. Check the Hive support matrix for current status.", "type": "unsupported_endpoint", "param": null, "code": "endpoint_not_available"}} - ``` - - `packages/sdk-tests/fixtures/golden/error-unknown.json`: - ```json - {"error": {"message": "Unknown endpoint: GET /v1/nonexistent", "type": "invalid_request_error", "param": null, "code": "unknown_endpoint"}} - ``` - -2. Create `packages/sdk-tests/js/tests/health/health.test.ts`: - - Use raw `fetch` (not OpenAI SDK) to GET `${HIVE_BASE_URL}/../health` (strip /v1 suffix). - - Assert status 200. - - Assert body contains `{"status":"ok"}`. - -3. Create `packages/sdk-tests/js/tests/models/list-models.test.ts`: - - Import `OpenAI` from `"openai"`. - - Create client: `new OpenAI({ baseURL: process.env.HIVE_BASE_URL ?? "http://localhost:8080/v1", apiKey: "test-key" })`. - - Call `client.models.list()`. - - Assert response has `object === "list"` and `data` is an array. - - Compare response shape against golden fixture `models-list.json`. - -4. Create `packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts`: - - Import `OpenAI` from `"openai"`. - - Create client with same base URL pattern. - - Test: Call `client.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: "hello" }] })`. - - Assert throws `OpenAI.NotFoundError`. - - Assert `err.status === 404`. - - Assert `err.error?.error?.type === "unsupported_endpoint"`. - - Assert `err.error?.error?.message` contains "planned but not yet available". - - Assert `err.error?.error?.message` does NOT contain "provider" or "upstream" or "OpenAI". - - Test: Call `client.fineTuning.jobs.create({ model: "gpt-4o", training_file: "file-abc123" })`. - - Assert throws `OpenAI.NotFoundError`. - - Assert `err.error?.error?.type === "unsupported_endpoint"`. - - Assert `err.error?.error?.code === "endpoint_unsupported"`. - -5. Create `packages/sdk-tests/js/tests/errors/error-shape.test.ts`: - - Use raw `fetch` to call an unsupported endpoint. - - Assert response JSON has exactly the shape: `{error: {message: string, type: string, param: null, code: string}}`. - - Assert `Content-Type` header is `application/json`. - -6. Create `packages/sdk-tests/js/tests/headers/compat-headers.test.ts`: - - Use raw `fetch` to GET `/v1/models`. - - Assert response headers include `x-request-id` (non-empty string). - - Assert response headers include `openai-version` with value `"2020-10-01"`. - - Assert response headers include `openai-processing-ms` with a numeric string value. - - Repeat for an unsupported endpoint to verify headers appear on error responses too. - -7. Create `packages/sdk-tests/js/tests/streaming/streaming-error.test.ts`: - - Use raw `fetch` to POST `/v1/chat/completions` with `stream: true` in body. - - Assert the response is NOT a streaming response (since endpoint is unsupported, it should return a normal JSON error). - - Assert status 404 with unsupported_endpoint error shape. - - This proves that streaming requests to unsupported endpoints fail explicitly rather than hanging. - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml build edge-api sdk-tests-js && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 8 && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-js 2>&1 | tail -30; docker compose -f deploy/docker/docker-compose.yml down - - - - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts contains `NotFoundError` and `unsupported_endpoint` - - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts asserts message does NOT contain "provider" or "upstream" - - packages/sdk-tests/js/tests/models/list-models.test.ts contains `client.models.list()` - - packages/sdk-tests/js/tests/headers/compat-headers.test.ts contains `x-request-id` and `openai-version` and `openai-processing-ms` - - packages/sdk-tests/js/tests/streaming/streaming-error.test.ts contains `stream` and `unsupported_endpoint` - - packages/sdk-tests/fixtures/golden/models-list.json contains `"object"` and `"list"` - - packages/sdk-tests/fixtures/golden/error-unsupported.json contains `"unsupported_endpoint"` - - All JS SDK tests pass when run against the edge-api container - - JS SDK compatibility tests pass: models.list works, unsupported endpoints throw NotFoundError with correct error shape, compat headers present on all responses, streaming requests to unsupported endpoints fail explicitly, golden fixtures established for regression - - - - Task 2: Create Python and Java SDK compatibility tests - - packages/sdk-tests/python/tests/__init__.py, - packages/sdk-tests/python/tests/conftest.py, - packages/sdk-tests/python/tests/test_health.py, - packages/sdk-tests/python/tests/test_models.py, - packages/sdk-tests/python/tests/test_unsupported.py, - packages/sdk-tests/python/tests/test_error_shape.py, - packages/sdk-tests/python/tests/test_headers.py, - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HealthTest.java, - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ModelsTest.java, - packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java, - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ErrorShapeTest.java, - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HeadersTest.java - - - packages/sdk-tests/python/pyproject.toml, - packages/sdk-tests/java/build.gradle, - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts, - packages/sdk-tests/js/tests/models/list-models.test.ts, - packages/sdk-tests/js/tests/headers/compat-headers.test.ts, - apps/edge-api/internal/errors/openai.go, - apps/edge-api/internal/middleware/unsupported.go - - -**Python SDK Tests:** - -1. Create `packages/sdk-tests/python/tests/__init__.py` (empty file). - -2. Create `packages/sdk-tests/python/tests/conftest.py`: - ```python - import os - import pytest - from openai import OpenAI - - @pytest.fixture - def client(): - base_url = os.environ.get("HIVE_BASE_URL", "http://localhost:8080/v1") - return OpenAI(base_url=base_url, api_key="test-key") - - @pytest.fixture - def base_url(): - return os.environ.get("HIVE_BASE_URL", "http://localhost:8080/v1") - ``` - -3. Create `packages/sdk-tests/python/tests/test_health.py`: - - Use `httpx` (bundled with openai SDK) to GET `{base_url}/../health`. - - Assert status 200 and body contains `{"status": "ok"}`. - -4. Create `packages/sdk-tests/python/tests/test_models.py`: - - Use `client.models.list()`. - - Assert response has `object == "list"` and `data` is a list. - -5. Create `packages/sdk-tests/python/tests/test_unsupported.py`: - - Test chat completions: `client.chat.completions.create(model="gpt-4o", messages=[{"role":"user","content":"hello"}])`. - - Assert raises `openai.NotFoundError`. - - Assert `err.status_code == 404`. - - Assert error body contains `type` of `"unsupported_endpoint"`. - - Assert error message does NOT contain "provider" or "upstream". - - Test fine-tuning: `client.fine_tuning.jobs.create(model="gpt-4o", training_file="file-abc123")`. - - Assert raises `openai.NotFoundError` with code `"endpoint_unsupported"`. - -6. Create `packages/sdk-tests/python/tests/test_error_shape.py`: - - Use `httpx.post` to call an unsupported endpoint raw. - - Assert JSON has shape `{"error": {"message": str, "type": str, "param": None, "code": str}}`. - - Assert Content-Type is `application/json`. - -7. Create `packages/sdk-tests/python/tests/test_headers.py`: - - Use `httpx.get` to call `/v1/models`. - - Assert `x-request-id` header is present and non-empty. - - Assert `openai-version` header equals `"2020-10-01"`. - - Assert `openai-processing-ms` header is present and is a non-negative integer when parsed. - -**Java SDK Tests:** - -8. Create `packages/sdk-tests/java/src/test/java/com/hive/sdktests/HealthTest.java`: - - Use `java.net.http.HttpClient` to GET `/health`. - - Assert status 200 and body contains `"status":"ok"`. - -9. Create `packages/sdk-tests/java/src/test/java/com/hive/sdktests/ModelsTest.java`: - - Create OpenAI client with `baseUrl` from `HIVE_BASE_URL` env var (default `http://localhost:8080/v1`). - - Call `client.models().list()`. - - Assert response has data (may be empty list). - - NOTE: Check the openai-java SDK v4.30.0 API for the exact client construction and method names. The Java SDK uses a builder pattern: `OpenAIClient client = OpenAIOkHttpClient.builder().baseUrl(baseUrl).apiKey("test-key").build()`. Adjust based on the actual SDK API. - -10. Create `packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java`: - - Call `client.chat().completions().create(...)` and assert it throws an exception with status 404. - - Verify the error body contains `"unsupported_endpoint"`. - - NOTE: The Java SDK throws `OpenAIServiceException` or similar for HTTP errors. Check the actual exception class name in the SDK. - -11. Create `packages/sdk-tests/java/src/test/java/com/hive/sdktests/ErrorShapeTest.java`: - - Use `java.net.http.HttpClient` to raw-call an unsupported endpoint. - - Parse response JSON and assert the error shape matches the OpenAI envelope. - -12. Create `packages/sdk-tests/java/src/test/java/com/hive/sdktests/HeadersTest.java`: - - Use `java.net.http.HttpClient` to GET `/v1/models`. - - Assert `x-request-id`, `openai-version`, and `openai-processing-ms` headers are present. - -**IMPORTANT for Java tests:** The openai-java SDK v4.30.0 may have a different API surface than assumed. Before writing tests, use Context7 or read the SDK source to confirm: -- Client builder class name and method -- How to override base URL -- Exception class names for HTTP errors -- Method names for models.list() and chat.completions.create() - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 8 && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-py 2>&1 | tail -20 && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-java 2>&1 | tail -20; docker compose -f deploy/docker/docker-compose.yml down - - - - packages/sdk-tests/python/tests/conftest.py contains `HIVE_BASE_URL` and `OpenAI(base_url=` - - packages/sdk-tests/python/tests/test_unsupported.py contains `NotFoundError` and `unsupported_endpoint` - - packages/sdk-tests/python/tests/test_unsupported.py asserts message does NOT contain "provider" - - packages/sdk-tests/python/tests/test_headers.py contains `x-request-id` and `openai-version` - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java contains `404` and `unsupported_endpoint` - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HeadersTest.java contains `x-request-id` - - All Python SDK tests pass when run against the edge-api container - - All Java SDK tests pass when run against the edge-api container - - Python and Java SDK tests pass with same coverage as JS: models listing works, unsupported endpoints throw correct SDK error types, error shapes match OpenAI envelope, compat headers present, error messages are provider-blind - - - - Task 3: Verify full SDK compatibility harness, Swagger UI, and support matrix - packages/sdk-tests/js/tests, packages/sdk-tests/python/tests, packages/sdk-tests/java/src/test - -Human verifies the complete SDK compatibility harness works end-to-end: -- All JS/Python/Java SDK test suites pass against the running edge-api -- Swagger UI loads and renders the OpenAI spec at http://localhost:8080/docs/ -- Support matrix in docs/support-matrix.md lists endpoints with correct statuses -- Unsupported endpoints return provider-blind OpenAI-style errors -- Compatibility headers (x-request-id, openai-version, openai-processing-ms) appear on all responses - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 10 && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-js && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-py && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-java && docker compose -f deploy/docker/docker-compose.yml run --rm edge-api go test ./... -v && curl -sf http://localhost:8080/docs/ | grep -q swagger-ui && docker compose -f deploy/docker/docker-compose.yml down && echo PASS - - All SDK test suites pass, Swagger UI renders, support matrix is complete, human has approved the harness - Complete SDK compatibility harness across JS, Python, and Java SDKs proving Hive returns correct responses for supported endpoints and proper OpenAI-style errors for unsupported ones. Swagger UI serving the OpenAI spec. Support matrix classifying all public endpoints. - -1. Run full test suite: `cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 10` -2. Verify JS tests: `docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-js` -3. Verify Python tests: `docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-py` -4. Verify Java tests: `docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-java` -5. Verify Go unit tests: `docker compose -f deploy/docker/docker-compose.yml run --rm edge-api go test ./... -v` -6. Open Swagger UI: Visit http://localhost:8080/docs/ in browser -- verify it loads and shows endpoint documentation. -7. Check support matrix: Review docs/support-matrix.md -- verify it lists endpoints with correct statuses. -8. Test unsupported endpoint manually: `curl -s http://localhost:8080/v1/chat/completions -X POST -H "Content-Type: application/json" -d '{}' | jq .` -9. Verify headers: `curl -I http://localhost:8080/v1/models` -- check for x-request-id, openai-version, openai-processing-ms. -10. Clean up: `docker compose -f deploy/docker/docker-compose.yml down` - - Type "approved" if all tests pass and Swagger UI renders, or describe issues - - - - - -- `docker compose run --rm sdk-tests-js` all tests pass (models, unsupported, errors, headers, streaming-error) -- `docker compose run --rm sdk-tests-py` all tests pass (health, models, unsupported, error_shape, headers) -- `docker compose run --rm sdk-tests-java` all tests pass (health, models, unsupported, error_shape, headers) -- `docker compose run --rm edge-api go test ./...` all Go unit tests pass -- Golden fixtures match actual response shapes -- SDK error classes (NotFoundError in JS/Python, service exception in Java) are triggered by unsupported endpoints -- All error messages are provider-blind (no "provider", "upstream", "OpenAI" in error text) - - - -- Official OpenAI JS SDK v6.33.0 works against Hive for /v1/models -- Official OpenAI Python SDK v2.30.0 works against Hive for /v1/models -- Official OpenAI Java SDK v4.30.0 works against Hive for /v1/models -- All three SDKs correctly parse error responses from unsupported endpoints -- Compatibility headers appear on every response -- Golden fixtures capture expected response shapes for regression -- A failing compatibility test blocks claiming an endpoint as supported - - - -After completion, create `.planning/phases/01-contract-compatibility-harness/01-03-SUMMARY.md` - diff --git a/.planning/phases/01-contract-compatibility-harness/01-03-SUMMARY.md b/.planning/phases/01-contract-compatibility-harness/01-03-SUMMARY.md deleted file mode 100644 index 129d9ba7e..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-03-SUMMARY.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 03 -subsystem: testing -tags: [openai-sdk, vitest, pytest, junit, compatibility, golden-fixtures, sdk-tests] - -# Dependency graph -requires: - - phase: 01-contract-compatibility-harness (plan 01) - provides: "Docker dev stack, SDK test scaffolds (JS/Python/Java), edge-api server" - - phase: 01-contract-compatibility-harness (plan 02) - provides: "OpenAI error envelope, unsupported endpoint middleware, compat headers, support matrix" -provides: - - "JS SDK compatibility tests (health, models, unsupported, error shape, headers, streaming error)" - - "Python SDK compatibility tests (health, models, unsupported, error shape, headers)" - - "Java SDK compatibility tests (health, models, unsupported, error shape, headers)" - - "Golden response fixtures for regression testing (models-list, error-unsupported, error-unknown)" -affects: [02-auth, 06-inference-surface] - -# Tech tracking -tech-stack: - added: [] - patterns: [sdk-compatibility-harness, golden-fixture-regression, provider-blind-error-assertions] - -key-files: - created: - - packages/sdk-tests/js/tests/health/health.test.ts - - packages/sdk-tests/js/tests/models/list-models.test.ts - - packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts - - packages/sdk-tests/js/tests/errors/error-shape.test.ts - - packages/sdk-tests/js/tests/headers/compat-headers.test.ts - - packages/sdk-tests/js/tests/streaming/streaming-error.test.ts - - packages/sdk-tests/python/tests/__init__.py - - packages/sdk-tests/python/tests/conftest.py - - packages/sdk-tests/python/tests/test_health.py - - packages/sdk-tests/python/tests/test_models.py - - packages/sdk-tests/python/tests/test_unsupported.py - - packages/sdk-tests/python/tests/test_error_shape.py - - packages/sdk-tests/python/tests/test_headers.py - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HealthTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ModelsTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/UnsupportedEndpointTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/ErrorShapeTest.java - - packages/sdk-tests/java/src/test/java/com/hive/sdktests/HeadersTest.java - - packages/sdk-tests/fixtures/golden/models-list.json - - packages/sdk-tests/fixtures/golden/error-unsupported.json - - packages/sdk-tests/fixtures/golden/error-unknown.json - modified: [] - -key-decisions: - - "Java fine-tuning test uses raw HTTP instead of SDK to avoid coupling to SDK API surface changes" - - "Python conftest uses httpx (bundled with openai SDK) for raw HTTP tests" - - "Golden fixtures capture minimal expected shapes for regression, not full response bodies" - -patterns-established: - - "SDK harness pattern: each language tests the same scenarios (health, models, unsupported, error shape, headers)" - - "Provider-blind assertions: every error test asserts message does NOT contain provider/upstream/openai" - - "Golden fixture regression: response shapes compared against committed JSON fixtures" - -requirements-completed: [COMP-01, COMP-02] - -# Metrics -duration: 5min -completed: 2026-03-28 ---- - -# Phase 01 Plan 03: SDK Compatibility Harness Summary - -**JS/Python/Java SDK test suites proving OpenAI SDK compatibility for models endpoint, unsupported endpoint errors, compat headers, golden fixture regression, and full Docker verification** - -## Performance - -- **Duration:** 5 min -- **Started:** 2026-03-29T01:47:37Z -- **Checkpoint approved:** 2026-03-28T22:38:50-04:00 -- **Tasks:** 3/3 complete -- **Files modified:** 22 - -## Accomplishments -- JS SDK tests: health, models listing, unsupported endpoint errors (planned + explicit), error envelope shape, compat headers, streaming error handling -- Python SDK tests: health, models listing, unsupported endpoint errors, error envelope shape, compat headers with uniqueness check -- Java SDK tests: health, models listing, unsupported endpoint errors, error envelope shape, compat headers -- Golden fixtures established for models-list, error-unsupported, and error-unknown response shapes -- All error tests assert provider-blind messaging (no "provider", "upstream", or "openai" in error text) -- End-to-end Docker verification completed for JS, Python, Java, Go, Swagger UI, support matrix, and compatibility headers -- Toolchain container drift fixed so Docker-only verification remains reproducible - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Create JS SDK compatibility tests with golden fixtures** - `dc45aa0` (feat) -2. **Task 2: Create Python and Java SDK compatibility tests** - `d9b97cc` (feat) -3. **Task 3: Verify full SDK compatibility harness** - Completed on `2026-03-28T22:38:50-04:00` after human approval - -## Files Created/Modified -- `deploy/docker/Dockerfile.toolchain` - Restored Docker toolchain installs with `GOTOOLCHAIN=auto` for Go 1.25-requiring codegen tools -- `packages/sdk-tests/fixtures/golden/models-list.json` - Golden fixture for /v1/models response -- `packages/sdk-tests/fixtures/golden/error-unsupported.json` - Golden fixture for planned endpoint error -- `packages/sdk-tests/fixtures/golden/error-unknown.json` - Golden fixture for unknown endpoint error -- `packages/sdk-tests/js/tests/health/health.test.ts` - JS health endpoint test -- `packages/sdk-tests/js/tests/models/list-models.test.ts` - JS models listing + golden comparison -- `packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts` - JS unsupported endpoint error tests -- `packages/sdk-tests/js/tests/errors/error-shape.test.ts` - JS raw error envelope shape test -- `packages/sdk-tests/js/tests/headers/compat-headers.test.ts` - JS compat header tests -- `packages/sdk-tests/js/tests/streaming/streaming-error.test.ts` - JS streaming error handling test -- `packages/sdk-tests/python/tests/conftest.py` - Python fixtures (client, base_url) -- `packages/sdk-tests/python/tests/test_health.py` - Python health endpoint test -- `packages/sdk-tests/python/tests/test_models.py` - Python models listing test -- `packages/sdk-tests/python/tests/test_unsupported.py` - Python unsupported endpoint error tests -- `packages/sdk-tests/python/tests/test_error_shape.py` - Python raw error envelope shape test -- `packages/sdk-tests/python/tests/test_headers.py` - Python compat header tests -- `packages/sdk-tests/java/.../HealthTest.java` - Java health endpoint test -- `packages/sdk-tests/java/.../ModelsTest.java` - Java models listing test -- `packages/sdk-tests/java/.../UnsupportedEndpointTest.java` - Java unsupported endpoint error tests -- `packages/sdk-tests/java/.../ErrorShapeTest.java` - Java raw error envelope shape test -- `packages/sdk-tests/java/.../HeadersTest.java` - Java compat header tests - -## Decisions Made -- Java fine-tuning test uses raw java.net.http.HttpClient instead of OpenAI SDK to avoid coupling to SDK fine-tuning API surface that may change across versions -- Python conftest uses httpx (bundled with openai SDK) for raw HTTP tests, avoiding an extra dependency -- Golden fixtures capture minimal expected shapes rather than full response bodies to allow flexibility -- Go verification for the Docker-only workflow runs through the `toolchain` container from `/workspace/apps/edge-api` - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Toolchain image drift broke Docker-only verification** -- **Found during:** Task 3 checkpoint verification -- **Issue:** `github.com/ogen-go/ogen/cmd/ogen@v1.20.2` now requires Go 1.25, but `deploy/docker/Dockerfile.toolchain` still installed it on `golang:1.24-alpine` without `GOTOOLCHAIN=auto` -- **Fix:** Added `GOTOOLCHAIN=auto` to the `ogen` and `oapi-codegen` install steps in `deploy/docker/Dockerfile.toolchain` -- **Verification:** Rebuilt the toolchain image and ran `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain 'cd /workspace/apps/edge-api && go test ./... -v'` successfully - -**2. [Rule 1 - Verification Path] Go tests were executed through the toolchain container** -- **Found during:** Task 3 checkpoint verification -- **Issue:** The original checkpoint command targeted the runtime `edge-api` container, but the working Go verification environment is the Docker `toolchain` container rooted at `/workspace/apps/edge-api` -- **Fix:** Verified Go tests from the toolchain container while keeping the runtime checks on the running `edge-api` service -- **Verification:** Swagger UI, response headers, and SDK harnesses were all validated against the running `edge-api` service after the toolchain image fix - -**Total deviations:** 2 auto-fixed (1 blocking, 1 verification-path correction) -**Impact on plan:** Both fixes were required to close the pending human-verification checkpoint without changing planned scope. - -## Issues Encountered -None beyond the auto-fixed deviations above. - -## User Setup Required -None - no external service configuration required. - -## Next Phase Readiness -- SDK compatibility harness fully verified in Docker across JS, Python, Java, Go, Swagger UI, and support-matrix checks -- Phase 01 remains blocked on the contract-docs verification gap recorded in `.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md` -- Once the Swagger/OpenAPI docs serve a Hive-classified spec instead of the raw upstream spec, Phase 01 can be marked complete and Phase 02 can begin -- Future endpoint implementations will add tests to these suites and update golden fixtures - -## Self-Check: PASSED - -All 21 key files verified present. JS, Python, Java, and Go verification commands passed during checkpoint closure. - ---- -*Phase: 01-contract-compatibility-harness* -*Completed: 2026-03-28 (all tasks complete, including Task 3 human verification)* diff --git a/.planning/phases/01-contract-compatibility-harness/01-04-PLAN.md b/.planning/phases/01-contract-compatibility-harness/01-04-PLAN.md deleted file mode 100644 index e4f01776e..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-04-PLAN.md +++ /dev/null @@ -1,282 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 04 -type: execute -wave: 4 -depends_on: - - 01-02 - - 01-03 -files_modified: - - packages/openai-contract/scripts/generate-matrix.sh - - packages/openai-contract/scripts/sync_hive_contract.py - - packages/openai-contract/generated/hive-openapi.yaml - - docs/support-matrix.md - - apps/edge-api/docs/swagger.go - - apps/edge-api/docs/swagger_test.go - - apps/edge-api/cmd/server/main.go - - deploy/docker/Dockerfile.edge-api - - deploy/docker/docker-compose.override.yml -autonomous: true -requirements: - - COMP-03 -must_haves: - truths: - - "The edge API serves /docs/openapi.yaml from a Hive-specific OpenAPI artifact derived from packages/openai-contract/matrix/support-matrix.json, not the raw upstream spec" - - "The served OpenAPI document advertises Hive's API base URL with `url: /v1` and does not contain `https://api.openai.com/v1`" - - "Every public operation in the served OpenAPI document includes x-hive-status and x-hive-phase values copied from support-matrix.json" - - "Organization and admin endpoints are excluded from the published Swagger contract so /docs reflects Hive's public API surface" - - "docs/support-matrix.md and packages/openai-contract/generated/hive-openapi.yaml are regenerated from the same support-matrix.json source" - - "The edge-api container copies and serves the generated Hive spec by default, and local Docker development sees regenerated contract artifacts without a manual image rebuild" - artifacts: - - path: "packages/openai-contract/scripts/generate-matrix.sh" - provides: "Real contract-sync entrypoint used from the Docker toolchain" - contains: "sync_hive_contract.py" - - path: "packages/openai-contract/scripts/sync_hive_contract.py" - provides: "Generator that derives the published OpenAPI contract and support-matrix markdown from support-matrix.json" - exports: ["load_matrix", "render_openapi", "render_markdown"] - - path: "packages/openai-contract/generated/hive-openapi.yaml" - provides: "Hive-specific OpenAPI document published at /docs/openapi.yaml" - contains: "x-hive-status" - - path: "docs/support-matrix.md" - provides: "Human-readable support matrix generated from support-matrix.json" - contains: "Generated from `packages/openai-contract/matrix/support-matrix.json`" - - path: "apps/edge-api/docs/swagger.go" - provides: "Docs handler serving Swagger UI and the generated Hive spec" - exports: ["SwaggerHandler"] - - path: "apps/edge-api/docs/swagger_test.go" - provides: "Regression tests proving /docs serves the generated Hive contract" - contains: "x-hive-status" - - path: "apps/edge-api/cmd/server/main.go" - provides: "Runtime wiring for the generated OpenAPI spec path" - contains: "generated/hive-openapi.yaml" - - path: "deploy/docker/Dockerfile.edge-api" - provides: "Container image copies the generated Hive spec into the edge-api runtime" - contains: "generated/hive-openapi.yaml" - key_links: - - from: "packages/openai-contract/matrix/support-matrix.json" - to: "packages/openai-contract/generated/hive-openapi.yaml" - via: "sync_hive_contract.py injects status/phase metadata and Hive server URL into the published spec" - pattern: "x-hive-status" - - from: "packages/openai-contract/matrix/support-matrix.json" - to: "docs/support-matrix.md" - via: "sync_hive_contract.py renders markdown rows from matrix entries" - pattern: "Generated from `packages/openai-contract/matrix/support-matrix.json`" - - from: "apps/edge-api/cmd/server/main.go" - to: "packages/openai-contract/generated/hive-openapi.yaml" - via: "OPENAPI_SPEC_PATH default points at the generated Hive spec" - pattern: "generated/hive-openapi.yaml" - - from: "deploy/docker/Dockerfile.edge-api" - to: "packages/openai-contract/generated/hive-openapi.yaml" - via: "COPY includes generated spec in the runtime image" - pattern: "generated/hive-openapi.yaml" - - from: "deploy/docker/docker-compose.override.yml" - to: "packages/openai-contract/generated/hive-openapi.yaml" - via: "develop.watch sync keeps contract artifacts current inside the dev container" - pattern: "packages/openai-contract" ---- - - -Close the Phase 1 documentation contract gap by publishing a Hive-specific OpenAPI document and support-matrix docs that stay derived from the runtime support matrix instead of drifting from it. - -Purpose: Phase 1 verification failed only on `COMP-03`. The runtime behavior, support-matrix enforcement, and SDK harness all passed, but `/docs` still serves the raw upstream OpenAI spec. This plan replaces that raw spec with a generated Hive contract so Swagger reflects the same public launch surface and status annotations that the runtime enforces. -Output: A real matrix-to-docs generation step, a generated `packages/openai-contract/generated/hive-openapi.yaml`, an updated generated `docs/support-matrix.md`, container wiring that serves the generated spec by default, and tests/verification proving `/docs` now exposes the Hive contract. - - - -@/home/sakib/.codex/get-shit-done/workflows/execute-plan.md -@/home/sakib/.codex/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md -@.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md -@.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md -@.planning/phases/01-contract-compatibility-harness/01-02-SUMMARY.md -@.planning/phases/01-contract-compatibility-harness/01-03-SUMMARY.md - - - -From packages/openai-contract/matrix/support-matrix.json: -```json -{ - "version": "0.1.0", - "generated": "2026-03-28", - "endpoints": [ - { - "method": "GET", - "path": "/v1/models", - "status": "supported_now", - "phase": 1, - "notes": "Lists available models" - } - ] -} -``` - -From apps/edge-api/docs/swagger.go: -```go -// /docs/openapi.yaml currently reads a spec file from disk and writes it as application/yaml -// /docs/ serves Swagger UI HTML that loads ./openapi.yaml -``` - -From apps/edge-api/cmd/server/main.go: -```go -// OPENAPI_SPEC_PATH currently defaults to /app/packages/openai-contract/upstream/openapi.yaml -// SUPPORT_MATRIX_PATH currently defaults to /app/packages/openai-contract/matrix/support-matrix.json -``` - -From .planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md: -```text -Gap: /docs is reachable, but it serves the raw upstream OpenAI spec instead of a Hive-specific contract view. -Missing outcomes: -- Serve a Hive-specific OpenAPI document that applies support classification to the browsable docs -- Replace the upstream OpenAI server URL in the served spec with Hive's API base URL -- Add a real generation/sync step so published docs stay derived from support-matrix.json -``` - - - - - - Task 1: Replace the placeholder docs sync with a real generator that derives published contract artifacts from support-matrix.json - - packages/openai-contract/scripts/generate-matrix.sh, - packages/openai-contract/scripts/sync_hive_contract.py, - packages/openai-contract/generated/hive-openapi.yaml, - docs/support-matrix.md - - - packages/openai-contract/matrix/support-matrix.json, - packages/openai-contract/upstream/openapi.yaml, - packages/openai-contract/overlays/hive-support-status.yaml, - packages/openai-contract/scripts/generate-matrix.sh, - docs/support-matrix.md, - .planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md - - -1. Add `packages/openai-contract/scripts/sync_hive_contract.py` with three top-level helpers named `load_matrix`, `render_openapi`, and `render_markdown`. - - `load_matrix` reads `packages/openai-contract/matrix/support-matrix.json` and builds a lookup keyed by `"METHOD /v1/path"`. - - `render_openapi` reads `packages/openai-contract/upstream/openapi.yaml`, strips the `/v1` prefix from matrix paths when matching upstream operations, and writes `packages/openai-contract/generated/hive-openapi.yaml`. - - In the generated spec, set the top-level server list exactly to: - ```yaml - servers: - - url: /v1 - description: Hive API base URL on the current host - ``` - - Remove any path whose operations are all marked `out_of_scope` so `/v1/organization/*` does not appear in the published Swagger contract. - - For every remaining operation, copy the matrix row into OpenAPI extensions: - - `x-hive-status: ` - - `x-hive-phase: ` - - `x-hive-notes: ` - - Preserve upstream request/response schemas and operation structure; only patch server metadata, visible path set, and Hive-specific extensions. -2. Use the same Python script to rewrite `docs/support-matrix.md` from `support-matrix.json`. - - Sort sections in this exact order: `supported_now`, `planned_for_launch`, `explicitly_unsupported_at_launch`, `out_of_scope`. - - Add a provenance line immediately after the title: - `Generated from \`packages/openai-contract/matrix/support-matrix.json\`. Do not edit manually.` - - Keep the markdown table columns exactly `Method | Path | Status | Phase | Notes`. -3. Replace the placeholder behavior in `packages/openai-contract/scripts/generate-matrix.sh`. - - Keep the file name, but turn it into a real entrypoint that runs `python3 packages/openai-contract/scripts/sync_hive_contract.py`. - - Fail fast if `packages/openai-contract/matrix/support-matrix.json` or `packages/openai-contract/upstream/openapi.yaml` is missing. - - Fail fast if the generated spec still contains `https://api.openai.com/v1` or if `packages/openai-contract/generated/hive-openapi.yaml` does not contain `x-hive-status:`. - - Print the number of generated public operations and the output file paths on success. -4. Commit the generated `packages/openai-contract/generated/hive-openapi.yaml` artifact to the repo so the served contract is deterministic and reviewable. - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml run --rm toolchain 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh' 2>&1 | tail -30 - - - - packages/openai-contract/scripts/generate-matrix.sh does NOT contain `placeholder for future automation` - - packages/openai-contract/scripts/generate-matrix.sh contains `sync_hive_contract.py` - - packages/openai-contract/scripts/sync_hive_contract.py contains `def load_matrix(` and `def render_openapi(` and `def render_markdown(` - - packages/openai-contract/generated/hive-openapi.yaml contains `url: /v1` - - packages/openai-contract/generated/hive-openapi.yaml contains `x-hive-status: supported_now` - - packages/openai-contract/generated/hive-openapi.yaml contains `x-hive-phase: 1` - - packages/openai-contract/generated/hive-openapi.yaml does NOT contain `https://api.openai.com/v1` - - packages/openai-contract/generated/hive-openapi.yaml does NOT contain `/organization/` - - docs/support-matrix.md contains `Generated from \`packages/openai-contract/matrix/support-matrix.json\`. Do not edit manually.` - - docker compose -f deploy/docker/docker-compose.yml run --rm toolchain 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh' exits 0 - - A real sync step generates both the published Hive OpenAPI contract and the support-matrix markdown from support-matrix.json, eliminating the placeholder docs workflow - - - - Task 2: Serve the generated Hive contract from edge-api and add regression coverage for the docs route - - apps/edge-api/docs/swagger.go, - apps/edge-api/docs/swagger_test.go, - apps/edge-api/cmd/server/main.go, - deploy/docker/Dockerfile.edge-api, - deploy/docker/docker-compose.override.yml - - - apps/edge-api/docs/swagger.go, - apps/edge-api/cmd/server/main.go, - deploy/docker/Dockerfile.edge-api, - deploy/docker/docker-compose.override.yml, - packages/openai-contract/generated/hive-openapi.yaml, - packages/openai-contract/matrix/support-matrix.json, - .planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md - - -1. Update `apps/edge-api/cmd/server/main.go` so `OPENAPI_SPEC_PATH` defaults to `/app/packages/openai-contract/generated/hive-openapi.yaml` instead of the upstream spec path. - - Keep `OPENAPI_SPEC_PATH` as the override env var name. - - Keep `SUPPORT_MATRIX_PATH` unchanged. - - Do not change the existing `/docs/` routing contract. -2. Update `deploy/docker/Dockerfile.edge-api`. - - Keep copying `packages/openai-contract/matrix/support-matrix.json`. - - Replace the existing OpenAPI copy step with: - `COPY packages/openai-contract/generated/hive-openapi.yaml ./packages/openai-contract/generated/hive-openapi.yaml` - - Do not copy the raw upstream spec into the runtime image for Swagger serving. -3. Update `deploy/docker/docker-compose.override.yml` so local dev sync includes `../../packages/openai-contract` to `/app/packages/openai-contract`. - - This must make regenerated contract artifacts visible inside the running dev container without forcing a full image rebuild. -4. Add `apps/edge-api/docs/swagger_test.go`. - - Use a temporary fixture file that contains `servers:`, `url: /v1`, and `x-hive-status: supported_now`. - - Assert `GET /docs/openapi.yaml` returns `200`, `Content-Type: application/yaml`, and a body containing `url: /v1`. - - Assert the body contains `x-hive-status: supported_now`. - - Assert the body does NOT contain `https://api.openai.com/v1`. - - Assert `GET /docs/` returns HTML containing both `swagger-ui` and `./openapi.yaml`. - - Assert a missing spec file returns `404` with body containing `spec file not found`. -5. Only change `apps/edge-api/docs/swagger.go` where needed to support those tests cleanly; preserve the handler shape and URL structure. - - - cd /home/sakib/hive/apps/edge-api && go test ./docs/... ./cmd/server/... -v -count=1 2>&1 | tail -30 - - - - apps/edge-api/cmd/server/main.go contains `/app/packages/openai-contract/generated/hive-openapi.yaml` - - deploy/docker/Dockerfile.edge-api contains `generated/hive-openapi.yaml` - - deploy/docker/Dockerfile.edge-api does NOT contain `packages/openai-contract/upstream/openapi.yaml` - - deploy/docker/docker-compose.override.yml contains `../../packages/openai-contract` - - apps/edge-api/docs/swagger_test.go contains `url: /v1` - - apps/edge-api/docs/swagger_test.go contains `x-hive-status: supported_now` - - apps/edge-api/docs/swagger_test.go contains `https://api.openai.com/v1` - - go test ./docs/... ./cmd/server/... exits 0 - - The edge API serves the generated Hive contract by default, the dev container sees regenerated contract artifacts, and docs-route tests prevent regressions back to the raw upstream spec - - - - - -- `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh'` exits 0 and prints generated artifact paths -- `cd /home/sakib/hive/apps/edge-api && go test ./docs/... ./cmd/server/... -v` exits 0 -- `docker compose -f deploy/docker/docker-compose.yml build edge-api` succeeds with the generated Hive spec copied into the image -- `docker compose -f deploy/docker/docker-compose.yml up -d edge-api && sleep 8 && docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'wget -qO- http://localhost:8080/docs/openapi.yaml | grep -q \"x-hive-status\" && wget -qO- http://localhost:8080/docs/openapi.yaml | grep -q \"url: /v1\" && ! wget -qO- http://localhost:8080/docs/openapi.yaml | grep -q \"https://api.openai.com/v1\"'` exits 0 -- `docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'wget -qO- http://localhost:8080/docs/ | grep -q swagger-ui'` exits 0 -- `docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'wget -qO- http://localhost:8080/docs/openapi.yaml | grep -q \"/organization/\"'` exits 1 - - - -- `/docs/openapi.yaml` serves a Hive-shaped contract instead of the raw upstream OpenAI spec -- The published contract advertises Hive's API base URL and contains x-hive-status/x-hive-phase metadata for public operations -- Organization/admin endpoints are not exposed in the published Swagger contract -- docs/support-matrix.md and the served OpenAPI contract are both regenerated from support-matrix.json -- The Docker edge-api image and local dev container both consume the generated Hive spec by default -- Phase 1 verification can satisfy `COMP-03` without changing the already-passing SDK and runtime compatibility evidence - - - -After completion, create `.planning/phases/01-contract-compatibility-harness/01-04-SUMMARY.md` - diff --git a/.planning/phases/01-contract-compatibility-harness/01-04-SUMMARY.md b/.planning/phases/01-contract-compatibility-harness/01-04-SUMMARY.md deleted file mode 100644 index 16bcbd5ba..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-04-SUMMARY.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -plan: 04 -subsystem: api -tags: [openapi, swagger, compatibility, docs, support-matrix, contract-generation] - -# Dependency graph -requires: - - phase: 01-contract-compatibility-harness (plan 02) - provides: "Support matrix classification, Swagger docs wiring, and public contract inventory" - - phase: 01-contract-compatibility-harness (plan 03) - provides: "Verified runtime compatibility surface and the COMP-03 gap diagnosis" -provides: - - "Generated Hive-specific OpenAPI contract derived from support-matrix.json" - - "Generated support-matrix markdown derived from the same source data as the published contract" - - "Edge API docs route serving the generated Hive contract by default" - - "Regression tests covering generated contract serving and default docs wiring" -affects: [02-auth, 06-inference-surface] - -# Tech tracking -tech-stack: - added: [py3-yaml] - patterns: [matrix-derived-contract-artifacts, generated-docs-source-of-truth, docs-route-regression-tests] - -key-files: - created: - - packages/openai-contract/scripts/sync_hive_contract.py - - packages/openai-contract/generated/hive-openapi.yaml - - apps/edge-api/cmd/server/main_test.go - - apps/edge-api/docs/swagger_test.go - modified: - - packages/openai-contract/scripts/generate-matrix.sh - - docs/support-matrix.md - - apps/edge-api/cmd/server/main.go - - deploy/docker/Dockerfile.edge-api - - deploy/docker/Dockerfile.toolchain - - deploy/docker/docker-compose.override.yml - -key-decisions: - - "Published docs are generated from support-matrix.json plus the upstream spec so runtime support classification stays the single source of truth" - - "The generated contract drops top-level upstream x-oaiMeta so out-of-scope organization/admin docs metadata cannot leak into Hive's published contract" - - "The generator entrypoint is POSIX-sh compatible and the toolchain image includes py3-yaml so Docker verification runs the same generation path as local development" - -patterns-established: - - "Contract-docs generation pattern: sync_hive_contract.py rewrites the published spec and markdown from support-matrix.json" - - "Docs serving pattern: OPENAPI_SPEC_PATH defaults to the committed generated contract artifact" - - "Regression pattern: docs route tests assert Hive-specific spec contents and absence of the upstream OpenAI base URL" - -requirements-completed: [COMP-03] - -# Metrics -duration: 21min -completed: 2026-03-28 ---- - -# Phase 01 Plan 04: Docs Contract Summary - -**Generated Hive-specific OpenAPI and support-matrix artifacts derived from the runtime support matrix, with `/docs` now serving the generated contract instead of the raw upstream spec** - -## Performance - -- **Duration:** 21 min -- **Started:** 2026-03-28T23:18:17-04:00 -- **Completed:** 2026-03-28T23:39:40-04:00 -- **Tasks:** 2/2 complete -- **Files modified:** 12 - -## Accomplishments -- Added a real contract-sync generator that derives both `packages/openai-contract/generated/hive-openapi.yaml` and `docs/support-matrix.md` from `support-matrix.json` -- Injected `x-hive-status`, `x-hive-phase`, and `x-hive-notes` into every published public operation while excluding out-of-scope organization/admin endpoints from the generated contract -- Switched the edge API and runtime image defaults to serve the generated Hive contract at `/docs/openapi.yaml` -- Added runtime regression tests for the docs route and spec-path defaults, plus Docker wiring so regenerated contract artifacts flow into local development without a manual rebuild - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Replace the placeholder docs sync with a real generator that derives published contract artifacts from support-matrix.json** - `6a83d88` (feat) -2. **Task 2: Serve the generated Hive contract from edge-api and add regression coverage for the docs route** - `3729296` (feat) - -**TDD red commit:** `8d4ae14` (`test(01-04): add failing tests for Hive contract generator`) - -## Files Created/Modified -- `packages/openai-contract/scripts/sync_hive_contract.py` - Generates the published Hive OpenAPI contract and support-matrix markdown from `support-matrix.json` -- `packages/openai-contract/scripts/generate-matrix.sh` - POSIX-sh entrypoint that validates inputs and generated outputs before succeeding -- `packages/openai-contract/generated/hive-openapi.yaml` - Committed generated OpenAPI artifact served by `/docs/openapi.yaml` -- `docs/support-matrix.md` - Generated human-readable contract surface derived from the same source data -- `apps/edge-api/cmd/server/main.go` - Defaults `OPENAPI_SPEC_PATH` to the generated Hive contract -- `apps/edge-api/cmd/server/main_test.go` - Verifies the generated-spec default path and env override behavior -- `apps/edge-api/docs/swagger_test.go` - Verifies `/docs/` and `/docs/openapi.yaml` serve the expected Hive contract behavior -- `deploy/docker/Dockerfile.edge-api` - Copies the generated contract artifact into the runtime image -- `deploy/docker/docker-compose.override.yml` - Syncs `packages/openai-contract` into the dev container so regenerated artifacts appear without a rebuild -- `deploy/docker/Dockerfile.toolchain` - Adds `py3-yaml` so Docker verification can run the Python generator - -## Decisions Made -- Kept the published contract generator in Python because the repository already had Python-based TDD coverage and the toolchain image already carries Python for developer workflows -- Scrubbed the upstream base URL from all generated spec strings, not just the `servers` block, so examples and metadata cannot drift back toward OpenAI production endpoints -- Removed the top-level upstream `x-oaiMeta` block from the published artifact because it reintroduced organization/admin documentation references that are outside Hive's public API surface - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Toolchain image lacked the YAML dependency required by the new generator** -- **Found during:** Task 1 verification -- **Issue:** `docker compose ... run --rm toolchain 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh'` failed with `ModuleNotFoundError: No module named 'yaml'` -- **Fix:** Added `py3-yaml` to `deploy/docker/Dockerfile.toolchain` -- **Files modified:** `deploy/docker/Dockerfile.toolchain` -- **Verification:** Rebuilt the toolchain image and reran the generator command successfully -- **Committed in:** `6a83d88` - -**2. [Rule 3 - Blocking] The generator entrypoint assumed bash in an Alpine `/bin/sh` container** -- **Found during:** Task 1 verification -- **Issue:** `generate-matrix.sh` used `#!/usr/bin/env bash` and `${BASH_SOURCE[0]}`, which failed in the toolchain container with `env: can't execute 'bash'` -- **Fix:** Converted the entrypoint to POSIX `sh` and replaced the script-dir resolution logic -- **Files modified:** `packages/openai-contract/scripts/generate-matrix.sh` -- **Verification:** The toolchain container now runs the generator command successfully -- **Committed in:** `6a83d88` - -**3. [Rule 1 - Missing Critical] Upstream docs metadata reintroduced `/organization/` references after path filtering** -- **Found during:** Task 1 acceptance review -- **Issue:** The generated spec removed out-of-scope API paths but the top-level upstream `x-oaiMeta` block still contained organization/admin documentation references, violating the published-contract acceptance criteria -- **Fix:** Removed the top-level `x-oaiMeta` block from the generated artifact -- **Files modified:** `packages/openai-contract/scripts/sync_hive_contract.py`, `packages/openai-contract/generated/hive-openapi.yaml` -- **Verification:** `rg -n "https://api.openai.com/v1|/organization/" packages/openai-contract/generated/hive-openapi.yaml` now returns only the expected Hive annotations and no upstream/org references -- **Committed in:** `6a83d88` - -**Total deviations:** 3 auto-fixed (2 blocking, 1 missing critical) -**Impact on plan:** All deviations were required to make the generated contract actually verifiable in Docker and to keep the published spec aligned with Hive's scoped public surface. - -## Issues Encountered -None beyond the auto-fixed deviations above. - -## User Setup Required -None - no external service configuration required. - -## Next Phase Readiness -- Phase 01 now has a generated, committed, and served Hive-specific OpenAPI contract that matches the support-matrix classification -- The remaining workflow step is phase-level verification so `COMP-03` can be rechecked against the refreshed docs path and the phase can be marked complete -- If verification passes, Phase 02 can proceed without further Phase 01 docs work - -## Self-Check: PASSED - -Verified with: -- `python3 -m unittest packages.openai-contract.scripts.test_sync_hive_contract` -- `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain "cd /workspace && packages/openai-contract/scripts/generate-matrix.sh"` -- `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain "cd /workspace/apps/edge-api && go test ./docs/... ./cmd/server/... -v -count=1"` - ---- -*Phase: 01-contract-compatibility-harness* -*Completed: 2026-03-28* diff --git a/.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md b/.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md deleted file mode 100644 index 86b4ba7af..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-CONTEXT.md +++ /dev/null @@ -1,106 +0,0 @@ -# Phase 1: Contract & Compatibility Harness - Context - -**Gathered:** 2026-03-28 -**Status:** Ready for planning - - -## Phase Boundary - -Define Hive's public OpenAI-facing compatibility contract for the launch-era product, import and version the source contract, publish an explicit endpoint support matrix for the public non-org/admin surface, enforce OpenAI-style unsupported behavior for anything outside the currently supported subset, generate Swagger/OpenAPI docs for the implemented surface, and create a compatibility harness that proves official SDK behavior instead of approximating it. - - - - -## Implementation Decisions - -### Launch coverage bar -- Publish an endpoint-by-endpoint matrix for the full public non-org/admin OpenAI surface rather than a partial or family-only summary. -- Treat the long-term launch target as a near-full public mirror, even though the `supported now` subset in Phase 1 will remain narrow. -- Use four explicit statuses in the public matrix: `supported now`, `planned for launch`, `explicitly unsupported at launch`, and `out of scope` for org/admin endpoints. -- The support matrix must distinguish future launch intent from current implementation status; Phase 1 must not blur those together. - -### Unsupported behavior contract -- Any public endpoint marked as not currently supported must return strict OpenAI-style unsupported errors rather than best-effort fallbacks or generic placeholder failures. -- Unsupported parameters, modes, or feature combinations on otherwise-supported endpoints must also fail explicitly with OpenAI-style errors instead of being silently ignored. -- Error messaging should be customer-clear but provider-blind: explain what capability is unavailable without exposing upstream provider identity or internal routing constraints. -- The published support matrix is authoritative for runtime behavior; if the matrix says a capability is unsupported or only planned, the runtime must reject it consistently until the matrix changes. - -### Compatibility proof bar -- Phase 1 should use a deep compatibility verification standard for official OpenAI JavaScript/TypeScript, Python, and Java SDKs rather than minimal smoke tests. -- Streaming compatibility must be proven with golden regression cases that cover event ordering, chunk shape, terminal events, and interruption or failure behavior. -- Compatibility proof must include error-path and unsupported-path fidelity, including HTTP status behavior, error object shape, compatibility headers, and explicit unsupported responses. -- A failing compatibility harness blocks Hive from claiming the affected endpoint or status as supported. - -### Docs and support matrix format -- Public documentation should expose an endpoint-by-endpoint reference table rather than relying on prose or family-only summaries. -- Each matrix row should include the endpoint or method, current status, brief support notes, and later-phase linkage when full implementation belongs to a later phase. -- Endpoint support and model support should be treated as separate views; model-level readiness or health must not be mixed into the endpoint matrix. -- Swagger/OpenAPI is the source for request and response shape, but the support matrix is the authoritative source of support status. - -### Claude's Discretion -- No additional product-scope decisions were delegated during discussion. -- Downstream agents may choose the exact codegen tools, test harness structure, documentation rendering approach, and internal implementation details as long as they preserve the matrix/status model, provider-blind unsupported behavior, and the high compatibility proof bar above. - - - - -## Canonical References - -**Downstream agents MUST read these before planning or implementing.** - -### Phase scope and requirements -- `.planning/ROADMAP.md` § "Phase 1: Contract & Compatibility Harness" — Defines the phase goal, success criteria, and plan breakdown for the compatibility harness. -- `.planning/REQUIREMENTS.md` § "Compatibility & Contract" — Defines `COMP-01`, `COMP-02`, and `COMP-03`. -- `.planning/REQUIREMENTS.md` § "Inference Surface" — Defines `API-08`, which requires explicit unsupported behavior for public endpoints outside the implemented launch subset. -- `.planning/PROJECT.md` § "Context" — States the product promise of mirroring the public OpenAI API surface except org/admin endpoints. -- `.planning/PROJECT.md` § "Constraints" — Locks Docker-only development, provider abstraction, privacy posture, and compatibility expectations. -- `.planning/STATE.md` § "Accumulated Context" — Carries forward project-level constraints already accepted for the current phase. - -### Research that should shape planning -- `.planning/research/SUMMARY.md` — Recommends a contract-first compatibility architecture and explains why the support matrix and SDK regression harness must come first. -- `.planning/research/ARCHITECTURE.md` — Describes the recommended `packages/openai-contract` and `packages/sdk-tests` structure and the contract-first public edge approach. -- `.planning/research/STACK.md` — Recommends the Docker-only toolchain, Go OpenAPI codegen options, and SDK compatibility harness tooling expectations. -- `.planning/research/PITFALLS.md` — Highlights "compatibility by approximation" as the main Phase 1 failure mode and reinforces explicit unsupported behavior. -- `.planning/research/FEATURES.md` — Explains why official SDK compatibility and endpoint capability classification are launch-critical dependencies. - - - - -## Existing Code Insights - -### Reusable Assets -- No application code exists yet; the repository is currently a planning and research scaffold. -- The most reusable current assets are the research documents that already define the contract-first approach, recommended structure, and compatibility risks. - -### Established Patterns -- The project is greenfield, so there are no existing implementation patterns to preserve at the code level. -- Project-level decisions already lock a Docker-only developer workflow and a contract-first architecture for public API compatibility. -- The repo's planning structure is already organized around phased delivery, so Phase 1 outputs should become the canonical contract baseline for later phases. - -### Integration Points -- Phase 1 decisions will shape the first implementation work in the future `packages/openai-contract` area described in `.planning/research/ARCHITECTURE.md`. -- The compatibility harness and regression suites should seed the future `packages/sdk-tests` area described in `.planning/research/ARCHITECTURE.md`. -- The support matrix and generated Swagger/OpenAPI docs will become the contract boundary that later endpoint implementation phases must obey. - - - - -## Specific Ideas - -- The support matrix should cover the full public non-org/admin surface even when current implementation is narrow. -- Public support status must distinguish `supported now` from `planned for launch`; the matrix cannot flatten those into one bucket. -- Public errors and future model-support views must remain provider-blind. - - - - -## Deferred Ideas - -- Add a provider-blind per-model health or support view separate from the endpoint matrix. This is valuable, but it is a separate capability from Phase 1's endpoint contract and documentation work. - - - ---- - -*Phase: 01-contract-compatibility-harness* -*Context gathered: 2026-03-28* diff --git a/.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md b/.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md deleted file mode 100644 index 0e501bf46..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-RESEARCH.md +++ /dev/null @@ -1,516 +0,0 @@ -# Phase 1: Contract & Compatibility Harness - Research - -**Researched:** 2026-03-28 -**Domain:** OpenAI contract import, Go OpenAPI codegen, SDK compatibility testing, Docker-only development workflow -**Confidence:** HIGH - -## Summary - -Phase 1 is a foundational phase that establishes Hive's compatibility contract before any business logic exists. The core work is: (1) importing and versioning the official OpenAI OpenAPI spec, (2) generating Go server types from it, (3) building a support matrix that classifies every public non-org/admin endpoint, (4) creating a compatibility harness that proves official SDK behavior against the implemented subset, (5) ensuring unsupported endpoints return strict OpenAI-style errors, (6) publishing Swagger/OpenAPI docs, and (7) containerizing the entire developer workflow. - -The OpenAI spec lives at `github.com/openai/openai-openapi` on the `manual_spec` branch as a ~1.3MB `openapi.yaml` file. There are no recent formal releases (last tagged 2.0.0 in June 2023), so Hive should pin to a specific commit SHA. The Go codegen ecosystem offers two strong options: `ogen` (v1.20.2, high-performance, strongly typed) and `oapi-codegen` (v2.6.0, simpler, more flexible). The OpenAI spec is large and uses complex oneOf/anyOf patterns extensively, so codegen tooling must be validated against the actual spec early -- partial generation with overlays is the pragmatic path. - -**Primary recommendation:** Import the OpenAI spec pinned to a commit SHA, create Hive overlay documents to mark support status and trim unsupported operations, generate Go server stubs with `ogen` (falling back to `oapi-codegen` for problematic endpoints), build SDK compatibility tests using official OpenAI JS/Python/Java SDKs pointed at a local Hive stub server, and containerize everything with Docker Compose `develop.watch`. - - - -## User Constraints (from CONTEXT.md) - -### Locked Decisions - -- Publish an endpoint-by-endpoint matrix for the full public non-org/admin OpenAI surface rather than a partial or family-only summary. -- Treat the long-term launch target as a near-full public mirror, even though the `supported now` subset in Phase 1 will remain narrow. -- Use four explicit statuses in the public matrix: `supported now`, `planned for launch`, `explicitly unsupported at launch`, and `out of scope` for org/admin endpoints. -- The support matrix must distinguish future launch intent from current implementation status; Phase 1 must not blur those together. -- Any public endpoint marked as not currently supported must return strict OpenAI-style unsupported errors rather than best-effort fallbacks or generic placeholder failures. -- Unsupported parameters, modes, or feature combinations on otherwise-supported endpoints must also fail explicitly with OpenAI-style errors instead of being silently ignored. -- Error messaging should be customer-clear but provider-blind: explain what capability is unavailable without exposing upstream provider identity or internal routing constraints. -- The published support matrix is authoritative for runtime behavior; if the matrix says a capability is unsupported or only planned, the runtime must reject it consistently until the matrix changes. -- Phase 1 should use a deep compatibility verification standard for official OpenAI JavaScript/TypeScript, Python, and Java SDKs rather than minimal smoke tests. -- Streaming compatibility must be proven with golden regression cases that cover event ordering, chunk shape, terminal events, and interruption or failure behavior. -- Compatibility proof must include error-path and unsupported-path fidelity, including HTTP status behavior, error object shape, compatibility headers, and explicit unsupported responses. -- A failing compatibility harness blocks Hive from claiming the affected endpoint or status as supported. -- Public documentation should expose an endpoint-by-endpoint reference table rather than relying on prose or family-only summaries. -- Each matrix row should include the endpoint or method, current status, brief support notes, and later-phase linkage when full implementation belongs to a later phase. -- Endpoint support and model support should be treated as separate views; model-level readiness or health must not be mixed into the endpoint matrix. -- Swagger/OpenAPI is the source for request and response shape, but the support matrix is the authoritative source of support status. - -### Claude's Discretion - -- No additional product-scope decisions were delegated during discussion. -- Downstream agents may choose the exact codegen tools, test harness structure, documentation rendering approach, and internal implementation details as long as they preserve the matrix/status model, provider-blind unsupported behavior, and the high compatibility proof bar above. - -### Deferred Ideas (OUT OF SCOPE) - -- Add a provider-blind per-model health or support view separate from the endpoint matrix. This is valuable, but it is a separate capability from Phase 1's endpoint contract and documentation work. - - - - - -## Phase Requirements - -| ID | Description | Research Support | -|----|-------------|-----------------| -| COMP-01 | Developer can use official OpenAI JS/TS, Python, and Java SDKs against Hive by changing only base URL and API key for supported endpoints. | OpenAI SDKs (Node v6.33.0, Python v2.30.0, Java v4.30.0) require only `base_url` override. Codegen from official spec ensures request/response shape fidelity. SDK compatibility harness validates drop-in behavior. | -| COMP-02 | Hive returns OpenAI-style HTTP status codes, error objects, and compatibility headers for both supported requests and explicit unsupported-feature responses. | OpenAI error format is `{"error": {"message": "...", "type": "...", "code": "..."}}` with standard HTTP status codes (400, 401, 403, 404, 429, 500). Unsupported endpoints should return 404 or 400 with clear messages. Compatibility headers include `openai-organization`, `openai-processing-ms`, `openai-version`, `x-request-id`. | -| COMP-03 | Developer can browse Swagger/OpenAPI documentation that matches the Hive public API contract and supported launch surface. | Swagger UI can be embedded in Go using `swaggest/swgui` or static embed. The Hive overlay spec (not raw OpenAI spec) should be the doc source, showing only what Hive exposes with correct support annotations. | -| API-08 | Public non-org/admin endpoints outside the initial launch subset are explicitly classified and return OpenAI-style unsupported responses until implemented. | The support matrix with four statuses drives a catch-all middleware that intercepts requests to classified-but-unsupported paths and returns structured OpenAI error responses. | - - - -## Standard Stack - -### Core - -| Library | Version | Purpose | Why Standard | -|---------|---------|---------|--------------| -| Go | 1.24+ (current stable) | Edge API server, codegen host | Stack decision from project research; verify exact version at implementation time | -| OpenAI OpenAPI spec | `manual_spec` branch, pinned SHA | Canonical contract source | Official spec from `github.com/openai/openai-openapi`; ~1.3MB YAML; no recent tagged releases, pin to commit SHA for reproducibility | -| ogen | v1.20.2 | Primary Go server/client codegen from OpenAPI v3 | Generates strongly-typed handlers with no reflect/interface{}, sum types for oneOf, high performance routing and validation; actively maintained (released 2026-03-27) | -| oapi-codegen | v2.6.0 | Secondary/fallback Go codegen | Simpler generation path, supports chi/echo/net-http servers, useful for endpoints where ogen's strict typing is harder to overlay; released 2026-02-27 | -| Docker Compose | Current stable | Local orchestration | `develop.watch` feature provides sync/rebuild/restart actions for hot-reload without host toolchain | -| air | v1.64.5 | Go hot-reload inside containers | Watches Go files and recompiles on change; pairs with Docker Compose watch for the outer sync layer | - -### Supporting - -| Library | Version | Purpose | When to Use | -|---------|---------|---------|-------------| -| OpenAI Node SDK | v6.33.0 | JS/TS compatibility testing | Point at local Hive stub with `baseURL` override for SDK regression harness | -| OpenAI Python SDK | v2.30.0 | Python compatibility testing | Point at local Hive stub with `base_url` override for SDK regression harness | -| OpenAI Java SDK | v4.30.0 | Java compatibility testing | Point at local Hive stub with `baseUrl` override for SDK regression harness | -| swaggest/swgui | v5 | Embedded Swagger UI for Go | Serve Hive's OpenAPI spec as browsable documentation inside the edge API | -| OpenAPI Overlay Spec | v1.1.0 | Spec augmentation without forking | Apply Hive-specific annotations (support status, custom descriptions) on top of imported OpenAI spec | - -### Alternatives Considered - -| Instead of | Could Use | Tradeoff | -|------------|-----------|----------| -| ogen (primary codegen) | oapi-codegen only | oapi-codegen is simpler but generates less type-safe code with more interface{} usage; ogen's strict typing is better for contract fidelity but may struggle with edge cases in the large OpenAI spec | -| OpenAPI Overlay Spec | Manual spec fork/edit | Overlays keep the upstream spec untouched and diffs reviewable; manual edits create merge conflicts on spec updates | -| air (Go hot reload) | Docker Compose watch sync+restart only | air recompiles Go on file change inside the container; Compose watch alone only syncs files but does not trigger Go rebuild | -| swaggest/swgui | Standalone Swagger UI container | Embedded approach is simpler for a single Go binary; standalone container adds operational complexity for dev but may be useful in production | - -**Installation (all containerized):** -```bash -# No host installs needed -- everything runs in Docker -docker compose up --watch # Start full dev stack with file sync -docker compose run --rm toolchain go generate ./... # Run codegen -docker compose run --rm sdk-tests npm test # JS SDK tests -docker compose run --rm sdk-tests-py pytest # Python SDK tests -docker compose run --rm sdk-tests-java ./gradlew test # Java SDK tests -``` - -## Architecture Patterns - -### Recommended Project Structure (Phase 1 scope) - -``` -platform/ -├── packages/ -│ ├── openai-contract/ -│ │ ├── upstream/ -│ │ │ └── openapi.yaml # Pinned copy of OpenAI spec (commit SHA tracked) -│ │ ├── overlays/ -│ │ │ ├── hive-support-status.yaml # Overlay: marks support status per endpoint -│ │ │ └── hive-descriptions.yaml # Overlay: Hive-specific descriptions -│ │ ├── generated/ -│ │ │ └── openapi-hive.yaml # Merged spec after overlays applied -│ │ ├── matrix/ -│ │ │ └── support-matrix.json # Machine-readable endpoint classification -│ │ └── scripts/ -│ │ ├── import-spec.sh # Fetch + pin upstream spec -│ │ ├── apply-overlays.sh # Merge overlays into generated spec -│ │ └── generate-matrix.sh # Extract matrix from annotated spec -│ └── sdk-tests/ -│ ├── js/ # Node SDK compatibility tests -│ ├── python/ # Python SDK compatibility tests -│ ├── java/ # Java SDK compatibility tests -│ └── fixtures/ -│ ├── golden/ # Golden response fixtures for regression -│ └── streaming/ # SSE event sequence fixtures -├── apps/ -│ └── edge-api/ -│ ├── cmd/server/main.go # Entry point -│ ├── internal/ -│ │ ├── generated/ # ogen/oapi-codegen output -│ │ ├── handler/ # Business logic (stub responses for Phase 1) -│ │ ├── middleware/ -│ │ │ ├── unsupported.go # Catch-all for unsupported endpoints -│ │ │ └── compat_headers.go # OpenAI compatibility headers -│ │ └── errors/ -│ │ └── openai.go # OpenAI error object builder -│ ├── docs/ -│ │ └── swagger.go # Embedded Swagger UI handler -│ └── go.mod -├── deploy/ -│ └── docker/ -│ ├── docker-compose.yml # Full dev stack -│ ├── docker-compose.override.yml # Watch/dev overrides -│ ├── Dockerfile.edge-api # Go build + air for dev -│ ├── Dockerfile.toolchain # Codegen tools (ogen, oapi-codegen, overlay tools) -│ ├── Dockerfile.sdk-tests-js # Node + OpenAI SDK -│ ├── Dockerfile.sdk-tests-py # Python + OpenAI SDK -│ └── Dockerfile.sdk-tests-java # Java + OpenAI SDK -└── docs/ - └── support-matrix.md # Human-readable endpoint reference table -``` - -### Pattern 1: Contract-First with Overlay - -**What:** Import the upstream OpenAI spec verbatim, apply Hive overlays to annotate support status and customize descriptions, then generate server types from the merged result. -**When to use:** Every time the upstream spec is updated or Hive's support scope changes. -**Example:** -```yaml -# overlays/hive-support-status.yaml (OpenAPI Overlay v1.1.0) -overlay: 1.1.0 -info: - title: Hive Support Status - version: 0.1.0 -actions: - - target: "$.paths['/v1/chat/completions'].post" - update: - x-hive-status: "supported_now" - x-hive-phase: 6 - - target: "$.paths['/v1/images/generations'].post" - update: - x-hive-status: "planned_for_launch" - x-hive-phase: 7 - - target: "$.paths['/v1/fine_tuning/jobs'].post" - update: - x-hive-status: "explicitly_unsupported_at_launch" -``` - -### Pattern 2: Support-Matrix-Driven Middleware - -**What:** A middleware reads the machine-readable support matrix at startup and intercepts requests to paths not marked `supported_now`, returning OpenAI-style error responses. -**When to use:** Every request to the edge API passes through this middleware. -**Example:** -```go -// internal/middleware/unsupported.go -func UnsupportedEndpointMiddleware(matrix *SupportMatrix) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - status := matrix.Lookup(r.Method, r.URL.Path) - if status != StatusSupportedNow { - writeOpenAIError(w, http.StatusNotFound, "unsupported_endpoint", - fmt.Sprintf("The endpoint %s %s is not currently supported.", - r.Method, r.URL.Path)) - return - } - next.ServeHTTP(w, r) - }) - } -} -``` - -### Pattern 3: OpenAI Error Envelope - -**What:** All error responses use the exact OpenAI error JSON shape so SDKs parse them correctly. -**When to use:** Every error path in the edge API. -**Example:** -```go -// internal/errors/openai.go -type OpenAIError struct { - Error OpenAIErrorBody `json:"error"` -} - -type OpenAIErrorBody struct { - Message string `json:"message"` - Type string `json:"type"` - Param *string `json:"param"` - Code *string `json:"code"` -} - -func WriteError(w http.ResponseWriter, httpStatus int, errType, message string, code *string) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(httpStatus) - json.NewEncoder(w).Encode(OpenAIError{ - Error: OpenAIErrorBody{ - Message: message, - Type: errType, - Code: code, - }, - }) -} -``` - -### Anti-Patterns to Avoid - -- **Forking the OpenAI spec directly:** Edit overlays instead; direct edits create unmergeable diffs when the upstream spec updates. -- **Generating code for the entire spec at once:** The OpenAI spec is ~1.3MB with hundreds of endpoints; generate only what Hive needs per phase, using overlays to scope. -- **Mixing support status into runtime config:** The support matrix should be a build-time artifact derived from the spec overlays, not a runtime config file that can drift from the published docs. -- **Testing with curl instead of official SDKs:** curl tests prove HTTP shape but miss SDK-specific parsing, retry behavior, and type validation. Always test with the real SDKs. - -## Don't Hand-Roll - -| Problem | Don't Build | Use Instead | Why | -|---------|-------------|-------------|-----| -| OpenAI request/response types | Hand-written Go structs for 100+ endpoints | `ogen` or `oapi-codegen` from the official spec | Types drift from spec; codegen guarantees structural correctness | -| OpenAPI spec customization | Fork and edit the OpenAI YAML directly | OpenAPI Overlay Specification v1.1.0 | Overlays are additive and composable; forks create merge conflicts | -| Swagger documentation UI | Custom docs page | `swaggest/swgui` embedded or Swagger UI static assets | Battle-tested rendering with try-it-out functionality | -| Go hot reload | Custom file watcher + rebuild script | `air` v1.64.5 inside container | Handles build errors, binary restart, and ignore patterns | -| Dev environment orchestration | Shell scripts for each service | Docker Compose with `develop.watch` | Declarative sync/rebuild/restart with native file watching | - -**Key insight:** Phase 1 is almost entirely a codegen, classification, and testing problem. The only custom code is the unsupported-endpoint middleware, the error envelope helper, and the compatibility header middleware. Everything else should be generated or imported. - -## Common Pitfalls - -### Pitfall 1: ogen Fails on Complex OpenAI Spec Constructs - -**What goes wrong:** The OpenAI spec uses deeply nested oneOf/anyOf, polymorphic request bodies, and complex discriminator patterns. ogen may fail to generate valid Go code for some of these. -**Why it happens:** The spec was written for Stainless (OpenAI's codegen tool), not for general-purpose generators. -**How to avoid:** Run ogen against the full spec early in Wave 0. Identify failing endpoints. For those, either: (a) use oapi-codegen as fallback, (b) create a trimmed overlay that removes the problematic constructs, or (c) hand-write types for a small number of complex endpoints. Document which strategy was used per endpoint. -**Warning signs:** ogen exits with errors referencing specific schema paths; generated code has compilation errors. - -### Pitfall 2: Support Matrix Drifts from Runtime Behavior - -**What goes wrong:** The published matrix says an endpoint is unsupported, but the middleware lets requests through (or vice versa). -**Why it happens:** The matrix is maintained manually and the middleware reads a different source of truth. -**How to avoid:** Generate the middleware's route table from the same machine-readable matrix that produces the documentation. Single source of truth. Test that every matrix entry matches runtime behavior. -**Warning signs:** SDK tests pass for endpoints that should be blocked; documentation shows different status than runtime. - -### Pitfall 3: SDK Version Skew Breaks Tests - -**What goes wrong:** Compatibility tests pass with one SDK version but fail with the latest because OpenAI added new required fields or changed defaults. -**Why it happens:** SDK versions are not pinned, or golden fixtures were recorded against an older API version. -**How to avoid:** Pin SDK versions in lockfiles. Record the OpenAI API version each golden fixture targets. Re-record fixtures when upgrading SDKs. -**Warning signs:** Tests break after dependency updates without any Hive code changes. - -### Pitfall 4: Streaming Tests Are Flaky or Incomplete - -**What goes wrong:** SSE event ordering, chunk shape, or terminal event tests pass intermittently or only cover the happy path. -**Why it happens:** Streaming tests are harder to write deterministically; teams skip error/interruption cases. -**How to avoid:** Use golden SSE event sequences as fixtures. Test: (a) normal completion, (b) chunk shape per event, (c) terminal `[DONE]` event, (d) mid-stream error, (e) client disconnect. Use deterministic stub responses, not live upstream calls. -**Warning signs:** Tests pass locally but fail in CI; no tests for error or interruption cases. - -### Pitfall 5: Docker Dev Environment Is Slow or Fragile - -**What goes wrong:** Go compilation inside Docker is slow; file sync misses changes; containers need manual restart. -**Why it happens:** Volume mounts without proper caching, missing Go module cache persistence, or misconfigured watch paths. -**How to avoid:** Use Docker Compose `develop.watch` with `sync` action for source files and `rebuild` for dependency files. Mount a named volume for the Go module cache. Use air inside the container for fast incremental rebuilds. -**Warning signs:** >10 second rebuild cycle; developers bypass Docker and install Go locally. - -## Code Examples - -### Docker Compose with Watch for Go Development - -```yaml -# deploy/docker/docker-compose.yml -services: - edge-api: - build: - context: ../../ - dockerfile: deploy/docker/Dockerfile.edge-api - ports: - - "8080:8080" - volumes: - - gomodcache:/go/pkg/mod - - gobuildcache:/root/.cache/go-build - develop: - watch: - - action: sync - path: ./apps/edge-api - target: /app/apps/edge-api - - action: sync - path: ./packages/openai-contract/generated - target: /app/packages/openai-contract/generated - - action: rebuild - path: ./apps/edge-api/go.mod - - toolchain: - build: - context: ../../ - dockerfile: deploy/docker/Dockerfile.toolchain - profiles: ["tools"] - volumes: - - ../../:/workspace - - gomodcache:/go/pkg/mod - -volumes: - gomodcache: - gobuildcache: -``` - -### Dockerfile for Go Edge API with Air - -```dockerfile -# deploy/docker/Dockerfile.edge-api -FROM golang:1.24-alpine AS base -RUN go install github.com/air-verse/air@v1.64.5 -WORKDIR /app -COPY go.work go.work.sum ./ -COPY apps/edge-api/go.mod apps/edge-api/go.sum ./apps/edge-api/ -COPY packages/ ./packages/ -RUN cd apps/edge-api && go mod download -COPY apps/edge-api/ ./apps/edge-api/ -CMD ["air", "-c", "apps/edge-api/.air.toml"] -``` - -### SDK Compatibility Test Structure (Node) - -```typescript -// packages/sdk-tests/js/tests/errors/unsupported-endpoint.test.ts -import OpenAI from "openai"; -import { describe, it, expect } from "vitest"; - -const client = new OpenAI({ - baseURL: process.env.HIVE_BASE_URL ?? "http://localhost:8080/v1", - apiKey: "test-key", -}); - -describe("unsupported endpoint returns OpenAI-style error", () => { - it("returns 404 with error object for fine-tuning", async () => { - try { - await client.fineTuning.jobs.create({ - model: "gpt-4o", - training_file: "file-abc123", - }); - expect.unreachable("should have thrown"); - } catch (err) { - expect(err).toBeInstanceOf(OpenAI.NotFoundError); - expect(err.status).toBe(404); - expect(err.error?.error?.type).toBe("unsupported_endpoint"); - expect(err.error?.error?.message).toContain("not currently supported"); - } - }); -}); -``` - -### OpenAI Compatibility Headers Middleware - -```go -// internal/middleware/compat_headers.go -func CompatHeaders(requestIDGenerator func() string) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - reqID := requestIDGenerator() - - w.Header().Set("x-request-id", reqID) - w.Header().Set("openai-version", "2020-10-01") - - rec := &responseRecorder{ResponseWriter: w} - next.ServeHTTP(rec, r) - - w.Header().Set("openai-processing-ms", - fmt.Sprintf("%d", time.Since(start).Milliseconds())) - }) - } -} -``` - -## State of the Art - -| Old Approach | Current Approach | When Changed | Impact | -|--------------|------------------|--------------|--------| -| OpenAI Chat Completions as primary API | Responses API is the new recommended API | 2025 | Hive must support both; Responses API will eventually supersede Chat Completions | -| Assistants API for multi-step workflows | Responses API with tools | Deprecated Aug 2026 | Do NOT implement Assistants API; it is being removed | -| Manual OpenAPI spec editing | OpenAPI Overlay Spec v1.1.0 | Oct 2024 (v1.0.0) | Use overlays instead of forking specs | -| `deepmap/oapi-codegen` import path | `oapi-codegen/oapi-codegen` v2 | May 2024 | Use the new import path `github.com/oapi-codegen/oapi-codegen/v2` | -| Docker volume mounts for dev | Docker Compose `develop.watch` | 2024 GA | Use watch actions (sync/rebuild/restart) instead of raw bind mounts | - -**Deprecated/outdated:** -- **Assistants API**: Being removed August 2026. Do not implement. -- **Completions API** (`/v1/completions`): Legacy. Still in spec but deprecated in favor of Chat Completions and Responses. -- **`deepmap/oapi-codegen`**: Old org name. Use `github.com/oapi-codegen/oapi-codegen/v2`. - -## Open Questions - -1. **How well does ogen handle the full OpenAI spec?** - - What we know: ogen supports oneOf/anyOf with discriminator inference and generates strongly-typed code. It is actively maintained (v1.20.2, released 2026-03-27). - - What's unclear: Whether the ~1.3MB OpenAI spec with its complex polymorphic types generates cleanly without errors. No public evidence of ogen being used against this specific spec. - - Recommendation: Run ogen against the spec in Wave 0 as a validation task. Have oapi-codegen ready as fallback. Document which endpoints need which generator. - -2. **What is the exact list of public non-org/admin endpoints to classify?** - - What we know: Major families include responses, chat/completions, completions, embeddings, images, audio, files, uploads, batches, vector_stores, fine_tuning, realtime, videos, moderations, models. - - What's unclear: The exact path-by-path inventory needs to be extracted from the spec programmatically. - - Recommendation: Parse the imported spec YAML to extract all paths and methods. Classify each against the four-status model. This is a plan task, not a pre-research activity. - -3. **Which OpenAI response headers do SDKs depend on?** - - What we know: `x-request-id`, `openai-processing-ms`, `openai-version`, and `openai-organization` are commonly referenced. SDKs use `x-request-id` for error reporting. - - What's unclear: Whether SDKs fail hard on missing headers or degrade gracefully. - - Recommendation: Test with missing headers in the SDK compatibility harness. Add headers incrementally based on what breaks. - -## Validation Architecture - -### Test Framework - -| Property | Value | -|----------|-------| -| Framework (Go) | `go test` with standard library | -| Framework (JS SDK tests) | Vitest 3.x | -| Framework (Python SDK tests) | pytest 8.x | -| Framework (Java SDK tests) | JUnit 5 + Gradle | -| Config file | None yet -- Wave 0 | -| Quick run command | `docker compose run --rm edge-api go test ./... -short` | -| Full suite command | `docker compose run --rm sdk-tests-js npm test && docker compose run --rm sdk-tests-py pytest && docker compose run --rm sdk-tests-java ./gradlew test && docker compose run --rm edge-api go test ./...` | - -### Phase Requirements to Test Map - -| Req ID | Behavior | Test Type | Automated Command | File Exists? | -|--------|----------|-----------|-------------------|-------------| -| COMP-01 | Official SDKs work with base URL change | integration | `docker compose run --rm sdk-tests-js npm test` | No -- Wave 0 | -| COMP-01 | Official SDKs work with base URL change | integration | `docker compose run --rm sdk-tests-py pytest` | No -- Wave 0 | -| COMP-01 | Official SDKs work with base URL change | integration | `docker compose run --rm sdk-tests-java ./gradlew test` | No -- Wave 0 | -| COMP-02 | Error responses match OpenAI shape | unit | `docker compose run --rm edge-api go test ./internal/errors/... -run TestOpenAIError` | No -- Wave 0 | -| COMP-02 | Unsupported endpoints return correct errors | integration | `docker compose run --rm sdk-tests-js npm test -- --grep "unsupported"` | No -- Wave 0 | -| COMP-02 | Compatibility headers present | integration | `docker compose run --rm sdk-tests-js npm test -- --grep "headers"` | No -- Wave 0 | -| COMP-03 | Swagger UI serves and renders spec | smoke | `curl -sf http://localhost:8080/docs/ | grep -q swagger-ui` | No -- Wave 0 | -| API-08 | All non-supported public endpoints return structured errors | integration | `docker compose run --rm sdk-tests-js npm test -- --grep "unsupported"` | No -- Wave 0 | - -### Sampling Rate - -- **Per task commit:** `docker compose run --rm edge-api go test ./... -short` -- **Per wave merge:** Full suite across all SDK languages + Go unit tests -- **Phase gate:** Full suite green before `/gsd:verify-work` - -### Wave 0 Gaps - -- [ ] `deploy/docker/docker-compose.yml` -- Docker Compose orchestration for all services -- [ ] `deploy/docker/Dockerfile.edge-api` -- Go dev image with air -- [ ] `deploy/docker/Dockerfile.toolchain` -- Codegen tools container -- [ ] `deploy/docker/Dockerfile.sdk-tests-js` -- Node + OpenAI SDK + Vitest -- [ ] `deploy/docker/Dockerfile.sdk-tests-py` -- Python + OpenAI SDK + pytest -- [ ] `deploy/docker/Dockerfile.sdk-tests-java` -- Java + OpenAI SDK + JUnit/Gradle -- [ ] `apps/edge-api/go.mod` -- Go module initialization -- [ ] `packages/sdk-tests/js/package.json` -- JS test project with vitest + openai SDK -- [ ] `packages/sdk-tests/python/pyproject.toml` -- Python test project with pytest + openai SDK -- [ ] `packages/sdk-tests/java/build.gradle` -- Java test project with JUnit + openai SDK -- [ ] `packages/openai-contract/upstream/openapi.yaml` -- Imported spec (pinned SHA) - -## Sources - -### Primary (HIGH confidence) - -- [github.com/openai/openai-openapi](https://github.com/openai/openai-openapi) - Official spec repo; confirmed `manual_spec` branch with `openapi.yaml` (~1.3MB); last tagged release 2.0.0 (2023-06-19) but branch actively maintained -- [github.com/ogen-go/ogen](https://github.com/ogen-go/ogen) - Confirmed v1.20.2 released 2026-03-27; supports oneOf/anyOf with discriminator inference -- [github.com/oapi-codegen/oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) - Confirmed v2.6.0 released 2026-02-27; supports chi/echo/net-http servers -- [github.com/openai/openai-node](https://github.com/openai/openai-node) - Confirmed v6.33.0 released 2026-03-25 -- [github.com/openai/openai-python](https://github.com/openai/openai-python) - Confirmed v2.30.0 released 2026-03-25 -- [github.com/openai/openai-java](https://github.com/openai/openai-java) - Confirmed v4.30.0 released 2026-03-25 -- [github.com/air-verse/air](https://github.com/air-verse/air) - Confirmed v1.64.5 released 2026-02-02 -- [Docker Compose Watch docs](https://docs.docker.com/compose/how-tos/file-watch/) - develop.watch with sync/rebuild/restart actions -- [OpenAPI Overlay Spec v1.1.0](https://spec.openapis.org/overlay/latest.html) - Official overlay mechanism for spec augmentation - -### Secondary (MEDIUM confidence) - -- [OpenAI API error codes guide](https://platform.openai.com/docs/guides/error-codes) - Error object shape `{error: {message, type, param, code}}`; HTTP status code mapping -- [OpenAI API Reference](https://developers.openai.com/api/reference/) - Full endpoint listing including responses, chat/completions, embeddings, images, audio, files, etc. -- [OpenAI Assistants deprecation](https://learn.microsoft.com/en-gb/answers/questions/5571874/openai-assistants-api-will-be-deprecated-in-august) - Assistants API deprecated August 2026 -- [swaggest/swgui](https://github.com/swaggest/swgui) - Embedded Swagger UI for Go with native embed support - -### Tertiary (LOW confidence) - -- [OpenAI gpt-oss verification cookbook](https://developers.openai.com/cookbook/articles/gpt-oss/verifying-implementations/) - Referenced in project research for API shape verification patterns; could not fetch content directly; verify methodology during implementation - -## Metadata - -**Confidence breakdown:** -- Standard stack: HIGH - All versions confirmed via GitHub releases API within 24 hours -- Architecture: HIGH - Project structure follows prior research (ARCHITECTURE.md, STACK.md) validated against current tooling -- Pitfalls: HIGH - Primary pitfall (compatibility by approximation) directly addressed by contract-first approach; codegen risk is the main unknown -- Validation: MEDIUM - Test framework choices are standard but none exist yet; Wave 0 setup is significant - -**Research date:** 2026-03-28 -**Valid until:** 2026-04-28 (stable domain; spec and SDK versions move frequently but patterns are stable) diff --git a/.planning/phases/01-contract-compatibility-harness/01-VALIDATION.md b/.planning/phases/01-contract-compatibility-harness/01-VALIDATION.md deleted file mode 100644 index d9b26e0c1..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-VALIDATION.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -phase: 1 -slug: contract-compatibility-harness -status: approved -nyquist_compliant: true -wave_0_complete: true -created: 2026-03-28 -updated: 2026-03-29 -verified: 2026-03-29 ---- - -# Phase 1 — Validation Strategy - -> Retroactive Nyquist audit completed after plans `01-01` through `01-04` and final phase re-verification. - ---- - -## Test Infrastructure - -| Property | Value | -|----------|-------| -| **Framework (Go runtime/contracts)** | `go test` via Docker toolchain | -| **Framework (JS SDK)** | Vitest `3.2.4` | -| **Framework (Python SDK)** | pytest `9.0.2` | -| **Framework (Java SDK)** | JUnit 5 via Gradle `8.14.4` | -| **Framework (Contract generator)** | `python3 -m unittest` | -| **Config files** | `packages/sdk-tests/js/vitest.config.ts`, `packages/sdk-tests/python/pyproject.toml`, `packages/sdk-tests/java/build.gradle` | -| **Quick run command** | `docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace/apps/edge-api && go test ./internal/errors/... ./internal/matrix/... ./internal/middleware/... ./docs/... ./cmd/server/... -count=1'` | -| **Full suite command** | `docker compose -f deploy/docker/docker-compose.yml up -d edge-api && docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-js && docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-py && docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-java && docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh' && docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace/apps/edge-api && go test ./internal/errors/... ./internal/matrix/... ./internal/middleware/... ./docs/... ./cmd/server/... -count=1' && docker compose -f deploy/docker/docker-compose.yml down` | -| **Estimated runtime** | ~60 seconds | - ---- - -## Sampling Rate - -- **After every task commit:** Run the quick Go contract/docs package suite in the toolchain container. -- **After every plan wave:** Run the full SDK harness, generator sync, and Go contract/docs package suite. -- **Before `$gsd-verify-work`:** Full suite must be green. -- **Max feedback latency:** 120 seconds. - ---- - -## Per-Task Verification Map - -| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status | -|---------|------|------|-------------|-----------|-------------------|-------------|--------| -| 01-01-01 | 01 | 1 | API-08 | smoke | `cd /home/sakib/hive && test -f go.work && test -f apps/edge-api/go.mod && test -f apps/edge-api/cmd/server/main.go && test -f apps/edge-api/.air.toml && test -f .gitignore && echo PASS` | ✅ | ✅ green | -| 01-01-02 | 01 | 1 | API-08 | smoke | `docker compose -f deploy/docker/docker-compose.yml config --quiet` | ✅ | ✅ green | -| 01-01-03 | 01 | 1 | API-08 | integration | `docker compose -f deploy/docker/docker-compose.yml build edge-api && docker compose -f deploy/docker/docker-compose.yml up -d edge-api && curl -sf http://localhost:8080/health && curl -sf http://localhost:8080/v1/models && docker compose -f deploy/docker/docker-compose.yml down` | ✅ | ✅ green | -| 01-02-01 | 02 | 2 | COMP-02, API-08 | unit | `cd /workspace/apps/edge-api && go test ./internal/errors/... ./internal/matrix/... -count=1` | ✅ | ✅ green | -| 01-02-02 | 02 | 2 | COMP-02, COMP-03, API-08 | unit | `cd /workspace/apps/edge-api && go test ./internal/middleware/... -count=1 && go build ./cmd/server` | ✅ | ✅ green | -| 01-03-01 | 03 | 3 | COMP-01, COMP-02 | integration | `docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-js` | ✅ | ✅ green | -| 01-03-02 | 03 | 3 | COMP-01, COMP-02 | integration | `docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-py && docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-java` | ✅ | ✅ green | -| 01-03-03 | 03 | 3 | COMP-01, COMP-02, COMP-03, API-08 | end-to-end | `docker compose -f deploy/docker/docker-compose.yml up -d edge-api && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-js && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-py && docker compose -f deploy/docker/docker-compose.yml run --rm sdk-tests-java && docker compose -f deploy/docker/docker-compose.yml run --rm edge-api go test ./... -v && curl -sf http://localhost:8080/docs/ | grep -q swagger-ui && docker compose -f deploy/docker/docker-compose.yml down` | ✅ | ✅ green | -| 01-04-01 | 04 | 4 | COMP-03 | unit/integration | `python3 -m unittest packages.openai-contract.scripts.test_sync_hive_contract && docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh'` | ✅ | ✅ green | -| 01-04-02 | 04 | 4 | COMP-03 | unit | `docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace/apps/edge-api && go test ./docs/... ./cmd/server/... -count=1'` | ✅ | ✅ green | - -*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* - ---- - -## Wave 0 Requirements - -Existing infrastructure covers all phase requirements. No deferred Wave 0 validation scaffolding remains. - ---- - -## Manual-Only Verifications - -| Behavior | Requirement | Why Manual | Test Instructions | -|----------|-------------|------------|-------------------| -| Hot-reload after editing a Go file in the running dev container | API-08 supporting developer workflow | File-watch timing depends on local Docker sync behavior and is not part of the launch-surface compatibility contract. Automated coverage already proves the container boots, rebuilds, and serves the contract surface. | Run `docker compose -f deploy/docker/docker-compose.yml up edge-api`, edit `apps/edge-api/cmd/server/main.go`, then confirm a rebuild in `docker compose logs -f edge-api`. | - ---- - -## Validation Sign-Off - -- [x] All phase requirements have automated verification. -- [x] Sampling continuity: no 3 consecutive tasks without automated verify. -- [x] No Wave 0 dependencies remain. -- [x] No watch-mode flags in quick or full validation commands. -- [x] Feedback latency is within 120 seconds for the current suite. -- [x] `nyquist_compliant: true` set in frontmatter. - -**Approval:** approved 2026-03-29 - ---- - -## Validation Audit 2026-03-29 - -| Metric | Count | -|--------|-------| -| Gaps found | 0 | -| Resolved | 0 | -| Escalated | 0 | - -Evidence used for this audit: - -- `python3 -m unittest packages.openai-contract.scripts.test_sync_hive_contract` passed: 3 tests. -- `docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-js` passed: 6 files, 11 tests. -- `docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-py` passed: 9 tests. -- `docker compose -f deploy/docker/docker-compose.yml run --rm -T sdk-tests-java` passed: Gradle `BUILD SUCCESSFUL`. -- `docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace && packages/openai-contract/scripts/generate-matrix.sh'` exited `0`. -- `docker compose -f deploy/docker/docker-compose.yml run --rm -T toolchain sh -lc 'cd /workspace/apps/edge-api && go test ./internal/errors/... ./internal/matrix/... ./internal/middleware/... ./docs/... ./cmd/server/... -count=1'` exited `0`. -- `.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md` already recorded phase verification as passed with `7/7` must-haves on `2026-03-29`. diff --git a/.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md b/.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md deleted file mode 100644 index 2166c93df..000000000 --- a/.planning/phases/01-contract-compatibility-harness/01-VERIFICATION.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -phase: 01-contract-compatibility-harness -verified: 2026-03-29T03:45:34Z -status: passed -score: 7/7 must-haves verified -gaps: [] ---- - -# Phase 01: Contract Compatibility Harness Verification Report - -**Phase Goal:** Make Hive's public API a verified compatibility product instead of an approximation, on top of a Docker-only developer workflow. -**Verified:** 2026-03-29T03:45:34Z -**Status:** passed -**Re-verification:** Yes - targeted gap-closure verification after plan `01-04` - -## Goal Achievement - -Phase 01 previously had one open gap: `COMP-03`, where `/docs` still served the raw upstream OpenAI spec. Plan `01-04` replaced that placeholder docs path with a generated Hive contract derived from `support-matrix.json`, added regression coverage, and rewired the runtime image to serve the generated artifact by default. - -This report re-verifies the former gap with fresh evidence and confirms the rest of the phase remains satisfied. Where a truth relies on code that `01-04` did not modify, that is called out explicitly as an inference from unchanged artifacts plus the prior successful verification. - -## Fresh Session Evidence - -- `python3 -m unittest packages.openai-contract.scripts.test_sync_hive_contract` passed: 3 tests -- `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain "cd /workspace && packages/openai-contract/scripts/generate-matrix.sh"` passed and regenerated 97 public operations -- `docker compose -f deploy/docker/docker-compose.yml run --rm toolchain "cd /workspace/apps/edge-api && go test ./docs/... ./cmd/server/... -v -count=1"` passed -- `docker compose -f deploy/docker/docker-compose.yml up -d --build edge-api` rebuilt and started the runtime image with the generated contract artifact copied into it -- `docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'body="$(wget -qO- http://localhost:8080/docs/)"; ...'` returned `PASS`, confirming Swagger UI still loads `./openapi.yaml` -- `docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'spec="$(wget -qO- http://localhost:8080/docs/openapi.yaml)"; ...'` returned `PASS`, confirming the served spec contains `url: /v1`, contains `x-hive-status:`, and does not contain `https://api.openai.com/v1` -- `docker compose -f deploy/docker/docker-compose.yml exec -T edge-api sh -lc 'wget -S -O- http://localhost:8080/v1/models ...'` returned `200 OK` with `X-Request-Id`, `Openai-Version: 2020-10-01`, and `Openai-Processing-Ms` - -## Observable Truths - -| # | Truth | Status | Evidence | -| --- | --- | --- | --- | -| 1 | Docker-only development and verification workflows exist for the edge API, toolchain, and SDK harnesses. | ✓ VERIFIED | Fresh toolchain and edge-api Docker runs succeeded. `deploy/docker/docker-compose.yml` still defines `edge-api`, `toolchain`, and the SDK test services. | -| 2 | The running edge API exposes `/v1/models` and returns OpenAI compatibility headers. | ✓ VERIFIED | Fresh runtime probe returned `200 OK` plus `X-Request-Id`, `Openai-Version`, and `Openai-Processing-Ms`. | -| 3 | Error responses use an OpenAI-style envelope and classify unsupported endpoints explicitly. | ✓ VERIFIED | Inference from unchanged `apps/edge-api/internal/errors/openai.go`, `apps/edge-api/internal/middleware/unsupported.go`, and their existing verified test coverage; plan `01-04` did not modify these paths. | -| 4 | Public endpoints are fully classified and runtime enforcement is matrix-driven. | ✓ VERIFIED | Fresh generator run succeeded from `support-matrix.json`, and runtime still loads `SUPPORT_MATRIX_PATH` through `matrix.LoadMatrix(...)`; plan `01-04` preserved the matrix-driven enforcement path. | -| 5 | Official OpenAI JS, Python, and Java SDKs work against Hive for the supported launch endpoint by changing only base URL and API key. | ✓ VERIFIED | Inference from unchanged plan `01-03` harness code and the prior passing SDK verification; plan `01-04` did not touch the SDK suites or `/v1/models` handler behavior. | -| 6 | The compatibility harness captures regressions for supported responses, unsupported responses, and headers. | ✓ VERIFIED | Inference from unchanged JS/Python/Java harness files and golden fixtures, plus new docs-route regression tests added in `apps/edge-api/docs/swagger_test.go` and `apps/edge-api/cmd/server/main_test.go`. | -| 7 | Swagger/OpenAPI docs at `/docs` match Hive's contract surface, not just the upstream OpenAI spec. | ✓ VERIFIED | Fresh container probe of `/docs/openapi.yaml` passed. The served artifact now comes from `packages/openai-contract/generated/hive-openapi.yaml`, advertises `url: /v1`, includes `x-hive-status`/`x-hive-phase`, and excludes the upstream base URL and out-of-scope organization/admin paths. | - -**Score:** 7/7 truths verified - -## Required Artifacts - -| Artifact | Status | Details | -| --- | --- | --- | -| `packages/openai-contract/scripts/sync_hive_contract.py` | ✓ VERIFIED | Generates the published Hive OpenAPI contract and support-matrix markdown from `support-matrix.json`. | -| `packages/openai-contract/scripts/generate-matrix.sh` | ✓ VERIFIED | Real POSIX-sh entrypoint; no placeholder text remains; Docker toolchain verification passed. | -| `packages/openai-contract/generated/hive-openapi.yaml` | ✓ VERIFIED | Generated artifact contains `url: /v1`, `x-hive-status`, and no `https://api.openai.com/v1` or `/organization/` strings. | -| `docs/support-matrix.md` | ✓ VERIFIED | Generated from `support-matrix.json` and clearly marked with provenance. | -| `apps/edge-api/cmd/server/main.go` | ✓ VERIFIED | `OPENAPI_SPEC_PATH` now defaults to `/app/packages/openai-contract/generated/hive-openapi.yaml`. | -| `apps/edge-api/docs/swagger_test.go` | ✓ VERIFIED | Covers `/docs/`, `/docs/openapi.yaml`, and missing-spec behavior. | -| `apps/edge-api/cmd/server/main_test.go` | ✓ VERIFIED | Guards the default generated-spec path and env override behavior. | -| `deploy/docker/Dockerfile.edge-api` | ✓ VERIFIED | Copies `packages/openai-contract/generated/hive-openapi.yaml` into the runtime image instead of the raw upstream spec. | -| `deploy/docker/docker-compose.override.yml` | ✓ VERIFIED | Syncs `../../packages/openai-contract` into `/app/packages/openai-contract` for local Docker development. | -| `deploy/docker/Dockerfile.toolchain` | ✓ VERIFIED | Includes `py3-yaml`, allowing Docker verification to run the Python generator. | - -## Requirements Coverage - -| Requirement | Description | Status | Evidence | -| --- | --- | --- | --- | -| `COMP-01` | Official OpenAI JS/Python/Java SDKs work against Hive by changing base URL and API key | ✓ SATISFIED | Unchanged from the previously verified plan `01-03` harness and not regressed by `01-04`. | -| `COMP-02` | OpenAI-style status codes, error objects, and compatibility headers | ✓ SATISFIED | Fresh `/v1/models` probe still returns compatibility headers; error/middleware paths were unchanged in `01-04`. | -| `COMP-03` | Swagger/OpenAPI docs match the Hive public API contract and supported launch surface | ✓ SATISFIED | Fresh `/docs/openapi.yaml` runtime probe and generator/toolchain verification confirm the served contract is Hive-specific and matrix-derived. | -| `API-08` | Unsupported public non-org/admin endpoints are classified and return explicit unsupported responses | ✓ SATISFIED | Matrix-driven classification remains intact and the generated contract now mirrors that public classification in docs as well. | - -## Issues Encountered - -None during final phase verification. The only prior gap (`COMP-03`) is resolved. - -## Conclusion - -Phase 01 now satisfies its original phase goal. The compatibility harness, runtime contract enforcement, and generated documentation surface are aligned: the runtime enforces `support-matrix.json`, the published docs are derived from the same source, and the built container serves the generated Hive contract by default. - ---- - -_Verified: 2026-03-29T03:45:34Z_ -_Verifier: Codex (manual phase re-verification after agent fallback)_ diff --git a/.planning/phases/02-identity-account-foundation/02-01-PLAN.md b/.planning/phases/02-identity-account-foundation/02-01-PLAN.md deleted file mode 100644 index 6fd861e7f..000000000 --- a/.planning/phases/02-identity-account-foundation/02-01-PLAN.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: 01 -type: execute -wave: 1 -depends_on: [] -files_modified: - - .env.example - - go.work - - go.work.sum - - deploy/docker/docker-compose.yml - - deploy/docker/docker-compose.override.yml - - deploy/docker/Dockerfile.control-plane - - apps/control-plane/.air.toml - - apps/control-plane/go.mod - - apps/control-plane/go.sum - - apps/control-plane/cmd/server/main.go - - apps/control-plane/internal/platform/config/config.go - - apps/control-plane/internal/platform/db/pool.go - - apps/control-plane/internal/platform/http/router.go - - supabase/migrations/20260328_01_identity_foundation.sql -autonomous: true -requirements: - - AUTH-01 - - AUTH-02 -must_haves: - truths: - - "Hosted Supabase remains the only password and recovery system; Hive adds no parallel credential store." - - "Phase 2 starts from a Docker-only control-plane service that shares the repo workflow with the existing edge API." - - "The first tenancy migration creates accounts, memberships, invitations, and core profiles before console features build on top." - artifacts: - - path: ".env.example" - provides: "Shared environment contract for Supabase, control-plane, and later web-console work." - contains: "CONTROL_PLANE_PORT=8081" - - path: "deploy/docker/Dockerfile.control-plane" - provides: "Docker-only Go dev image for the new service." - contains: "EXPOSE 8081" - - path: "apps/control-plane/cmd/server/main.go" - provides: "Control-plane entrypoint with `/health` and router wiring." - contains: "ListenAndServe" - - path: "supabase/migrations/20260328_01_identity_foundation.sql" - provides: "Workspace, membership, invitation, and core profile schema." - contains: "create table public.accounts" - key_links: - - from: "deploy/docker/docker-compose.yml" - to: "deploy/docker/Dockerfile.control-plane" - via: "Compose builds and runs the new control-plane container." - pattern: "Dockerfile\\.control-plane" - - from: "apps/control-plane/cmd/server/main.go" - to: "apps/control-plane/internal/platform/http/router.go" - via: "The entrypoint mounts the shared router returned by the platform HTTP package." - pattern: "NewRouter" ---- - - -Create the Docker-only control-plane foundation, shared environment contract, and first Supabase tenancy migration for Phase 2. - -Purpose: Later identity, console, and profile plans all depend on a running control-plane service plus a durable account schema in Supabase Postgres. -Output: A new `apps/control-plane` module, Docker wiring on port `8081`, repo-level env contract, and the Phase 2 tenancy migration. - - - -@/home/sakib/.codex/get-shit-done/workflows/execute-plan.md -@/home/sakib/.codex/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/02-identity-account-foundation/02-CONTEXT.md -@.planning/phases/02-identity-account-foundation/02-RESEARCH.md -@.planning/phases/02-identity-account-foundation/02-VALIDATION.md - - - - - - Task 1: Create the control-plane module, Docker image, and shared environment contract - - .env.example, - go.work, - go.work.sum, - deploy/docker/docker-compose.yml, - deploy/docker/docker-compose.override.yml, - deploy/docker/Dockerfile.control-plane, - apps/control-plane/.air.toml, - apps/control-plane/go.mod, - apps/control-plane/go.sum, - apps/control-plane/cmd/server/main.go, - apps/control-plane/internal/platform/config/config.go, - apps/control-plane/internal/platform/db/pool.go, - apps/control-plane/internal/platform/http/router.go - - - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md, - deploy/docker/docker-compose.yml, - deploy/docker/docker-compose.override.yml, - go.work, - apps/edge-api/go.mod - - -1. Create `.env.example` at the repo root with exactly these keys: - - `SUPABASE_URL=https://.supabase.co` - - `SUPABASE_ANON_KEY=` - - `SUPABASE_SERVICE_ROLE_KEY=` - - `SUPABASE_DB_URL=` - - `CONTROL_PLANE_PORT=8081` - - `CONTROL_PLANE_BASE_URL=http://localhost:8081` - - `NEXT_PUBLIC_SUPABASE_URL=https://.supabase.co` - - `NEXT_PUBLIC_SUPABASE_ANON_KEY=` - - `NEXT_PUBLIC_APP_URL=http://localhost:3000` - -2. Update `go.work` to add `./apps/control-plane` alongside `./apps/edge-api`. Generate `go.work.sum` if the new module introduces entries. - -3. Create `deploy/docker/Dockerfile.control-plane` using: - - base image `golang:1.24-alpine` - - package install `git` - - tool install `github.com/air-verse/air@v1.64.5` - - `WORKDIR /app` - - copy `go.work`, `go.work.sum`, `apps/control-plane/go.mod`, and `apps/control-plane/go.sum` - - `RUN cd apps/control-plane && go mod download` - - copy `apps/control-plane/` - - `EXPOSE 8081` - - `CMD ["air", "-c", "apps/control-plane/.air.toml"]` - -4. Update `deploy/docker/docker-compose.yml` to add a `control-plane` service that: - - builds from `deploy/docker/Dockerfile.control-plane` - - publishes `8081:8081` - - mounts named volumes for `/go/pkg/mod` and `/root/.cache/go-build` - - passes `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `SUPABASE_DB_URL`, and `CONTROL_PLANE_PORT` - - runs a healthcheck `wget --no-verbose --tries=1 --spider http://localhost:8081/health` - -5. Update `deploy/docker/docker-compose.override.yml` so `control-plane` gets a `develop.watch` sync rule for `../../apps/control-plane` plus a rebuild rule for `../../apps/control-plane/go.mod`. - -6. Create `apps/control-plane/.air.toml` with: - - `root = "/app/apps/control-plane"` - - build command `go build -o ./tmp/main ./cmd/server` - - binary `./tmp/main` - - include extensions `["go", "toml"]` - - exclude `["tmp", "vendor", "testdata"]` - -7. Create `apps/control-plane/go.mod` with module path `github.com/sakibsadmanshajib/hive/apps/control-plane`, Go `1.24`, and `pgx/v5` for database access. - -8. Create `apps/control-plane/cmd/server/main.go` plus platform support packages so the service: - - loads config from env - - opens a `pgxpool` connection - - serves `/health` returning `{"status":"ok"}` - - mounts the router returned by `internal/platform/http/router.go` - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml config --services | grep -qx "control-plane" && rg -n "CONTROL_PLANE_PORT=8081|NEXT_PUBLIC_SUPABASE_URL=https://\\.supabase\\.co" .env.example && rg -n "EXPOSE 8081|ListenAndServe|/health" deploy/docker/Dockerfile.control-plane apps/control-plane/cmd/server/main.go - - - - go.work contains `./apps/control-plane` - - deploy/docker/Dockerfile.control-plane contains `EXPOSE 8081` - - deploy/docker/docker-compose.yml contains `control-plane` and `8081:8081` - - apps/control-plane/cmd/server/main.go contains `ListenAndServe` and `/health` - - .env.example contains `CONTROL_PLANE_BASE_URL=http://localhost:8081` - - Control-plane module, Docker wiring, and the shared env contract exist and are ready for identity APIs. - - - - Task 2: Add the Phase 2 tenancy migration - - supabase/migrations/20260328_01_identity_foundation.sql - - - .planning/ROADMAP.md, - .planning/REQUIREMENTS.md, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - -1. Create `supabase/migrations/20260328_01_identity_foundation.sql` with: - - `public.accounts` - - `id uuid primary key default gen_random_uuid()` - - `slug text not null unique` - - `display_name text not null` - - `account_type text not null check (account_type in ('personal', 'business'))` - - `owner_user_id uuid not null references auth.users(id)` - - `created_at timestamptz not null default now()` - - `updated_at timestamptz not null default now()` - - `public.account_memberships` - - `id uuid primary key default gen_random_uuid()` - - `account_id uuid not null references public.accounts(id) on delete cascade` - - `user_id uuid not null references auth.users(id) on delete cascade` - - `role text not null check (role in ('owner', 'member'))` - - `status text not null check (status in ('active', 'invited'))` - - `created_at timestamptz not null default now()` - - `unique(account_id, user_id)` - - `public.account_invitations` - - `id uuid primary key default gen_random_uuid()` - - `account_id uuid not null references public.accounts(id) on delete cascade` - - `email text not null` - - `role text not null check (role in ('member'))` - - `token_hash text not null unique` - - `expires_at timestamptz not null` - - `accepted_at timestamptz` - - `invited_by_user_id uuid not null references auth.users(id)` - - `created_at timestamptz not null default now()` - - `public.account_profiles` - - `account_id uuid primary key references public.accounts(id) on delete cascade` - - `owner_name text not null` - - `login_email text not null` - - `country_code text` - - `state_region text` - - `profile_setup_complete boolean not null default false` - - `created_at timestamptz not null default now()` - - `updated_at timestamptz not null default now()` - - indexes on `account_memberships.user_id`, `account_invitations.email`, and `accounts.slug` - - - cd /home/sakib/hive && rg -n "create table public\\.(accounts|account_memberships|account_invitations|account_profiles)" supabase/migrations/20260328_01_identity_foundation.sql - - - - supabase/migrations/20260328_01_identity_foundation.sql contains `create table public.accounts` - - supabase/migrations/20260328_01_identity_foundation.sql contains `create table public.account_memberships` - - supabase/migrations/20260328_01_identity_foundation.sql contains `create table public.account_invitations` - - supabase/migrations/20260328_01_identity_foundation.sql contains `create table public.account_profiles` - - The tenancy migration defines the durable account model that later identity and profile plans depend on. - - - - - -- Run `cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml config --services | grep -qx "control-plane"` to confirm the control-plane service is wired into the Docker-only workflow. -- Run `cd /home/sakib/hive && rg -n "create table public\\.(accounts|account_memberships|account_invitations|account_profiles)" supabase/migrations/20260328_01_identity_foundation.sql` to confirm the tenancy schema exists. - - - -- `control-plane` exists in Docker Compose, serves `/health`, and reads the shared Supabase env contract. -- The Phase 2 base migration defines accounts, memberships, invitations, and core profiles in Supabase Postgres. - - - -After completion, create `.planning/phases/02-identity-account-foundation/02-01-SUMMARY.md` - diff --git a/.planning/phases/02-identity-account-foundation/02-01-SUMMARY.md b/.planning/phases/02-identity-account-foundation/02-01-SUMMARY.md deleted file mode 100644 index 1229bfcc8..000000000 --- a/.planning/phases/02-identity-account-foundation/02-01-SUMMARY.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: 01 -subsystem: control-plane -tags: [go, docker, supabase, postgres, migrations, infrastructure] -dependency_graph: - requires: [] - provides: - - apps/control-plane Go module with /health endpoint - - Docker Compose control-plane service on port 8081 - - Supabase tenancy migration (accounts, memberships, invitations, profiles) - - Shared .env.example environment contract - affects: - - All subsequent Phase 2 plans (depend on control-plane running) - - Phase 3+ (billing/profile plans depend on accounts schema) -tech_stack: - added: - - github.com/jackc/pgx/v5 v5.7.2 (Postgres driver for control-plane) - - github.com/air-verse/air v1.64.5 (hot-reload inside Docker) - - golang:1.24-alpine (control-plane Docker base image) - patterns: - - Go workspace (go.work) for multi-module monorepo - - Functional options pattern deferred; platform packages use constructor injection - - pgxpool for connection pool management -key_files: - created: - - .env.example - - apps/control-plane/.air.toml - - apps/control-plane/go.mod - - apps/control-plane/go.sum - - apps/control-plane/cmd/server/main.go - - apps/control-plane/internal/platform/config/config.go - - apps/control-plane/internal/platform/db/pool.go - - apps/control-plane/internal/platform/http/router.go - - deploy/docker/Dockerfile.control-plane - - supabase/migrations/20260328_01_identity_foundation.sql - modified: - - go.work (added ./apps/control-plane) - - deploy/docker/docker-compose.yml (added control-plane service) - - deploy/docker/docker-compose.override.yml (added develop.watch for control-plane) -decisions: - - DB connection failure at startup is non-fatal (logs warning) so /health responds even without SUPABASE_DB_URL provisioned - - go.work.sum was not generated (no cross-workspace module dependencies yet) - - supabase/migrations directory required Docker with root to create (owned by root) - - token_hash stored instead of raw invitation token for security -metrics: - duration: 3min - completed_date: "2026-03-29" - tasks_completed: 2 - files_created: 10 - files_modified: 3 ---- - -# Phase 2 Plan 1: Control-Plane Foundation & Tenancy Migration Summary - -**One-liner:** Docker-hosted Go control-plane service on port 8081 with pgxpool, /health endpoint, air hot-reload, and Supabase tenancy migration defining accounts/memberships/invitations/profiles. - -## What Was Built - -### Task 1: Control-plane module, Docker image, and shared environment contract - -Created the `apps/control-plane` Go module from scratch with a clean three-layer structure: -- `internal/platform/config` — env-sourced config with validation -- `internal/platform/db` — pgxpool open/ping with descriptive errors -- `internal/platform/http` — mux router with `/health` returning `{"status":"ok"}` -- `cmd/server/main.go` — graceful shutdown, DB optional at startup - -Docker wiring: -- `Dockerfile.control-plane` uses `golang:1.24-alpine` + air for hot reload with `GOTOOLCHAIN=auto` (matches existing edge-api pattern from Phase 1) -- `docker-compose.yml` gains a `control-plane` service on `8081:8081` with healthcheck -- `docker-compose.override.yml` gains `develop.watch` sync/rebuild rules for the new service - -Shared env contract in `.env.example` covers Supabase, control-plane, and Next.js console keys. - -### Task 2: Phase 2 tenancy migration - -`supabase/migrations/20260328_01_identity_foundation.sql` defines: -- `public.accounts` — workspace entity (personal/business) owned by a Supabase auth user -- `public.account_memberships` — owner/member roles with active/invited status; unique constraint on (account_id, user_id) -- `public.account_invitations` — email invites with hashed tokens, expiry, and accepted_at -- `public.account_profiles` — minimal pre-billing profile (name, email, country, state, setup flag) -- Indexes on `accounts.slug`, `account_memberships.user_id`, `account_invitations.email` - -## Deviations from Plan - -### Auto-fixed Issues - -None — plan executed exactly as written. - -### Notes - -- `go.work.sum` was not created because there are no cross-workspace module dependencies. The file is referenced in Dockerfile.control-plane with `go.work.sum*` (optional glob) to handle this gracefully. -- The `supabase/` directory was owned by root; the migrations subdirectory was created via Docker with root privileges. - -## Verification Results - -All acceptance criteria passed: -- `go.work` contains `./apps/control-plane` -- `deploy/docker/Dockerfile.control-plane` contains `EXPOSE 8081` -- `deploy/docker/docker-compose.yml` contains `control-plane` and `8081:8081` -- `apps/control-plane/cmd/server/main.go` contains `ListenAndServe` and `/health` -- `.env.example` contains `CONTROL_PLANE_BASE_URL=http://localhost:8081` -- All four migration tables verified with grep - -## Commits - -| Hash | Message | -|------|---------| -| ff254b0 | feat(02-01): create control-plane module, Docker wiring, and shared env contract | -| d30eb57 | feat(02-01): add Phase 2 tenancy migration | - -## Self-Check: PASSED - -| File | Status | -|------|--------| -| .env.example | FOUND | -| apps/control-plane/cmd/server/main.go | FOUND | -| deploy/docker/Dockerfile.control-plane | FOUND | -| supabase/migrations/20260328_01_identity_foundation.sql | FOUND | -| ff254b0 (Task 1 commit) | VERIFIED | -| d30eb57 (Task 2 commit) | VERIFIED | diff --git a/.planning/phases/02-identity-account-foundation/02-02-PLAN.md b/.planning/phases/02-identity-account-foundation/02-02-PLAN.md deleted file mode 100644 index ce4105d30..000000000 --- a/.planning/phases/02-identity-account-foundation/02-02-PLAN.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: 02 -type: execute -wave: 2 -depends_on: - - 02-01 -files_modified: - - apps/control-plane/cmd/server/main.go - - apps/control-plane/internal/platform/http/router.go - - apps/control-plane/internal/auth/types.go - - apps/control-plane/internal/auth/client.go - - apps/control-plane/internal/auth/middleware.go - - apps/control-plane/internal/accounts/types.go - - apps/control-plane/internal/accounts/repository.go - - apps/control-plane/internal/accounts/service.go - - apps/control-plane/internal/accounts/http.go - - apps/control-plane/internal/accounts/service_test.go - - apps/control-plane/internal/accounts/http_test.go -autonomous: true -requirements: - - AUTH-01 - - AUTH-02 -must_haves: - truths: - - "The first authenticated visit provisions exactly one default workspace account plus one owner membership when no membership exists." - - "Unverified users can read viewer state, but invitation creation stays blocked with a deterministic `email_verification_required` response." - - "Accepting an invitation does not silently change the active workspace; switching accounts is an explicit later user action." - artifacts: - - path: "apps/control-plane/internal/auth/client.go" - provides: "Hosted Supabase token lookup for authenticated requests." - contains: "/auth/v1/user" - - path: "apps/control-plane/internal/accounts/service.go" - provides: "Workspace bootstrap, invitation rules, and current-account resolution." - exports: ["EnsureViewerContext", "CreateInvitation", "AcceptInvitation"] - - path: "apps/control-plane/internal/accounts/http.go" - provides: "Viewer, members, invitation, and invitation-accept HTTP handlers." - contains: "/api/v1/viewer" - key_links: - - from: "apps/control-plane/internal/auth/middleware.go" - to: "apps/control-plane/internal/auth/client.go" - via: "Auth middleware resolves the Supabase viewer before routing." - pattern: "LookupUser" - - from: "apps/control-plane/internal/accounts/http.go" - to: "apps/control-plane/internal/accounts/service.go" - via: "HTTP handlers delegate viewer bootstrap, invitation policy, and current-account selection to the accounts service." - pattern: "EnsureViewerContext" ---- - - -Implement the hosted-Supabase-backed identity APIs: viewer bootstrap, members list, invitation create and accept flows, and backend current-account selection semantics. - -Purpose: The console cannot render a real authenticated workspace until the control plane owns viewer context, invitation rules, and account selection. -Output: Auth middleware, viewer contract, invitation APIs, membership acceptance, and `X-Hive-Account-ID` support for explicit workspace selection. - - - -@/home/sakib/.codex/get-shit-done/workflows/execute-plan.md -@/home/sakib/.codex/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/02-identity-account-foundation/02-CONTEXT.md -@.planning/phases/02-identity-account-foundation/02-RESEARCH.md -@.planning/phases/02-identity-account-foundation/02-VALIDATION.md -@.planning/phases/02-identity-account-foundation/02-01-PLAN.md - - - - - - Task 1: Implement Supabase viewer lookup, default workspace bootstrap, and invitation APIs - - apps/control-plane/cmd/server/main.go, - apps/control-plane/internal/platform/http/router.go, - apps/control-plane/internal/auth/types.go, - apps/control-plane/internal/auth/client.go, - apps/control-plane/internal/auth/middleware.go, - apps/control-plane/internal/accounts/types.go, - apps/control-plane/internal/accounts/repository.go, - apps/control-plane/internal/accounts/service.go, - apps/control-plane/internal/accounts/http.go, - apps/control-plane/internal/accounts/service_test.go, - apps/control-plane/internal/accounts/http_test.go - - - .env.example, - supabase/migrations/20260328_01_identity_foundation.sql, - apps/control-plane/cmd/server/main.go, - apps/control-plane/internal/platform/http/router.go, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - - - Test: first authenticated request with no existing membership provisions one `personal` account, one `owner` membership, and one `account_profiles` row - - Test: viewer response returns `email_verified=false` and `gates.can_invite_members=false` when Supabase returns `email_confirmed_at = null` - - Test: `POST /api/v1/accounts/current/invitations` returns 403 with code `email_verification_required` for an unverified owner - - Test: verified owner can create an invitation for `member@example.com` and the response contains an invitation token - - -1. Create `apps/control-plane/internal/auth/types.go` with a `Viewer` struct containing `UserID`, `Email`, `EmailVerified`, and `FullName`. - -2. Create `apps/control-plane/internal/auth/client.go` with `LookupUser(ctx, bearerToken string) (Viewer, error)` that: - - sends `GET ${SUPABASE_URL}/auth/v1/user` - - forwards the caller bearer token as `Authorization: Bearer ` - - sets `apikey: ${SUPABASE_ANON_KEY}` - - parses `id`, `email`, `email_confirmed_at`, and `user_metadata.full_name` - - sets `EmailVerified` only when `email_confirmed_at` is non-null - -3. Create `apps/control-plane/internal/auth/middleware.go` that: - - requires a bearer token on all `/api/v1/*` routes - - calls `LookupUser` - - stores the resolved `Viewer` on request context - - returns `401` JSON when the bearer token is missing or invalid - -4. Create `apps/control-plane/internal/accounts/types.go` and `repository.go` with methods: - - `EnsureDefaultAccount(ctx, viewer Viewer) (ViewerContext, error)` - - `ListMembers(ctx, accountID uuid.UUID) ([]Member, error)` - - `CreateInvitation(ctx, accountID uuid.UUID, inviterUserID uuid.UUID, email string) (Invitation, error)` - - `AcceptInvitation(ctx, viewer Viewer, token string) (uuid.UUID, error)` - -5. In `EnsureDefaultAccount`, provision the default workspace exactly like this when the viewer has no memberships: - - `account_type = "personal"` - - `display_name = "'s Workspace"` when `full_name` is present - - fallback display name `"'s Workspace"` when `full_name` is blank - - `slug` derived from the chosen display name in lowercase kebab-case - - insert an `owner` membership with status `active` - - insert an `account_profiles` row with `owner_name`, `login_email`, and `profile_setup_complete = false` - -6. Create `apps/control-plane/internal/accounts/service.go` so `ViewerContext` returns: - - `user` - - `current_account` - - `memberships` - - `gates.can_invite_members` - - `gates.can_manage_api_keys` - Set both gates to `true` only when the viewer is verified and the selected membership role is `owner`. - -7. Create `apps/control-plane/internal/accounts/http.go` and wire routes in `cmd/server/main.go` / `router.go`: - - `GET /api/v1/viewer` - - `GET /api/v1/accounts/current/members` - - `POST /api/v1/accounts/current/invitations` - - `POST /api/v1/invitations/accept` - Return JSON only and use `403` with code `email_verification_required` when an unverified user attempts invitation creation. - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml run --rm control-plane go test ./internal/accounts/... -count=1 - - - - apps/control-plane/internal/auth/client.go contains `/auth/v1/user` - - apps/control-plane/internal/accounts/http.go contains `/api/v1/viewer` - - apps/control-plane/internal/accounts/http.go contains `/api/v1/accounts/current/invitations` - - apps/control-plane/internal/accounts/service.go contains `email_verification_required` - - apps/control-plane/internal/accounts/service_test.go contains `TestEnsureDefaultAccount` - - apps/control-plane/internal/accounts/service_test.go contains `TestInvitationRequiresVerifiedEmail` - - Viewer bootstrap and invitation APIs exist with verification-aware policy enforcement. - - - - Task 2: Add explicit current-account selection semantics and invitation acceptance behavior - - apps/control-plane/internal/accounts/types.go, - apps/control-plane/internal/accounts/repository.go, - apps/control-plane/internal/accounts/service.go, - apps/control-plane/internal/accounts/http.go, - apps/control-plane/internal/accounts/service_test.go, - apps/control-plane/internal/accounts/http_test.go - - - apps/control-plane/internal/accounts/types.go, - apps/control-plane/internal/accounts/repository.go, - apps/control-plane/internal/accounts/service.go, - apps/control-plane/internal/accounts/http.go, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - - - Test: `GET /api/v1/viewer` accepts optional `X-Hive-Account-ID` and returns that account as `current_account` when the viewer is an active member - - Test: invalid or unauthorized `X-Hive-Account-ID` falls back to the existing current/default membership instead of erroring - - Test: `POST /api/v1/invitations/accept` creates an active `member` membership and returns the joined account id without switching `current_account` automatically - - -1. Extend the accounts service and repository with a current-account resolution path: - - read optional request header `X-Hive-Account-ID` - - if the header matches an active membership, use that account as `current_account` - - if the header is missing, invalid, or outside the viewer memberships, fall back to the existing default membership - -2. Keep invitation behavior concrete: - - generated invitations always use role `member` - - invitation expiry is `72h` from creation - - only the current-account owner can create invitations - - `AcceptInvitation` requires the authenticated viewer email to equal the invitation email case-insensitively - - `AcceptInvitation` creates an `active` membership, stamps `accepted_at`, and returns the joined account id - - accepting an invitation does not alter the current-account fallback on the same request - -3. Add tests for: - - explicit current-account selection via `X-Hive-Account-ID` - - fallback when the requested account is absent or unauthorized - - invitation acceptance returning the joined account id while leaving current-account selection explicit - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml run --rm control-plane go test ./internal/accounts/... -run "TestSelectCurrentAccount|TestAcceptInvitation" -count=1 - - - - apps/control-plane/internal/accounts/http.go contains `X-Hive-Account-ID` - - apps/control-plane/internal/accounts/service.go contains `current_account` - - apps/control-plane/internal/accounts/http_test.go contains `TestAcceptInvitation` - - apps/control-plane/internal/accounts/service_test.go contains `TestSelectCurrentAccount` - - Current-account selection is explicit and testable, and invitation acceptance no longer leaves switch behavior ambiguous. - - - - - -- Run `cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml run --rm control-plane go test ./internal/accounts/... -count=1` to verify bootstrap, invitation, and current-account selection behavior. - - - -- The first authenticated viewer bootstrap creates one default workspace, one owner membership, and one core profile row when no membership exists. -- `GET /api/v1/viewer`, `GET /api/v1/accounts/current/members`, `POST /api/v1/accounts/current/invitations`, and `POST /api/v1/invitations/accept` are implemented with deterministic tests. -- Active workspace selection is explicit via `X-Hive-Account-ID`; accepting an invitation does not switch the current account automatically. - - - -After completion, create `.planning/phases/02-identity-account-foundation/02-02-SUMMARY.md` - diff --git a/.planning/phases/02-identity-account-foundation/02-02-SUMMARY.md b/.planning/phases/02-identity-account-foundation/02-02-SUMMARY.md deleted file mode 100644 index c2f9c69a6..000000000 --- a/.planning/phases/02-identity-account-foundation/02-02-SUMMARY.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: "02" -subsystem: identity-accounts -tags: - - auth - - accounts - - invitations - - workspace-bootstrap -dependency_graph: - requires: - - 02-01 (control-plane scaffold, DB schema) - provides: - - auth.Viewer type and Supabase LookupUser client - - Bearer auth middleware for /api/v1/* routes - - Workspace bootstrap on first login - - Viewer context API with capability gates - - Invitation create and accept flows - - Explicit current-account selection via X-Hive-Account-ID - affects: - - 02-03 (console session — depends on viewer API) - - 02-04 and later (API key / billing phases depend on gates) -tech_stack: - added: - - github.com/google/uuid v1.6.0 - patterns: - - Repository interface with pgxRepository production impl and stubRepo for tests - - Service layer owns all business logic; HTTP handlers are thin - - GateError typed error for policy enforcement - - SHA-256 token hashing (token_hash stored, not raw token) - - context.Value viewer propagation through request context -key_files: - created: - - apps/control-plane/internal/auth/types.go - - apps/control-plane/internal/auth/client.go - - apps/control-plane/internal/auth/middleware.go - - apps/control-plane/internal/accounts/types.go - - apps/control-plane/internal/accounts/repository.go - - apps/control-plane/internal/accounts/service.go - - apps/control-plane/internal/accounts/http.go - - apps/control-plane/internal/accounts/service_test.go - - apps/control-plane/internal/accounts/http_test.go - modified: - - apps/control-plane/cmd/server/main.go - - apps/control-plane/internal/platform/config/config.go - - apps/control-plane/internal/platform/http/router.go - - apps/control-plane/go.mod - - apps/control-plane/go.sum - - deploy/docker/Dockerfile.control-plane -decisions: - - HashToken (SHA-256 hex) used consistently in service and tests — raw token returned once at invitation creation, never stored - - X-Hive-Account-ID fallback is silent — invalid or unauthorized account IDs fall back to default membership without error - - AcceptInvitation does not alter current-account selection on the same request — switching is an explicit user action - - EnsureViewerContext is idempotent — subsequent calls for an existing viewer reuse existing memberships - - GateError is a typed error exported for errors.As checks — enables deterministic code in HTTP responses -metrics: - duration: 8min - completed_date: "2026-03-29" - tasks_completed: 2 - files_changed: 15 ---- - -# Phase 02 Plan 02: Identity APIs — Viewer Bootstrap and Invitation Flows Summary - -**One-liner:** Supabase-backed identity APIs with first-login workspace provisioning, verification-aware capability gates, explicit account selection via header, and invitation create/accept flows using SHA-256 token hashing. - -## What Was Built - -The hosted Supabase-backed identity layer for the Hive control plane: - -1. **Auth layer** (`internal/auth/`) - - `Viewer` struct carrying `UserID`, `Email`, `EmailVerified`, `FullName` - - `Client.LookupUser` calls `GET ${SUPABASE_URL}/auth/v1/user` forwarding the caller bearer token - - `Middleware.Require` wraps handlers, returns 401 JSON on missing/invalid tokens, stores `Viewer` in request context - -2. **Accounts service** (`internal/accounts/service.go`) - - `EnsureViewerContext` provisions a default personal workspace + owner membership + profile on first login (no existing memberships) - - Workspace display name seeds from `FullName` or email local part - - `X-Hive-Account-ID` selects current account explicitly; falls back silently on invalid/unauthorized values - - `Gates.CanInviteMembers` and `Gates.CanManageAPIKeys` are both true only for verified owners - - `CreateInvitation` enforces `email_verification_required` gate, generates 72h expiry token, stores SHA-256 hash - - `AcceptInvitation` validates email match (case-insensitive), creates active `member` membership, returns joined account ID without altering current account - -3. **HTTP handlers** (`internal/accounts/http.go`) - - `GET /api/v1/viewer` — viewer context with user, current_account, memberships, gates - - `GET /api/v1/accounts/current/members` — member list for current account - - `POST /api/v1/accounts/current/invitations` — invitation creation with 403 + code on gate violation - - `POST /api/v1/invitations/accept` — accepts invitation, returns joined account_id - -4. **Repository** (`internal/accounts/repository.go`) - - `Repository` interface with 9 methods - - `pgxRepository` production implementation using pgx/v5 - - `stubRepo` in tests enables fully in-memory testing without a live DB - -5. **Router and main.go** updated to wire auth middleware and accounts handler - -## Test Coverage - -16 tests covering: -- First-login workspace bootstrap creates account + owner membership + profile -- Display name falls back from full name to email local part -- Unverified user gates are false; verified owner gates are true -- Unverified owner invitation blocked with `email_verification_required` -- Verified owner can create invitation with token in response -- Explicit account selection via `X-Hive-Account-ID` -- Fallback when requested account is invalid or unauthorized -- Invitation acceptance creates active member membership, returns account_id -- Email mismatch on accept is rejected - -All 16 tests pass: `ok github.com/sakibsadmanshajib/hive/apps/control-plane/internal/accounts 0.006s` - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Dockerfile.control-plane missing edge-api go.mod copy** -- **Found during:** Build step -- **Issue:** `go.work` references `./apps/edge-api` but Dockerfile only copied `apps/control-plane/go.mod` — workspace resolution failed during `go mod download` -- **Fix:** Added `COPY apps/edge-api/go.mod apps/edge-api/go.sum* ./apps/edge-api/` to Dockerfile -- **Files modified:** `deploy/docker/Dockerfile.control-plane` -- **Commit:** 43febdb - -**2. [Rule 3 - Blocking] google/uuid not in go.sum** -- **Found during:** First Docker build -- **Issue:** `go.mod` required `github.com/google/uuid v1.6.0` but `go.sum` had no entry for it -- **Fix:** Ran `go mod tidy` in a one-off golang:1.24-alpine container to update `go.sum` -- **Files modified:** `apps/control-plane/go.mod`, `apps/control-plane/go.sum` -- **Commit:** 43febdb - -**3. [Rule 3 - Blocking] Test run path mismatch with go.work root** -- **Found during:** First test run -- **Issue:** Plan's verify command used `./internal/accounts/...` relative to go.work root `/app` — pattern didn't match -- **Fix:** Run tests as `cd /app/apps/control-plane && go test ./internal/accounts/...` -- **Note:** Plan's automated verify path needs updating for future runs - -## Self-Check: PASSED diff --git a/.planning/phases/02-identity-account-foundation/02-03-PLAN.md b/.planning/phases/02-identity-account-foundation/02-03-PLAN.md deleted file mode 100644 index 52651f60f..000000000 --- a/.planning/phases/02-identity-account-foundation/02-03-PLAN.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: 03 -type: execute -wave: 2 -depends_on: - - 02-01 -files_modified: - - deploy/docker/docker-compose.yml - - deploy/docker/docker-compose.override.yml - - deploy/docker/Dockerfile.web-console - - apps/web-console/package.json - - apps/web-console/package-lock.json - - apps/web-console/tsconfig.json - - apps/web-console/middleware.ts - - apps/web-console/app/page.tsx - - apps/web-console/app/auth/sign-in/page.tsx - - apps/web-console/app/auth/sign-up/page.tsx - - apps/web-console/app/auth/forgot-password/page.tsx - - apps/web-console/app/auth/reset-password/page.tsx - - apps/web-console/app/auth/callback/route.ts - - apps/web-console/lib/supabase/browser.ts - - apps/web-console/lib/supabase/server.ts -autonomous: false -requirements: - - AUTH-01 - - AUTH-02 - - AUTH-03 -must_haves: - truths: - - "Sign-up, sign-in, verification, and password recovery all use hosted Supabase auth flows instead of Hive-owned password handling." - - "The console session survives refresh and normal revisits through SSR session refresh and cookie persistence." - - "Returning authenticated users land on `/console`, not a stale deep link." - artifacts: - - path: "deploy/docker/Dockerfile.web-console" - provides: "Docker-only Next.js dev/test container for the console." - contains: "EXPOSE 3000" - - path: "apps/web-console/middleware.ts" - provides: "SSR session refresh and `/console` route protection." - contains: "/console" - - path: "apps/web-console/app/auth/callback/route.ts" - provides: "Auth code exchange route with constrained redirect targets." - contains: "exchangeCodeForSession" - key_links: - - from: "apps/web-console/middleware.ts" - to: "apps/web-console/lib/supabase/server.ts" - via: "Session middleware refreshes the SSR Supabase client." - pattern: "createServerClient" ---- - - -Create the web-console app foundation: Docker wiring, hosted Supabase auth pages, SSR session refresh, and callback handling. - -Purpose: Phase 2 needs a real browser entrypoint before the authenticated console shell can layer on viewer state, workspace switching, and profile UX. -Output: A new Next.js `web-console` app, Docker service on port `3000`, Supabase auth routes, root redirect, and session-preserving middleware. - - - -@/home/sakib/.codex/get-shit-done/workflows/execute-plan.md -@/home/sakib/.codex/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/02-identity-account-foundation/02-CONTEXT.md -@.planning/phases/02-identity-account-foundation/02-RESEARCH.md -@.planning/phases/02-identity-account-foundation/02-VALIDATION.md -@.planning/phases/02-identity-account-foundation/02-01-PLAN.md -@.planning/phases/02-identity-account-foundation/02-02-PLAN.md - - - - - - Task 1: Create the web-console package, Docker service, and Supabase client helpers - - deploy/docker/docker-compose.yml, - deploy/docker/docker-compose.override.yml, - deploy/docker/Dockerfile.web-console, - apps/web-console/package.json, - apps/web-console/package-lock.json, - apps/web-console/tsconfig.json, - apps/web-console/lib/supabase/browser.ts, - apps/web-console/lib/supabase/server.ts - - - .env.example, - deploy/docker/docker-compose.yml, - deploy/docker/docker-compose.override.yml, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - -1. Create `deploy/docker/Dockerfile.web-console` using `node:22-alpine` with: - - `WORKDIR /app/apps/web-console` - - `COPY apps/web-console/package.json apps/web-console/package-lock.json* ./` - - `RUN npm install` - - `COPY apps/web-console/ ./` - - `EXPOSE 3000` - - `CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"]` - -2. Update `deploy/docker/docker-compose.yml` to add a `web-console` service that: - - builds from `deploy/docker/Dockerfile.web-console` - - publishes `3000:3000` - - depends on `control-plane` - - passes `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `NEXT_PUBLIC_APP_URL`, and `CONTROL_PLANE_BASE_URL=http://control-plane:8081` - -3. Update `deploy/docker/docker-compose.override.yml` so `web-console` gets: - - a sync watch rule for `../../apps/web-console` - - a rebuild watch rule for `../../apps/web-console/package.json` - -4. Create `apps/web-console/package.json` with: - - `name: "@hive/web-console"` - - dependencies `next@16.1.0`, `react@19.2.0`, `react-dom@19.2.0`, `@supabase/ssr`, and `@supabase/supabase-js` - - scripts `dev`, `build`, `start`, `test:unit`, and `test:e2e` - -5. Create `apps/web-console/tsconfig.json` for a Next.js App Router project and generate `package-lock.json`. - -6. Create `apps/web-console/lib/supabase/browser.ts` and `server.ts` so: - - browser helpers call `createBrowserClient` - - server helpers call `createServerClient` - - both read `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` - - - cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml config --services | grep -qx "web-console" && rg -n "test:unit|test:e2e|@supabase/ssr|next" apps/web-console/package.json - - - - deploy/docker/Dockerfile.web-console contains `EXPOSE 3000` - - deploy/docker/docker-compose.yml contains `web-console` and `3000:3000` - - apps/web-console/package.json contains `@supabase/ssr` - - apps/web-console/package.json contains `test:unit` - - apps/web-console/lib/supabase/server.ts contains `createServerClient` - - The web-console package and Docker service exist with hosted Supabase client helpers. - - - - Task 2: Add auth routes, root redirect, and SSR session middleware - - apps/web-console/middleware.ts, - apps/web-console/app/page.tsx, - apps/web-console/app/auth/sign-in/page.tsx, - apps/web-console/app/auth/sign-up/page.tsx, - apps/web-console/app/auth/forgot-password/page.tsx, - apps/web-console/app/auth/reset-password/page.tsx, - apps/web-console/app/auth/callback/route.ts - - - apps/web-console/package.json, - apps/web-console/tsconfig.json, - apps/web-console/lib/supabase/browser.ts, - apps/web-console/lib/supabase/server.ts, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - -1. Create `apps/web-console/middleware.ts` with these exact behaviors: - - refresh the Supabase session for every request under `/console` - - redirect unauthenticated `/console` requests to `/auth/sign-in` - - redirect authenticated requests from `/` to `/console` - - do not preserve arbitrary console deep links during sign-in; authenticated users should land on `/console` - -2. Create auth routes: - - `app/auth/sign-in/page.tsx` with a form that calls `signInWithPassword` - - `app/auth/sign-up/page.tsx` with a form that calls `signUp` and sets the email redirect to `${NEXT_PUBLIC_APP_URL}/auth/callback` - - `app/auth/forgot-password/page.tsx` with a form that calls `resetPasswordForEmail(email, { redirectTo: \`${NEXT_PUBLIC_APP_URL}/auth/callback?next=/auth/reset-password\` })` - - `app/auth/reset-password/page.tsx` with a form that calls `updateUser({ password })` - - `app/auth/callback/route.ts` that calls `exchangeCodeForSession`, accepts only `/console` and `/auth/reset-password` as `next` targets, and redirects to `/console` when `next` is missing or invalid - -3. Create `app/page.tsx` so the root route redirects authenticated users to `/console` and anonymous users to `/auth/sign-in`. - - - cd /home/sakib/hive && rg -n "signInWithPassword|signUp|resetPasswordForEmail|exchangeCodeForSession|/auth/reset-password" apps/web-console/app -g '*.ts' -g '*.tsx' && rg -n "NextResponse.redirect|/console" apps/web-console/middleware.ts - - - - apps/web-console/app/auth/sign-in/page.tsx contains `signInWithPassword` - - apps/web-console/app/auth/sign-up/page.tsx contains `signUp` - - apps/web-console/app/auth/forgot-password/page.tsx contains `redirectTo` - - apps/web-console/app/auth/callback/route.ts contains `exchangeCodeForSession` - - apps/web-console/app/auth/callback/route.ts contains `/auth/reset-password` - - apps/web-console/middleware.ts contains `NextResponse.redirect` - - The web console supports hosted auth flows and preserves browser sessions across `/console` visits. - - - - - -- Run `cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml config --services | grep -qx "web-console"` to confirm the web console is wired into Docker. -- Run `cd /home/sakib/hive && rg -n "signInWithPassword|signUp|resetPasswordForEmail|exchangeCodeForSession" apps/web-console/app -g '*.ts' -g '*.tsx'` to confirm the auth routes exist. - - - -- The Next.js `web-console` service runs in Docker on port `3000`. -- Hosted Supabase auth pages and callback handling exist for sign-up, sign-in, verification, and password recovery. -- SSR session refresh protects `/console` and returns authenticated users to `/console`. - - - -After completion, create `.planning/phases/02-identity-account-foundation/02-03-SUMMARY.md` - diff --git a/.planning/phases/02-identity-account-foundation/02-03-SUMMARY.md b/.planning/phases/02-identity-account-foundation/02-03-SUMMARY.md deleted file mode 100644 index bade6e385..000000000 --- a/.planning/phases/02-identity-account-foundation/02-03-SUMMARY.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: "03" -subsystem: web-console -tags: - - next.js - - supabase - - auth - - docker - - ssr -dependency_graph: - requires: - - 02-01 (control-plane service and Docker wiring) - provides: - - apps/web-console Next.js app with Docker service on port 3000 - - Hosted Supabase auth pages (sign-in, sign-up, forgot-password, reset-password) - - SSR session middleware for /console route protection - - Auth callback route with constrained redirect targets - affects: - - 02-04 and beyond (console shell layers onto this foundation) -tech_stack: - added: - - next@15.1.0 - - react@19.0.0 - - "@supabase/ssr@^0.6.1" - - "@supabase/supabase-js@^2.48.1" - - vitest@^3.1.1 - - "@vitejs/plugin-react@^4.4.1" - - "@playwright/test@^1.51.1" - patterns: - - Next.js App Router with SSR session refresh via Supabase SSR helpers - - Constrained callback redirects (allowlist of /console, /auth/reset-password) - - Docker Compose develop.watch for hot-reload in containers -key_files: - created: - - deploy/docker/Dockerfile.web-console - - apps/web-console/package.json - - apps/web-console/tsconfig.json - - apps/web-console/vitest.config.ts - - apps/web-console/.gitignore - - apps/web-console/lib/supabase/browser.ts - - apps/web-console/lib/supabase/server.ts - - apps/web-console/middleware.ts - - apps/web-console/app/page.tsx - - apps/web-console/app/auth/sign-in/page.tsx - - apps/web-console/app/auth/sign-up/page.tsx - - apps/web-console/app/auth/forgot-password/page.tsx - - apps/web-console/app/auth/reset-password/page.tsx - - apps/web-console/app/auth/callback/route.ts - - apps/web-console/__tests__/supabase-helpers.test.ts - - apps/web-console/__tests__/auth-routes.test.ts - modified: - - deploy/docker/docker-compose.yml - - deploy/docker/docker-compose.override.yml -decisions: - - Middleware uses named export `middleware` (not default export) per Next.js App Router convention - - Callback route uses an explicit allowlist (Set) for next= redirect targets rather than regex — simpler and less error-prone - - apps/web-console/.gitignore negates root-level `lib/` Python gitignore entry so the Next.js lib/ source directory can be committed - - Server helper accepts ReadonlyRequestCookies parameter rather than calling cookies() internally — keeps the helper testable without mocking Next.js internals -metrics: - duration: "8min" - completed_date: "2026-03-29" - tasks_completed: 2 - files_created: 16 - files_modified: 2 - tests_added: 15 ---- - -# Phase 02 Plan 03: Web Console Foundation Summary - -**One-liner:** Next.js web-console app with Supabase SSR session helpers, hosted auth pages (sign-in/sign-up/forgot-password/reset-password/callback), Docker service on port 3000, and route-protecting middleware. - -## What Was Built - -A new `apps/web-console` Next.js App Router application that serves as the authenticated developer console entrypoint. The app uses hosted Supabase auth for all password flows and Supabase SSR helpers for persistent server-side session management. - -### Task 1: web-console package, Docker service, and Supabase client helpers - -Created the full package scaffold: `package.json` with `@supabase/ssr`, `@supabase/supabase-js`, `next`, `react`, `vitest`, and `@playwright/test`. Added `Dockerfile.web-console` (node:22-alpine, EXPOSE 3000) and wired a `web-console` service into `docker-compose.yml` (port 3000:3000, depends on control-plane). Added docker-compose.override.yml watch rules for hot reload. Implemented `lib/supabase/browser.ts` (createBrowserClient) and `lib/supabase/server.ts` (createServerClient with full SSR cookie handling including setAll try/catch for Server Component contexts). - -**TDD:** 4 tests validating helper exports and env var wiring. - -### Task 2: Auth routes, root redirect, and SSR session middleware - -Implemented all hosted auth flows: -- `middleware.ts`: refreshes Supabase session on every non-static request, redirects unauthenticated `/console` requests to `/auth/sign-in`, redirects authenticated `/` visits to `/console` -- `app/auth/sign-in/page.tsx`: `signInWithPassword` form -- `app/auth/sign-up/page.tsx`: `signUp` with `emailRedirectTo: ${NEXT_PUBLIC_APP_URL}/auth/callback` -- `app/auth/forgot-password/page.tsx`: `resetPasswordForEmail` with `redirectTo: ${NEXT_PUBLIC_APP_URL}/auth/callback?next=/auth/reset-password` -- `app/auth/reset-password/page.tsx`: `updateUser({ password })` form -- `app/auth/callback/route.ts`: `exchangeCodeForSession` with allowlisted redirect targets (`/console`, `/auth/reset-password`); falls back to `/console` when `next` is missing or invalid -- `app/page.tsx`: SSR root redirect based on session state - -**TDD:** 11 tests covering middleware exports, callback redirect logic (valid/invalid/missing next targets), and component exports. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 1 - Bug] Root .gitignore Python section blocks apps/web-console/lib/** - -- **Found during:** Task 1 commit -- **Issue:** The root `.gitignore` contains `lib/` from a Python gitignore template (line 246), which caused `git add` to reject `apps/web-console/lib/supabase/*.ts` -- **Fix:** Created `apps/web-console/.gitignore` with `!lib/` to override the root-level exclusion for this subdirectory -- **Files modified:** `apps/web-console/.gitignore` (created) -- **Commit:** 3c5b77e - -**2. [Rule 1 - Bug] Middleware test assumed default export but Next.js uses named export** - -- **Found during:** Task 2 TDD GREEN phase -- **Issue:** Test asserted `mod.default` but Next.js App Router middleware must use a named export `middleware` — `mod.default` was undefined -- **Fix:** Updated test to check `mod.middleware` which matches Next.js convention -- **Files modified:** `apps/web-console/__tests__/auth-routes.test.ts` -- **Commit:** 8366629 - -## Commits - -| Hash | Message | -|------|---------| -| 3c5b77e | feat(02-03): create web-console package, Docker service, and Supabase client helpers | -| 8366629 | feat(02-03): add auth routes, root redirect, and SSR session middleware | - -## Self-Check: PASSED - -All 12 key files confirmed present. Both commits (3c5b77e, 8366629) found in git log. 15/15 tests passing. diff --git a/.planning/phases/02-identity-account-foundation/02-04-PLAN.md b/.planning/phases/02-identity-account-foundation/02-04-PLAN.md deleted file mode 100644 index 535d8f28b..000000000 --- a/.planning/phases/02-identity-account-foundation/02-04-PLAN.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: 04 -type: execute -wave: 3 -depends_on: - - 02-02 - - 02-03 -files_modified: - - apps/web-console/app/console/layout.tsx - - apps/web-console/app/console/page.tsx - - apps/web-console/app/console/members/page.tsx - - apps/web-console/app/console/account-switch/route.ts - - apps/web-console/app/invitations/accept/page.tsx - - apps/web-console/components/nav-shell.tsx - - apps/web-console/components/workspace-switcher.tsx - - apps/web-console/components/verification-banner.tsx - - apps/web-console/lib/control-plane/client.ts - - apps/web-console/lib/viewer-gates.ts - - apps/web-console/vitest.config.ts - - apps/web-console/playwright.config.ts - - apps/web-console/tests/unit/viewer-gates.test.ts - - apps/web-console/tests/e2e/auth-shell.spec.ts -autonomous: false -requirements: - - AUTH-01 - - AUTH-02 - - AUTH-03 -must_haves: - truths: - - "Unverified users can enter a real but mostly locked console shell." - - "Members can accept invitations without auto-switching the current workspace." - - "Workspace switching is explicit, persisted, and verifiable." - artifacts: - - path: "apps/web-console/lib/control-plane/client.ts" - provides: "Viewer fetcher that forwards the Supabase access token plus selected account context." - exports: ["getViewer"] - - path: "apps/web-console/components/workspace-switcher.tsx" - provides: "Explicit workspace selection UI." - contains: "hive_account_id" - - path: "apps/web-console/app/invitations/accept/page.tsx" - provides: "Invitation acceptance UX that joins the account without switching immediately." - contains: "/api/v1/invitations/accept" - key_links: - - from: "apps/web-console/app/console/account-switch/route.ts" - to: "apps/web-console/lib/control-plane/client.ts" - via: "The switch route persists `hive_account_id`, which the control-plane client forwards on subsequent viewer fetches." - pattern: "X-Hive-Account-ID" ---- - - -Build the authenticated console shell, verification-aware lock state, read-only members experience, invitation acceptance flow, and explicit workspace switching. - -Purpose: Phase 2 must make shared tenancy actually usable, not only modeled in the database. This plan turns the control-plane viewer APIs into a navigable console with explicit current-account selection. -Output: `/console` shell, members roster, verification banner, invitation acceptance route, switcher persistence via `hive_account_id`, and automated UI tests. - - - -@/home/sakib/.codex/get-shit-done/workflows/execute-plan.md -@/home/sakib/.codex/get-shit-done/templates/summary.md - - - -@.planning/PROJECT.md -@.planning/ROADMAP.md -@.planning/STATE.md -@.planning/phases/02-identity-account-foundation/02-CONTEXT.md -@.planning/phases/02-identity-account-foundation/02-RESEARCH.md -@.planning/phases/02-identity-account-foundation/02-VALIDATION.md -@.planning/phases/02-identity-account-foundation/02-02-PLAN.md -@.planning/phases/02-identity-account-foundation/02-03-PLAN.md - - - - - - Task 1: Build the verification-aware shell, viewer gates, and read-only members view - - apps/web-console/app/console/layout.tsx, - apps/web-console/app/console/page.tsx, - apps/web-console/app/console/members/page.tsx, - apps/web-console/components/nav-shell.tsx, - apps/web-console/components/verification-banner.tsx, - apps/web-console/lib/control-plane/client.ts, - apps/web-console/lib/viewer-gates.ts, - apps/web-console/vitest.config.ts, - apps/web-console/playwright.config.ts, - apps/web-console/tests/unit/viewer-gates.test.ts - - - apps/web-console/middleware.ts, - apps/web-console/lib/supabase/server.ts, - apps/web-console/app/auth/callback/route.ts, - apps/control-plane/internal/accounts/http.go, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - -1. Create `apps/web-console/lib/control-plane/client.ts` with a `getViewer()` helper that: - - reads the Supabase access token from the server session - - reads cookie `hive_account_id` when present - - calls `GET ${CONTROL_PLANE_BASE_URL}/api/v1/viewer` - - forwards `Authorization: Bearer ` - - forwards `X-Hive-Account-ID: ` when the cookie exists - -2. Create `apps/web-console/lib/viewer-gates.ts` with these exact rules: - - `canInviteMembers(viewer)` returns `viewer.gates.can_invite_members` - - `canManageApiKeys(viewer)` returns `viewer.gates.can_manage_api_keys` - - `allowedUnverifiedRoutes = ["/console", "/console/setup", "/console/settings/profile", "/console/members"]` - -3. Create `apps/web-console/app/console/layout.tsx` so it: - - loads the viewer with `getViewer()` - - renders `NavShell`, `WorkspaceSwitcher`, and `VerificationBanner` - - always shows the verification banner when `viewer.user.email_verified === false` - -4. Create `apps/web-console/app/console/page.tsx` as the main dashboard route. It should render: - - heading `Dashboard` - - workspace summary using `viewer.current_account.display_name` - - reminder copy when `email_verified` is false - -5. Create `apps/web-console/app/console/members/page.tsx` as a read-only roster by default: - - load member rows from `GET ${CONTROL_PLANE_BASE_URL}/api/v1/accounts/current/members` - - render a disabled invite button and exact helper text `Email verification is required before you can invite teammates.` when `canInviteMembers(viewer)` is false - - when the viewer is verified and allowed, post invite JSON `{ "email": "" }` to `POST ${CONTROL_PLANE_BASE_URL}/api/v1/accounts/current/invitations` - -6. Create `apps/web-console/vitest.config.ts`, `apps/web-console/playwright.config.ts`, and `apps/web-console/tests/unit/viewer-gates.test.ts` covering `canInviteMembers`, `canManageApiKeys`, and `allowedUnverifiedRoutes`. - - - cd /home/sakib/hive && rg -n "allowedUnverifiedRoutes|X-Hive-Account-ID|Email verification is required before you can invite teammates\\." apps/web-console -g '*.ts' -g '*.tsx' && rg -n "canInviteMembers|allowedUnverifiedRoutes" apps/web-console/tests/unit/viewer-gates.test.ts - - - - apps/web-console/lib/control-plane/client.ts contains `X-Hive-Account-ID` - - apps/web-console/lib/viewer-gates.ts contains `allowedUnverifiedRoutes = ["/console", "/console/setup", "/console/settings/profile", "/console/members"]` - - apps/web-console/app/console/page.tsx contains `Dashboard` - - apps/web-console/app/console/members/page.tsx contains `Email verification is required before you can invite teammates.` - - apps/web-console/tests/unit/viewer-gates.test.ts contains `allowedUnverifiedRoutes` - - The console shell can render viewer state, protect sensitive actions, and show a read-only members roster for unverified users. - - - - Task 2: Add explicit workspace switching and invitation acceptance UX - - apps/web-console/app/console/account-switch/route.ts, - apps/web-console/app/invitations/accept/page.tsx, - apps/web-console/components/workspace-switcher.tsx, - apps/web-console/tests/e2e/auth-shell.spec.ts - - - apps/web-console/app/console/layout.tsx, - apps/web-console/app/console/members/page.tsx, - apps/web-console/lib/control-plane/client.ts, - apps/web-console/lib/viewer-gates.ts, - .planning/phases/02-identity-account-foundation/02-CONTEXT.md, - .planning/phases/02-identity-account-foundation/02-RESEARCH.md - - - - Test: `accepting an invitation keeps current workspace until switcher changes it` - - Test: `workspace switcher persists selected account` - - Test: `unverified members page stays locked` - - -1. Create `apps/web-console/components/workspace-switcher.tsx` so it: - - renders every membership from `viewer.memberships` - - submits the selected `account_id` to `POST /console/account-switch` - - marks the current account using `viewer.current_account.id` - -2. Create `apps/web-console/app/console/account-switch/route.ts` that: - - accepts `account_id` from a form POST - - verifies the requested account id exists in `viewer.memberships` before persisting it - - sets cookie `hive_account_id=` - - redirects back to `/console` - - does not mutate Supabase session state - -3. Create `apps/web-console/app/invitations/accept/page.tsx` that: - - requires an authenticated Supabase session - - reads invitation token from the request - - posts it to `POST ${CONTROL_PLANE_BASE_URL}/api/v1/invitations/accept` - - redirects to `/console/members?joined=1` - - does not change `hive_account_id`; the newly joined workspace appears in the switcher until the user explicitly selects it - -4. Create `apps/web-console/tests/e2e/auth-shell.spec.ts` covering: - - `unverified members page stays locked` - - `accepting an invitation keeps current workspace until switcher changes it` - - `workspace switcher persists selected account` - Use environment variables `E2E_VERIFIED_EMAIL`, `E2E_VERIFIED_PASSWORD`, `E2E_UNVERIFIED_EMAIL`, `E2E_UNVERIFIED_PASSWORD`, and `E2E_INVITATION_TOKEN`. - - - cd /home/sakib/hive && rg -n "hive_account_id|/api/v1/invitations/accept|workspace switcher persists selected account|accepting an invitation keeps current workspace until switcher changes it" apps/web-console -g '*.ts' -g '*.tsx' - - - - apps/web-console/components/workspace-switcher.tsx contains `account_id` - - apps/web-console/app/console/account-switch/route.ts contains `hive_account_id` - - apps/web-console/app/invitations/accept/page.tsx contains `/api/v1/invitations/accept` - - apps/web-console/tests/e2e/auth-shell.spec.ts contains `workspace switcher persists selected account` - - apps/web-console/tests/e2e/auth-shell.spec.ts contains `accepting an invitation keeps current workspace until switcher changes it` - - Shared tenancy is usable: users can accept invitations, see joined workspaces, and switch the active account explicitly. - - - - - -- Run `cd /home/sakib/hive && rg -n "hive_account_id|X-Hive-Account-ID|/api/v1/invitations/accept" apps/web-console -g '*.ts' -g '*.tsx'` to verify account-switch and invitation-accept wiring. -- Run `cd /home/sakib/hive && docker compose -f deploy/docker/docker-compose.yml run --rm web-console npm run test:e2e -- --grep "(unverified members page stays locked|accepting an invitation keeps current workspace until switcher changes it|workspace switcher persists selected account)"` to verify the shell, invite-accept flow, and current-account persistence. - - - -- Unverified users can read the console shell and members roster but cannot invite teammates. -- Invitation acceptance adds the new membership without auto-switching the current account. -- Workspace selection is explicit, persisted, and test-covered through `hive_account_id`. - - - -After completion, create `.planning/phases/02-identity-account-foundation/02-04-SUMMARY.md` - diff --git a/.planning/phases/02-identity-account-foundation/02-04-SUMMARY.md b/.planning/phases/02-identity-account-foundation/02-04-SUMMARY.md deleted file mode 100644 index 830086312..000000000 --- a/.planning/phases/02-identity-account-foundation/02-04-SUMMARY.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -phase: 02-identity-account-foundation -plan: "04" -subsystem: web-console -tags: - - console-shell - - viewer-gates - - workspace-switching - - invitation-acceptance - - verification-banner -dependency_graph: - requires: - - 02-02 - - 02-03 - provides: - - authenticated console shell with verification lock state - - workspace switcher persisting hive_account_id - - invitation acceptance without auto-switch - affects: - - apps/web-console -tech_stack: - added: [] - patterns: - - Server Component viewer fetcher forwarding Supabase access token + X-Hive-Account-ID - - Gate functions over viewer.gates fields (canInviteMembers, canManageApiKeys) - - Cookie-based account context (hive_account_id) separate from Supabase session - - E2E tests using environment variables for verified/unverified credentials -key_files: - created: - - apps/web-console/lib/control-plane/client.ts - - apps/web-console/lib/viewer-gates.ts - - apps/web-console/app/console/layout.tsx - - apps/web-console/app/console/page.tsx - - apps/web-console/app/console/members/page.tsx - - apps/web-console/components/nav-shell.tsx - - apps/web-console/components/verification-banner.tsx - - apps/web-console/components/workspace-switcher.tsx - - apps/web-console/app/console/account-switch/route.ts - - apps/web-console/app/invitations/accept/page.tsx - - apps/web-console/playwright.config.ts - - apps/web-console/tests/unit/viewer-gates.test.ts (pre-existing RED phase, GREEN implemented) - - apps/web-console/tests/e2e/auth-shell.spec.ts - modified: - - apps/web-console/vitest.config.ts (already existed, no changes needed) -decisions: - - "WorkspaceSwitcher uses an HTML form POST to /console/account-switch so it works without client-side JS and avoids direct cookie mutation in a component." - - "account-switch route validates account_id against viewer.memberships before persisting — prevents users from switching into unauthorized accounts." - - "invitations/accept page does not set hive_account_id after joining — the new membership appears in the switcher and requires explicit selection per the plan spec." - - "VerificationBanner renders based on viewer.user.email_verified === false in layout so it appears on every console route without per-page logic." -metrics: - duration: "15min" - completed_date: "2026-03-29" - tasks_completed: 2 - files_created: 13 -requirements: - - AUTH-01 - - AUTH-02 - - AUTH-03 ---- - -# Phase 02 Plan 04: Console Shell, Viewer Gates, and Workspace Switching Summary - -**One-liner:** Verification-aware Next.js console shell with hive_account_id cookie persistence for explicit workspace switching and invitation acceptance without auto-switching. - -## What Was Built - -### Task 1: Verification-aware shell, viewer gates, and read-only members view - -- **`lib/control-plane/client.ts`** — `getViewer()` fetches from `GET /api/v1/viewer` forwarding `Authorization: Bearer ` and `X-Hive-Account-ID: ` when the `hive_account_id` cookie is set. Also exposes `getMembers()` for the members roster. -- **`lib/viewer-gates.ts`** — `canInviteMembers(viewer)`, `canManageApiKeys(viewer)`, and `allowedUnverifiedRoutes` constant with exactly 4 entries. -- **`app/console/layout.tsx`** — Async Server Component that calls `getViewer()`, renders `VerificationBanner` (shown when `email_verified === false`), `WorkspaceSwitcher`, and nav links. -- **`app/console/page.tsx`** — Dashboard page showing workspace `display_name` and reminder copy for unverified users. -- **`app/console/members/page.tsx`** — Read-only roster by default; shows disabled invite button with helper text "Email verification is required before you can invite teammates." when `canInviteMembers(viewer)` is false. -- **`components/nav-shell.tsx`** — Navigation sidebar component. -- **`components/verification-banner.tsx`** — Warning banner rendered in layout when email is unverified. -- **`playwright.config.ts`** — Playwright E2E config targeting `localhost:3000` by default, configurable via `PLAYWRIGHT_BASE_URL`. -- **Unit tests** (9/9 passing): `canInviteMembers`, `canManageApiKeys`, `allowedUnverifiedRoutes` coverage. - -### Task 2: Workspace switching and invitation acceptance - -- **`components/workspace-switcher.tsx`** — Renders all `viewer.memberships` as `