IBEX Harness
DocsBenchmarksBlogChangelogRoadmap
GitHub
IBEX Harness

Documentation

Architecture Decision RecordsADR-0002: Repository foundation bootstrapADR-0003: Branch protection and merge policyADR-0004: Protobuf and code generation policyADR-0005: Postgres migration strategyADR-0006: Auth protobuf contract (`ibex.auth.v1`)ADR-0007: Auth token validation implementationADR-0008: Security scanning and CI quality gatesADR-0009: Permission bitmap layoutADR-0010: Cryptography policyADR-0011: Proxy auth gRPC client and middlewareADR-0012: Proxy request normalization (OpenAI chat)ADR-0013: Proxy input validation and stable error envelopeADR-0014: Core domain migration sequencingADR-0015: Proxy rate limit skeleton (Phase 1)ADR-0016: Proxy agent identity verification (Phase 1)ADR-0017: Request ID and trace context strategy (Phase 1)ADR-0018: Graceful shutdown contract (Phase 1)ADR-0019: OpenTelemetry provider configuration (Phase 1)ADR-0020: Shared package boundaries — `packages/config` and `packages/apierror`ADR-0021: Prometheus Metric Catalog (Phase 1)ADR-0022: Health check contract (Phase 1)ADR-0023: Docs site architecture (Phase 1.5)ADR-0024: Benchmark data publishing modelADR-0025: LLM provider abstractionADR-0026: OpenAI client designADR-0027: Streaming dual-write strategyADR-0028: Auth cache designADR-0029: Token revocation propagation via Redis pub/subADR-0030: Directive versioning strategyADR-0031: System prompt injection strategyADR-0032: Session data model and retentionADR-0033: ClickHouse llm_traces schema and retentionADR-0034: Proxy overhead performance measurement methodologyADR-0035: Chat Idempotency-Key Redis dedupeADR-0038: Context assembly service design and gRPC contractADR-0039: Proxy Postgres ownership for session and directive storesADR-0040: Anthropic provider adapterADR-0041: Model capability registryADR-0042: Self-hosted OpenAI-compatible LLM adapterADR-0043: Tokenizer registry architectureADR-0044: Non-streaming response pipelineADR-0045: Streaming response transformationADR-0046: Embedder interface and profile registryADR-0047: Memory temporal validity foundationADR-0048: Memory multi-label categoriesADR-0049: Memory relationship graph readinessADR-0050: MCP server skeleton (transport, auth, audit)ADR-0051: Local LGTM observability stack (Phase 2.5 exit pull-forward)ADR-0052: Memory schema v2 expand (HNSW, quality columns)ADR-0053: Vector store abstraction and composite scoring v2ADR-0054: In-process Presidio PII detection in memoryADR-0055: Memory write-path exact and near-duplicate dedupADR-0056: Temporal-interval-aware conflict detectionADR-0057: Memory write-path persistence and orchestrationADR-0058: Semantic search FTS fallback triggerADR-0059: Hot cache read pathADR-0060: Memory service tenant isolation architectureADR-0061: Retrieval-quality benchmark methodologyADR-0062: Worker task observability and dead-letter handlingADR-0063: Extraction output schema v2ADR-0064: Extraction provider registry and batch executionADR-0065: Extraction batch idempotency and turn pointer orderingADR-0066: Extraction quality evaluation harnessADR-0067: Context capability catalog generate-and-diff for token budgetsADR-0068: Composite scoring-time relevance gateADR-0069: Context packer bounded DP knapsackADR-0070: Context formatting locked ordering and session nonceADR-0071: Context assembly gRPC degradation ladder and retrieval deadlineADR-0072: Extraction enqueue via worker HTTP (not Celery Redis wire format)ADR-0073: Organization Lifecycle and Cascading SuspensionADR-0074 Provider Credential StorageADR-0075 Per-Org Model Routing PolicyADR-0076: Circuit Breaker per ProviderADR-0077: Provider Fallback RoutingADR-0078: Operator runtime topology & environment contract (4.P.0)ADR-0079: Operator session RS256 dual-verify and TOTP step-upIBEX Console service rename and boundaryADR-0081: Fail-closed Proxy runtime controlsD2 metadata-only trace read contractD2 publication, identity, query, and lifecycle contract
ADRs›ADR-0078: Operator runtime topology & environment contract (4.P.0)
ADRs

