Non-Functional Requirements
Quality attributes — performance, reliability, compatibility, security, privacy.
Status: Draft v0.1 · 2026-05-03
Source documents: PRD.md, SPEC.md, API.md, CLI.md, MCP.md, AI.md
Companion: FR.md
This document captures the quality attributes of indx: how well the system must do what FR.md says it must do. Every NFR is testable; every NFR has a measurable target or a verification method.
How to read this document
Section titled “How to read this document”- ID: stable identifier (
NFR-<area>-<n>). - Target: the measurable bar. A number where possible.
- Measure: how compliance is verified.
- Priority:
MUST(release-blocking),SHOULD(target for v1, may slip to v1.1),MAY(post-v1).
NFR-PERF — Performance
Section titled “NFR-PERF — Performance”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-PERF-1 | MUST | Cold start to ready: < 2 s | Container docker run to /v1/health returning 200 on a 1 k-note vault. | PRD §8 |
| NFR-PERF-2 | MUST | p50 single-note read: ≤ 5 ms; p95: ≤ 50 ms | k6/autocannon against /v1/notes/{path} on a warm 10 k-note vault. | PRD §8 |
| NFR-PERF-3 | MUST | p50 search (10 k notes): ≤ 30 ms; p95: ≤ 200 ms | k6 against /v1/search?mode=lexical with realistic queries. | PRD §8 |
| NFR-PERF-4 | MUST | Full reindex of 10 k notes: < 30 s on a developer laptop (M-series or equivalent). | vault_reindex from cold cache; wall clock. | SPEC §5.3 |
| NFR-PERF-5 | MUST | Incremental reindex on a typical edit: < 5 s | chokidar event → index updated → note.updated emitted. | SPEC §5.3 |
| NFR-PERF-6 | SHOULD | NDJSON list streaming: first byte < 50 ms; sustained ≥ 5 000 rows/s. | Accept: application/x-ndjson against /v1/notes. | API §11 |
| NFR-PERF-7 | MUST | Patch op application: ≤ 20 ms for a single op on a 50 KB note. | Microbenchmark of core.md.patch. | SPEC §6.2 |
NFR-SCALE — Scalability
Section titled “NFR-SCALE — Scalability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-SCALE-1 | MUST | The system SHALL operate on vaults up to 100 k notes with the sizing in SPEC.md §10.4. | Boot, reindex, sample CRUD on a synthetic 100 k-note vault. | SPEC §10.4 |
| NFR-SCALE-2 | MUST | Sustained sustained write throughput: ≥ 50 writes/s/token without backpressure under default rate limit. | k6 PUT /v1/notes; observe latency stable. | API §13 |
| NFR-SCALE-3 | SHOULD | Concurrent SSE subscribers per server: ≥ 100 without affecting p95 read latency by more than 10 %. | Load harness with 100 SSE clients + read traffic. | API §9 |
| NFR-SCALE-4 | MAY | Horizontal scaling out of one container is (post-v1). v1 is single-container, single-process. | — | PRD §4 |
NFR-RES — Resource footprint
Section titled “NFR-RES — Resource footprint”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-RES-1 | MUST | Docker image: < 100 MB compressed. | docker manifest inspect of the published tag. | PRD §8 |
| NFR-RES-2 | MUST | Idle RAM: < 256 MB on a 1 k-note vault. | docker stats after 60 s idle post-boot. | PRD §8, SPEC §10.4 |
| NFR-RES-3 | SHOULD | Peak RAM during reindex on a 10 k-note vault: ≤ 500 MB. | docker stats during vault_reindex. | SPEC §10.4 |
| NFR-RES-4 | SHOULD | Index disk size: ~5 KB / note average. | Measure .indx/index.db after reindex on representative corpora. | SPEC §10.4 |
NFR-REL — Reliability & data integrity
Section titled “NFR-REL — Reliability & data integrity”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-REL-1 | MUST | Every write SHALL be atomic (write-to-temp + rename). A crash mid-write SHALL leave the vault in either the old or new state — never partial. | Fault-injection tests: SIGKILL the process between write and rename. | SPEC §6.1 |
| NFR-REL-2 | MUST | Concurrent writes to the same path SHALL be serialized; the second writer SHALL receive 409 etag_mismatch if If-Match is set. | Concurrency test harness. | SPEC §6.1, API §3 |
| NFR-REL-3 | MUST | Index drift after external file edits SHALL be self-healing. The watcher + content hashing SHALL converge within one debounce window (default 200 ms). | Modify a file with sed; observe event + index update. | SPEC §5.3 |
| NFR-REL-4 | MUST | Deleting .indx/ and restarting SHALL restore full functionality after one reindex. | Integration test: rm -rf .indx/; restart; vault_status reports indexed_notes matches file count. | PRD §9, SPEC §3 |
| NFR-REL-5 | MUST | Idempotent replays SHALL return the cached response within 24 h; replays with a different body SHALL fail with 409 idempotency_key_reused. | Integration test against /v1/notes/{p} with repeated Idempotency-Key. | API §12 |
NFR-COMPAT — Obsidian compatibility
Section titled “NFR-COMPAT — Obsidian compatibility”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-COMPAT-1 | MUST | Round-trip “open → write-back unchanged” on a corpus of 1 000+ public sample vaults SHALL produce byte-identical files when no edit is intended. | Property-based test in CI; corpus checked into tests/fixtures/vaults/. | PRD §8, SPEC §13 |
| NFR-COMPAT-2 | MUST | Round-trip with intended edits SHALL produce semantic-identical files (same AST modulo the requested change), preserving unknown frontmatter keys. | Same harness as NFR-COMPAT-1; assertions on AST diff. | SPEC §6.2, FR-F-2 |
| NFR-COMPAT-3 | MUST | Indx SHALL never modify .obsidian/. After indx writes, opening the same folder in Obsidian SHALL show no surprises. | Manual smoke test in CI; .obsidian/ checksum unchanged. | PRD §6 (J6), SPEC §3 |
| NFR-COMPAT-4 | MUST | The on-disk format SHALL be Obsidian’s existing format plus a single .indx/ directory; indx-internal files SHALL NOT pollute user folders. | Filesystem audit after a session. | PRD §4 |
NFR-USE-AGENT — Agent ergonomics
Section titled “NFR-USE-AGENT — Agent ergonomics”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-USE-AGENT-1 | MUST | Agent task success rate on the internal 50-task benchmark (CRUD, search, link-graph, canvas) SHALL be ≥ 95 %. | Benchmark run on each release candidate using Claude/GPT/local models. | PRD §8, SPEC §13 |
| NFR-USE-AGENT-2 | MUST | Every UI capability SHALL be reachable via at least one tool call on each of API, CLI, MCP — no screen-scraping required. | API/CLI/MCP coverage matrix in CI. | PRD §1, MCP §7 |
| NFR-USE-AGENT-3 | MUST | Output across surfaces SHALL be deterministic given the same inputs (no spinners, ANSI, timestamps, or non-deterministic ordering on stdout). | Snapshot tests on CLI/MCP output. | CLI §1 |
| NFR-USE-AGENT-4 | MUST | Errors SHALL be machine-readable: stable code, structured details, predictable HTTP status / exit code / isError. | Cross-surface error code tests. | API §4, CLI §2.2, MCP §3 |
| NFR-USE-AGENT-5 | MUST | A new agent connecting cold SHALL be productive in one round trip by calling tools/list + resources/list (MCP), GET /openapi.json (API), or indx schema * (CLI). | Onboarding test: agent that has never seen indx completes a CRUD task using only discovery output. | MCP §1, API §14, CLI §3.10 |
NFR-USE-HUMAN — Human usability
Section titled “NFR-USE-HUMAN — Human usability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-USE-HUMAN-1 | MUST | The web UI SHALL meet WCAG 2.1 AA on the editor, file tree, search, and activity panel. | Lighthouse + axe-core in CI. | PRD §3 |
| NFR-USE-HUMAN-2 | MUST | UI SHALL be responsive at viewports ≥ 320 px (mobile-class) without functionality loss. | Storybook viewport stories; Playwright responsive checks. | PRD §5 |
| NFR-USE-HUMAN-3 | SHOULD | Time-to-first-edit on an empty vault: ≤ 60 s from docker run per PRD.md §6 J5. | Manual stopwatch test in onboarding script. | PRD §6 (J5) |
| NFR-USE-HUMAN-4 | MUST | The UI SHALL never block on a remote AI call; semantic search degradation SHALL be silent (warning surfaced, not modal). | Disconnect embeddings provider; UI remains usable. | FR-S-4 |
NFR-SEC — Security
Section titled “NFR-SEC — Security”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-SEC-1 | MUST | All paths supplied via API/CLI/MCP SHALL be normalized; .. escapes and absolute paths SHALL be rejected with 400 bad_request. | Property test: 1 k random path strings; none escape INDX_VAULT. | SPEC §6.4, §12 |
| NFR-SEC-2 | MUST | Tokens SHALL be stored hashed (or kept only in-memory from env). The .indx/config.json SHALL persist token IDs + scopes, not the secret itself. | Inspect config.json after token issuance. | SPEC §8.1, §9.2 |
| NFR-SEC-3 | MUST | The system SHALL make zero outbound calls unless an embeddings provider is explicitly configured (no telemetry, no analytics, no update pings). | Network-namespace test: deny egress; system boots and serves with embeddings disabled. | PRD §9, SPEC §12 |
| NFR-SEC-4 | MUST | Per-token rate limit SHALL default to 100 rps with 1 000 burst; configurable. Limits SHALL apply uniformly across surfaces sharing a token. | Load test verifying 429 + headers. | API §13, SPEC §12 |
| NFR-SEC-5 | MUST | Web UI sessions SHALL set SameSite=Strict cookies; CSRF tokens SHALL be required for any state-changing request originating from the UI. | OWASP ZAP CSRF probe. | SPEC §8.2 |
| NFR-SEC-6 | SHOULD | The Docker image SHALL be built from gcr.io/distroless/nodejs24-debian12 (no shell, minimal CVE surface). | docker history audit. | SPEC §10.1 |
| NFR-SEC-7 | MUST | The threat model SHALL be documented: trust boundary = host filesystem; defenses against unauthenticated callers, scope escalation, path traversal; explicitly out of scope: malicious agent with valid vault:write, host root, embeddings provider attacks. | docs/SECURITY.md published before GA. | SPEC §12 |
| NFR-SEC-8 | SHOULD | Dependency CVEs of severity ≥ HIGH SHALL be patched within 14 days. | pnpm audit + GitHub advisories cron. | — |
NFR-PRIV — Privacy
Section titled “NFR-PRIV — Privacy”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-PRIV-1 | MUST | No vault content SHALL leave the host except (a) embedding requests to a configured provider, and (b) responses to authenticated callers. | Network capture during a browse + edit session with embeddings off. | PRD §9, SPEC §12 |
| NFR-PRIV-2 | MUST | Logs SHALL NOT include note bodies or frontmatter values; paths and sizes only. | Log-redaction test on a vault containing canary strings. | SPEC §11 |
| NFR-PRIV-3 | SHOULD | When embeddings are enabled, the user SHALL be able to scope which paths are eligible for embedding (allow/deny globs). | Integration test: gated paths produce no provider calls. | SPEC §9.2 |
NFR-DEPLOY — Deployability
Section titled “NFR-DEPLOY — Deployability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-DEPLOY-1 | MUST | A user SHALL be able to launch indx with one docker run plus a vault volume mount and a token env var. | README quickstart followed verbatim by a fresh user. | PRD §4, SPEC §10.3 |
| NFR-DEPLOY-2 | MUST | The image SHALL ship a healthcheck endpoint (GET /v1/health) suitable for Docker, Kubernetes, and Vercel-style probes. | docker inspect shows healthcheck. | SPEC §10.1, §11 |
| NFR-DEPLOY-3 | MUST | The container SHALL run as a non-root user with the vault volume writable to that user. | docker exec id returns non-root. | SPEC §10 |
| NFR-DEPLOY-4 | SHOULD | The image SHALL run on linux/amd64 and linux/arm64. | Multi-arch buildx in CI. | — |
| NFR-DEPLOY-5 | MUST | The CLI SHALL be installable both via npm i -g @indx/cli and via the bundled binary inside the Docker image. | npm publish smoke test; docker exec ... indx --version. | SPEC §1 |
| NFR-DEPLOY-6 | SHOULD | Honoring X-Forwarded-* SHALL be opt-in via INDX_TRUST_PROXY=true so the system is safe behind a reverse proxy. | Proxy integration test. | SPEC §9.1 |
NFR-MAINT — Maintainability
Section titled “NFR-MAINT — Maintainability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-MAINT-1 | MUST | TypeScript strict mode SHALL be on across the monorepo; no any in public APIs. | tsc --noEmit in CI. | SPEC §2 |
| NFR-MAINT-2 | MUST | A single Zod tree SHALL be the source of truth; TS types, JSON Schema, OpenAPI 3.1 SHALL be derived. | Build script generates all three; CI fails on drift. | SPEC §14, FR-PKG-1 |
| NFR-MAINT-3 | MUST | Unit-test coverage on @indx/core SHALL be ≥ 85 % lines. | vitest --coverage. | SPEC §13 |
| NFR-MAINT-4 | MUST | Property-based tests SHALL run on the AST patch ops and the round-trip serializer on every PR. | CI required check. | SPEC §13 |
| NFR-MAINT-5 | MUST | Adding a capability SHALL be one PR touching: (1) Zod schema, (2) @indx/core op, (3) API route, (4) CLI verb, (5) MCP tool. PR template SHALL enforce. | PR template + CI lint. | SPEC §14 |
| NFR-MAINT-6 | SHOULD | New dependencies SHALL require a “why not the existing stack” note in docs/DECISIONS.md. | PR review checklist. | IMPLEMENTATION §0 |
NFR-COMPAT-API — API stability
Section titled “NFR-COMPAT-API — API stability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-COMPAT-API-1 | MUST | /v1/* SHALL be additive-only; no breaking changes within a major. | OpenAPI diff in CI fails on breaking changes under /v1. | API §15 |
| NFR-COMPAT-API-2 | MUST | Breaking changes SHALL ship under /v2; /v1 SHALL remain available for at least one minor release after /v2 GA. | Release process documented. | API §15 |
| NFR-COMPAT-API-3 | MUST | Deprecated paths SHALL include a Deprecation: <date> header and a Link: <docs>; rel="deprecation". | Integration test on deprecated routes. | API §15 |
| NFR-COMPAT-API-4 | MUST | The OpenAPI document SHALL be the contract; clients generated from it SHALL be the supported client path. | Codegen smoke test. | API §14 |
NFR-OBS — Observability
Section titled “NFR-OBS — Observability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-OBS-1 | MUST | Logs SHALL be structured JSON on stdout (pino); every log line SHALL include level, time, msg, and an actor field where applicable. | Log schema test. | SPEC §11 |
| NFR-OBS-2 | MUST | /v1/health SHALL respond in < 50 ms under normal load. | k6 probe. | SPEC §11 |
| NFR-OBS-3 | SHOULD | OTLP traces SHALL include spans for: route handler, core op, SQLite query, embedding call. | Trace inspection in a Tempo/Jaeger backend. | SPEC §11 |
| NFR-OBS-4 | MUST | The .indx/events.log SHALL roll at 10 MB and retain at least 5 historical files. | File rotation test. | SPEC §7 |
NFR-PORT — Portability
Section titled “NFR-PORT — Portability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-PORT-1 | MUST | The runtime SHALL target Node.js 24 LTS. No platform-specific binaries beyond better-sqlite3 (and sqlite-vss if enabled). | Build matrix: linux/amd64, linux/arm64, macOS dev. | SPEC §2 |
| NFR-PORT-2 | MUST | Path handling SHALL be POSIX. Vault files containing characters legal on Linux SHALL not require escaping in API/CLI/MCP beyond a single percent-encoding. | Edge-case path test corpus. | SPEC §6.4, API §5.1 |
| NFR-PORT-3 | SHOULD | The CLI SHALL run on Linux, macOS, and Windows (WSL acceptable). | npm install + smoke test on each. | — |
NFR-LIC — Licensing & openness
Section titled “NFR-LIC — Licensing & openness”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-LIC-1 | MUST | The codebase SHALL be MIT-licensed; the OpenAPI document and JSON Schemas SHALL be CC0 or MIT. | LICENSE file; spec licensing notice. | PRD §4 |
| NFR-LIC-2 | MUST | All dependencies SHALL have permissive licenses (MIT/BSD/Apache-2.0); copyleft (GPL/AGPL) is forbidden in the runtime image. | License-check CI step. | — |
| NFR-LIC-3 | SHOULD | A reproducible-build attestation SHALL be published for each release. | SLSA-style provenance. | — |
NFR-AI — Built-in AI runtime
Section titled “NFR-AI — Built-in AI runtime”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-AI-1 | MUST | When no AI provider is configured, indx SHALL make zero outbound calls and SHALL serve the vault with full functionality minus AI ops. | Egress-deny network namespace test; /v1/ai/* returns 503 ai_unavailable. | AI §2, NFR-PRIV-1 |
| NFR-AI-2 | MUST | ai_summarize of a single 50 KB note SHALL complete in ≤ 6 s p95 against a streaming-capable provider; ai_ask with top_k: 8 SHALL stream a first byte in ≤ 1.5 s p95. | k6 + provider stub matching real provider latencies. | AI §5.1, §5.2 |
| NFR-AI-3 | MUST | Repeating an AI op with identical inputs against an unchanged vault SHALL be served from cache in ≤ 50 ms with usage.cost_usd: 0. | Integration test: warm cache hit on /v1/ai/summarize. | AI §8.1 |
| NFR-AI-4 | MUST | AI cache invalidation SHALL be incremental — a single note edit SHALL only invalidate cache entries whose scope cited that note. | Unit test on cache key + content fingerprint. | AI §8.1 |
| NFR-AI-5 | MUST | Citation verification post-hoc SHALL be 100 %: every citation in a returned citations[] SHALL resolve to an existing path/etag/anchor at the moment of return; broken citations SHALL be dropped or fail the op (ai_grounding_failed). | Property test: 1 k random AI runs, zero unverified citations leak. | AI §6 |
| NFR-AI-6 | MUST | Determinism: with temperature: 0, seed: 0, and a recording-mode provider stub, AI ops SHALL produce byte-identical structured payloads across runs. | Snapshot tests in tests/ai/. | AI §13 |
| NFR-AI-7 | MUST | AI prompts and model outputs SHALL NOT appear in pino logs or .indx/events.log. Only paths, counts, durations, costs, and error codes are persisted. | Log-redaction test on a vault containing canary prompt/output strings. | AI §12, NFR-PRIV-2 |
| NFR-AI-8 | SHOULD | When INDX_AI_DAILY_COST_USD is set, the runtime SHALL refuse calls projected to exceed the cap; over-cap calls SHALL respond within ≤ 100 ms without contacting the provider. | Quota integration test. | AI §8.3 |
| NFR-AI-9 | MUST | INDX_AI_DENY_GLOBS SHALL exclude matching paths from any AI op even when an agent specifies them in scope; excluded items SHALL be reported via globs_excluded warning, never silently included. | Integration test against a deny-listed Private/** path. | AI §14, NFR-PRIV-3 |
| NFR-AI-10 | MUST | AI ops SHALL NOT auto-mutate the vault: ai_relate returns draft patches only; ai_toc writes only when write: { path } is explicit and the caller carries vault:write. | Authorization + behavior tests. | AI §5.4, FR-A-2 |
| NFR-AI-11 | SHOULD | The 50-task agent benchmark SHALL include ≥ 14 AI tasks covering each op (summarize, ask, toc, relate, tag, metadata, extract); pass rate SHALL meet NFR-USE-AGENT-1. | Benchmark run; per-task pass/fail recorded. | NFR-USE-AGENT-1, AI §1 |
| NFR-AI-12 | MUST | ai_tag and ai_metadata suggest mode SHALL be deterministic in tag/value choice given identical inputs and temperature: 0, seed: 0 (see NFR-AI-6); only rationale text MAY vary across runs. | Snapshot tests on a 50-note fixture. | AI §13 |
| NFR-AI-13 | MUST | Apply ops (ai_tag / ai_metadata / ai_extract with apply: true) SHALL be per-note atomic: a SIGKILL between writes SHALL never leave a note partially modified, and SHALL never leave the index inconsistent with the on-disk content of any note. | Fault-injection test on a 100-note batch. | AI §5.8, NFR-REL-1 |
| NFR-AI-14 | MUST | ai_metadata writes for special keys (tags, aliases, cssclasses) SHALL produce frontmatter that round-trips byte-identically through Obsidian (NFR-COMPAT-1). | Round-trip property test on a 200-note fixture. | NFR-COMPAT-1, FR-F-3 |
NFR-MIG — Migration & evolvability
Section titled “NFR-MIG — Migration & evolvability”| ID | Priority | Target | Measure | Trace |
|---|---|---|---|---|
| NFR-MIG-1 | MUST | Schema migrations on .indx/index.db SHALL run automatically on startup if the schema version differs; failures SHALL roll back and surface in /v1/health. | Boot against an out-of-date DB; verify migration. | SPEC §3 |
| NFR-MIG-2 | MUST | The vault directory format SHALL never require migration to be opened by a newer indx version (forward-compatible by construction; the on-disk format is Obsidian’s). | Sample-vault tests across releases. | PRD §4 |
Acceptance: how NFRs gate a release
Section titled “Acceptance: how NFRs gate a release”A release candidate SHALL pass the following gates before being tagged latest:
- Performance gate: k6 scripts in
tests/perf/meet NFR-PERF-1 through NFR-PERF-5 on the reference 10 k-note vault. - Compatibility gate: Round-trip property tests (NFR-COMPAT-1, -2) pass on the 1 k-vault corpus.
- Agent gate: 50-task benchmark passes at ≥ 95 % (NFR-USE-AGENT-1) on at least two model families.
- Security gate: Dependency audit clean (NFR-SEC-8); ZAP probe clean (NFR-SEC-5); penetration-test path-traversal corpus passes (NFR-SEC-1).
- Resource gate:
docker statsconfirms NFR-RES-1, -2, -3 on the reference vault. - API gate: OpenAPI breaking-change diff is empty against the previous
/v1release (NFR-COMPAT-API-1).
Failures on any MUST gate block the release. SHOULD gates produce a release-note advisory but do not block.