provisioning-code/.ncl-cache/4415b3082ee4db3b7a1e8be0870aed56e9a74fb975a3a35ea1d16f585ea9e25c.json

108 lines
6.6 KiB
JSON

{
"alternatives_considered": [
{
"option": "PostgreSQL",
"why_rejected": "Requires an external server process — no embedded mode for solo deployment. Schema evolution requires explicit migration tooling. No native document storage for Cedar policies."
},
{
"option": "SQLite",
"why_rejected": "No native namespace/tenant isolation. No document model. Concurrent write performance under multiple async tasks is constrained. No WebSocket server mode for multi-user."
},
{
"option": "Redis",
"why_rejected": "No real persistence guarantees (AOF is not the same as durable embedded storage). Key-value only — no document or relational queries for audit trail or task history."
}
],
"consequences": {
"negative": [
"SurrealDB v2 API uses snake_case builtins; bind() requires owned values; ID fields in structs needed to avoid RecordId parsing issues",
"MVCC conflicts under concurrent write load require retry_on_conflict with exponential backoff + jitter on store_secret, store_key, and lease operations",
"Full-text search and graph queries are available but deferred to avoid over-engineering"
],
"positive": [
"Single storage abstraction: SurrealPool is Clone, shareable across tokio tasks",
"No external process in solo mode: embedded RocksDB starts with the service binary",
"AI/MCP context: AI service queries audit:events and orchestrator:tasks directly without ETL pipelines",
"Test isolation: DbConfig::Memory (in-process Surreal<Mem>) requires no external binary"
]
},
"constraints": [
{
"check": {
"must_be_empty": true,
"paths": [
"core/nulib/",
"catalog/"
],
"pattern": "surrealdb|surreal|SurrealDB",
"tag": "Grep"
},
"claim": "CLI code (.nu files) must NOT access SurrealDB directly — all reads/writes from CLI go through service HTTP APIs",
"id": "cli-no-surrealdb-direct",
"rationale": "Direct SurrealDB access from CLI violates the SOLID boundary (ADR-014). All state mutations must go through the service layer to maintain audit trail and authorization.",
"scope": "core/nulib/, extensions/",
"severity": "Hard"
},
{
"check": {
"path": "platform/crates/platform-db/",
"present": true,
"tag": "FileExists"
},
"claim": "The five namespaces (orchestrator, vault, control_center, audit, workspace) under the `provisioning` database must not be changed without an ADR",
"id": "namespace-layout-fixed",
"rationale": "Namespace layout is the boundary contract between services. Changing it without an ADR risks data loss and cross-service coupling.",
"scope": "platform/crates/platform-db/",
"severity": "Hard"
},
{
"check": {
"cmd": "rg 'store_secret|store_key|create_lease' platform/ --include='*.rs' | grep -v retry",
"expect_exit": 1,
"tag": "NuCmd"
},
"claim": "Operations on store_secret, store_key, and lease operations must use retry_on_conflict with exponential backoff + jitter",
"id": "retry-on-mvcc-conflict",
"rationale": "SurrealDB MVCC conflicts are expected under concurrent write load. Without retry, concurrent task executions silently fail on lease creation.",
"scope": "platform/crates/platform-db/src/retry.rs, platform/secretumvault/",
"severity": "Soft"
}
],
"context": "The platform needs a single persistent data store that can operate embedded (RocksDB, zero external process) in solo mode, run as an external WebSocket server in multi-user deployments without schema changes, support five distinct service namespaces with well-typed schemas (orchestrator, vault, control_center, audit, workspace), store heterogeneous data (task logs append-only, secrets as encrypted blobs, Cedar policies as documents, audit events as time-series, git sync state as mutable), and be queryable by the AI/MCP service for context gathering without a separate analytics database. PostgreSQL requires a server process, SQLite has no native namespacing, Redis has no real persistence. SurrealDB is the only option supporting all five requirements simultaneously.",
"date": "2026-02-17",
"decision": "SurrealDB is the exclusive persistent store for all platform state. No service reads raw files or environment variables for credentials at runtime — all reads go through SurrealDB (secrets via Vault Service, which stores ciphertext in SurrealDB). Namespace layout under `provisioning` database: orchestrator (tasks, task_events, execution_logs, config_hashes, provider_cache), vault (secrets, keys, leases, secret_versions, audit_trail), control_center (users, sessions, cedar_policies, policy_evaluations), audit (events, metrics), workspace (registrations, deployments, git_sync_state, extensions). Mode selection via DbConfig: Memory (tests), Embedded (solo, RocksDB), Server (multi-user, WebSocket). Schema initialization via DEFINE TABLE IF NOT EXISTS DDL — no migration framework for additive changes.",
"id": "adr-013",
"ontology_check": {
"decision_string": "SurrealDB is the exclusive persistent store; CLI accesses state only via service HTTP APIs; five fixed namespaces under provisioning database",
"invariants_at_risk": [
"solid-boundaries",
"config-driven-always"
],
"verdict": "Safe"
},
"rationale": [
{
"claim": "Single storage abstraction across solo and multi-user modes",
"detail": "SurrealPool is Clone (Arc<Surreal<Any>> internally), shareable across tokio tasks. The same codebase connects to embedded RocksDB in solo mode and WebSocket server in multi-user — only the DbConfig changes."
},
{
"claim": "No external process in solo mode",
"detail": "Embedded RocksDB starts with the service binary. Solo mode requires no external database process, reducing startup dependencies and enabling CI runs without infrastructure."
},
{
"claim": "AI/MCP context without ETL pipelines",
"detail": "The AI service queries audit:events and orchestrator:tasks directly for context. SurrealDB's document+relational model handles heterogeneous schemas without separate analytics infrastructure."
},
{
"claim": "Test isolation via DbConfig::Memory",
"detail": "In-process Surreal<Mem> requires no external binary — every cargo test run gets a fresh, isolated database. Integration tests run without external infrastructure."
}
],
"related_adrs": [
"adr-012-nats-event-broker",
"adr-014-solid-enforcement",
"adr-015-solo-mode-architecture"
],
"status": "Accepted",
"title": "SurrealDB as the Global Persistent Store"
}