ADR-0078: Operator runtime topology & environment contract (4.P.0)

Architecture decision record 0078 — Option B static operator UI + services/api CORS/CSRF/SSE.

ADR-0078: Operator runtime topology & environment contract (4.P.0)

  • Status: Accepted
  • Date: 2026-09-14
  • Authors: IBEX Harness team
  • ADR listing checked at write time: ls web/content/docs/adr/*.mdx — highest prior was 0077; this file is 0078.

Context

Track P needs a live operator UI ↔ API ↔ SSE topology before Track D dashboard work. Discovery confirmed:

  • Public web/ is a Cloudflare Pages static export (NEXT_STATIC_EXPORT=1 → output: "export"); converting it into a server-capable BFF would fight ADR-0023 and the existing deploy pipeline.
  • Auth/API today are PAT Bearer-only; no cookie/CORS/CSRF middleware existed in services/.
  • services/api /ready was startup-latched (unlike auth/proxy live checks; ADR-0022).
  • Provider SSE (stream_forward.go, ADR-0027) must never be reused for operator events.
  • Production Kubernetes/ingress for the management API is not shipped (kubernetes.mdx — no Helm charts yet).

Decision

1) Topology: Option B (locked)

SurfaceHostingRole
Public docs (web/)Cloudflare Pages ibex-harness-docsUnchanged static docs
Operator UI (services/dashboard)Cloudflare Pages ibex-harness-operatorNew static SPA shell
Operator API + operator-event SSEservices/apiCORS/CSRF origin; SSE origin

Option A′ rejected: converting web/ into a server-capable Next app (or attaching operator UI to the docs Pages project) would couple operator auth/SSE to the docs static-export pipeline, expand blast radius on docs deploys, and conflict with ADR-0023’s Pages-only decision for the public site.

No BFF / API gateway. CORS and CSRF are owned by services/api only.

2) Origins matrix (verified against live repo infra)

Ingress verification (2026-09-14; rechecked for PR 3): web/content/docs/deployment/kubernetes.mdx and infra/ contain no provisioned staging/production Ingress/Gateway for services/api. The chart now contains an optional API/SSE Ingress contract, disabled until an owner supplies a real host and TLS Secret. Staging/production API hostnames below remain planned DNS targets, not currently provisioned endpoints. Local uses host-process bind on IBEX_API_PORT default 8010.

EnvironmentOperator UI originAPI / SSE originTLS terminationCookie SameSiteNotes
localhttp://localhost:3100http://localhost:8010none (plaintext)LaxSame host (localhost) for cookie SameSite; do not mix with 127.0.0.1; Secure=false
previewhttps://*.ibex-harness-operator.pages.devnot provisioned — use local or future staging APICloudflare Pages edge (UI only)None + Secure when UI↔API are cross-sitePreview UI must not embed PATs in URLs; Pages deploy is workflow_dispatch only
staginghttps://operator.staging.ibexharness.com (planned)https://api.staging.ibexharness.com (planned; no Ingress yet)Planned: Cloudflare/edge for UI; API TLS TBD with 4.P.5 HelmStrict once both are *.ibexharness.com with Domain=.ibexharness.comFlag deviation until Ingress exists
productionhttps://operator.ibexharness.com (planned)https://api.ibexharness.com (planned; no Ingress yet)Same as stagingStrict + Secure=true + Domain=.ibexharness.comDocs remain https://ibexharness.com / docs paths

IBEX_ALLOWED_ORIGINS must list the exact operator UI origin(s) for that environment (never *).

3) CORS / CSRF

  • CORS: Starlette CORSMiddleware with explicit allow-list from IBEX_ALLOWED_ORIGINS, allow_credentials=True.
  • CSRF: double-submit cookie (DASHBOARD_CSRF_*) + X-CSRF-Token on cookie-authenticated mutating requests. Bearer-PAT callers are exempt. Login is CSRF-exempt (establishes cookies).

4) Session cookies (PROVISIONAL — 4.P.1)

Short-lived HttpOnly Secure (in non-local) access cookie (DASHBOARD_SESSION_COOKIE_NAME, default ibex_session) + refresh cookie (DASHBOARD_REFRESH_COOKIE_NAME, default ibex_refresh).

Provisional stub (default off): 4.P.0 may issue HS256 JWTs from services/api using JWT_HMAC_SECRET (≥32 bytes) only when IBEX_OPERATOR_FEATURE_ENABLED=true. JWT_PRIVATE_KEY_PEM is not accepted as HMAC material. Routes under /v1/operator/session stay disabled until 4.P.1 provides RS256 issuer/verifier in services/auth. Never store a PAT in the browser as a session.

5) Operator-event SSE

New module under services/api (app/sse/operator_events.py). Redis fan-in via redis.asyncio pub/sub (same intent as packages/redissub; Python-native). Does not import/call/copy forwardSSEStream / stream_forward.go.

Contract:

  • text/event-stream, flush-at-event-boundary, finite write deadline (IBEX_SSE_WRITE_DEADLINE_SECONDS, default 15s — stricter than provider path per F4-033)
  • Slow-write metric ibex_api_operator_sse_slow_writes_total
  • Monotonic id: fields; clients resume with Last-Event-ID (events with id <= Last-Event-ID are not re-sent)
  • Drain on process shutdown: lifespan _close_runtime begins drain, closes in-flight streams within IBEX_SHUTDOWN_TIMEOUT (API: integer seconds; Go services keep duration strings) — mirrors ADR-0018 intent. Do not replace the ASGI server's SIGTERM handler.

6) Readiness (ADR-0022 alignment)

  • /health — process alive only
  • /ready — live per-request Postgres + auth gRPC checks with timeouts (no longer startup-latch-only)

7) 4.P.5 scope note

Operator UI hosting stays Cloudflare-Pages-only and is exempt from the Helm/K8s buildout. Only API / workers / data plane need K8s overlays in 4.P.5.

The optional Helm Ingress fronts only the API and operator-event SSE service. It must remain disabled while the host, TLS Secret, forwarded-header policy, and proxy buffering/timeout behavior are not provisioned and verified. The console artifact, API artifact, schema, fixture, and rollback owner must share one evidence-manifest identity during staging verification.

8) Kill switch / rollback

IBEX_OPERATOR_FEATURE_ENABLED defaults to false. When false (or unset), operator session/SSE routes return 503 while public docs Pages continues unaffected. Operator Pages deploy is workflow_dispatch only (preview) until API Ingress exists — no production auto-deploy with a planned API hostname.

Consequences

  • New Pages project + operator-deploy.yml (independent of web-deploy.yml).
  • Env docs reconcile pre-existing IBEX_ALLOWED_ORIGINS / JWT_* / DASHBOARD_* names.
  • Staging/prod API hostnames require Ingress work in later milestones — documented as planned, not invented as live.

References

  • ADR-0018 — drain intent
  • ADR-0022 — /health vs /ready
  • ADR-0023 — docs Pages topology
  • ADR-0027 — provider SSE stays separate
  • Milestone 4.P.0

Was this page helpful?

Edit on GitHub

Last updated on

PreviousADR-0077: Provider Fallback RoutingNextADR-0079: Operator session RS256 dual-verify and TOTP step-up

On this page

  • Context
  • Decision
  • 1) Topology: Option B (locked)
  • 2) Origins matrix (verified against live repo infra)
  • 3) CORS / CSRF
  • 4) Session cookies (PROVISIONAL — 4.P.1)
  • 5) Operator-event SSE
  • 6) Readiness (ADR-0022 alignment)
  • 7) 4.P.5 scope note
  • 8) Kill switch / rollback
  • Consequences
  • References
0%