Functional Requirements
Functional requirements with stable IDs, traceable to spec sections.
Status: Draft v0.1 · 2026-05-03
Source documents: PRD.md, SPEC.md, API.md, CLI.md, MCP.md, AI.md
Scope: v1.0 (alpha → GA). Items marked (post-v1) are tracked here for traceability but are out of v1 scope per PRD.md §5.
How to read this document
Section titled “How to read this document”- Every requirement has a stable ID (
FR-<area>-<n>). IDs are referenced from epics, tests, and pull requests. - Priority:
MUST(v1 release-blocking),SHOULD(v1 target, may slip to v1.1),MAY(nice-to-have / post-v1). - Trace column links the requirement back to the source document section so reviewers can validate intent.
- A requirement is satisfied iff its acceptance criteria can be exercised against any one of the three surfaces (API / CLI / MCP) — surface-specific items are tagged.
FR-V — Vault foundation
Section titled “FR-V — Vault foundation”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-V-1 | MUST | The system SHALL operate against a single user-supplied vault directory mounted at INDX_VAULT (default /vault). | SPEC §3 |
| FR-V-2 | MUST | The system SHALL treat the vault directory as the source of truth; deleting .indx/ and restarting SHALL produce a fully functional system after one reindex. | PRD §9, SPEC §3 |
| FR-V-3 | MUST | The system SHALL never write to .obsidian/; it MAY read it (config only) to honor user preferences such as attachment folder and tag delimiters. | PRD §6 (J6), SPEC §3 |
| FR-V-4 | MUST | The system SHALL ignore .git/, .indx/, and .obsidian/ during the indexing walk (the latter is read separately for config). | SPEC §5.3 |
| FR-V-5 | MUST | The system SHALL accept only vault-relative POSIX paths through every public surface, normalizing leading /, collapsing .., and rejecting paths that escape the vault root. | SPEC §6.4, API §5.1 |
| FR-V-6 | SHOULD | The system SHALL support relocating .indx/ outside the vault via INDX_INDEX_DIR for users who do not want index files inside the vault. | SPEC §10.2, PRD §11 (Q1) |
| FR-V-7 | MUST | Path matching SHALL be case-sensitive by default; an opt-in case_insensitive_match setting SHALL fall back to a case-folded lookup when an exact match fails. | SPEC §6.4 |
| FR-V-8 | MUST | The system SHALL support exactly one vault per running container in v1; multi-vault is (post-v1). | PRD §7 |
FR-N — Notes (markdown CRUD)
Section titled “FR-N — Notes (markdown CRUD)”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-N-1 | MUST | The system SHALL support reading a note returning { path, kind, etag, mtime_ms, size, frontmatter, outline, body }, with an include selector to limit fields. | API §6.1, CLI §3.1, MCP §3.1 |
| FR-N-2 | MUST | The system SHALL support creating a note via PUT/note write/note_write, accepting either a body (with optional fenced frontmatter) or a { frontmatter, body } pair. | API §6.2, CLI §3.1, MCP §3.2 |
| FR-N-3 | MUST | The system SHALL support replacing an existing note with the same verbs as FR-N-2, honoring If-Match for optimistic concurrency. | API §6.2, SPEC §6.1 |
| FR-N-4 | MUST | The system SHALL support deleting a note (hard delete) and SHALL maintain a 5-minute tombstone so subsequent reads return 410 gone (vs. 404 not_found). | API §6.4 |
| FR-N-5 | MUST | The system SHALL support moving/renaming a note. By default the operation rewrites incoming wikilinks to keep the graph intact; update_links: false SHALL disable rewriting. | API §6.5, SPEC §6.3, MCP §3.2 |
| FR-N-6 | MUST | The system SHALL support listing notes with filters: path_glob, tag (repeatable), frontmatter (key=value, repeatable), limit, opaque cursor. | API §6.6, CLI §3.1 |
| FR-N-7 | MUST | List endpoints SHALL stream NDJSON (one object per line, no envelope, no cursor) when the caller requests Accept: application/x-ndjson / --ndjson. | API §11, CLI §3.1 |
| FR-N-8 | MUST | The system SHALL expose a fast existence check (note_exists / note exists / HTTP HEAD) that returns without loading the body. | CLI §3.1, MCP §3.1 |
| FR-N-9 | MUST | The system SHALL expose a heading outline of a note as [{ level, text, line, block_id }]. | API §6.1, CLI §3.1 |
| FR-N-10 | SHOULD | Note creation SHALL accept an if_not_exists flag that turns the operation into an idempotent create (no error if the note already exists; the existing resource is returned). | CLI §4.1, MCP §3.2 |
FR-E — Editing model (atomic writes & patches)
Section titled “FR-E — Editing model (atomic writes & patches)”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-E-1 | MUST | All writes SHALL go through one engine path (core.vault.writeNote/peers); UI, API, CLI, MCP SHALL share the path. | PRD §9, SPEC §6.1 |
| FR-E-2 | MUST | Every write SHALL be atomic: write-to-temp + rename. The index SHALL be updated synchronously before the write returns. | SPEC §6.1 |
| FR-E-3 | MUST | Every read SHALL return a strong ETag (first 16 hex chars of xxhash64 of bytes); every write SHALL accept and honor If-Match and reject mismatches with 409 etag_mismatch. | API §3, §6.2; SPEC §6.1 |
| FR-E-4 | MUST | The system SHALL implement an AST-aware patch language with the following ops: set_frontmatter, delete_frontmatter, append_body, prepend_body, insert_before_heading, insert_after_heading, replace_section, replace_block, rename_heading. | SPEC §6.2, API §6.3, MCP §3.4 |
| FR-E-5 | MUST | A patch request SHALL accept an ordered array of ops applied in order; failure of any op SHALL roll back the entire request (no partial writes). | SPEC §6.2, API §6.3 |
| FR-E-6 | MUST | Patches SHALL be applied against the parsed AST, not text positions, and re-serialized through a printer configurable per-vault (e.g., list marker, thematic break) via .indx/config.json. | SPEC §6.2 |
| FR-E-7 | MUST | Every write SHALL accept an Idempotency-Key. Replays within 24 h SHALL return the original cached response with X-Indx-Idempotent-Replay: true; replays with the same key but a different body SHALL return 409 idempotency_key_reused. | API §12 |
| FR-E-8 | MUST | Every write verb SHALL return the full new resource (read-after-write), including new etag, mtime_ms, and outline. The agent SHALL NOT need a follow-up GET. | CLI §3.1, MCP §3.4 |
| FR-E-9 | MUST | Validation of new content SHALL run before write. Parse warnings are allowed; parse errors SHALL reject the write with 422 parse_failed. | SPEC §6.1, API §4.1 |
| FR-E-10 | MUST | When INDX_READONLY=true, every write surface SHALL reject writes with 423 readonly. | SPEC §9.1, API §4.1 |
FR-F — Vault format (Obsidian compatibility)
Section titled “FR-F — Vault format (Obsidian compatibility)”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-F-1 | MUST | The system SHALL parse YAML frontmatter between --- fences at the start of a file; types SHALL be inferred (string, number, bool, list, date, datetime). | SPEC §4.1 |
| FR-F-2 | MUST | Unknown frontmatter keys SHALL be preserved verbatim on round-trip. | SPEC §4.1, PRD §11 (Q4) |
| FR-F-3 | MUST | The system SHALL recognize and index Obsidian conventions: tags:, aliases:, cssclasses:. | SPEC §4.1 |
| FR-F-4 | MUST | The system SHALL parse and resolve wikilink forms: [[Note]], [[Note|Display]], [[Note#Heading]], [[Note#^block-id]], [[#Heading]] (intra-note). | SPEC §4.2 |
| FR-F-5 | MUST | Wikilink resolution SHALL match by filename without extension, falling back to aliases: in target frontmatter. | SPEC §4.2 |
| FR-F-6 | MUST | The system SHALL parse embed forms: ![[Note]], ![[Note#Heading]], ![[Note#^id]], ![[image.png]], ![[file.pdf]]. | SPEC §4.3 |
| FR-F-7 | MUST | The system SHALL recognize callouts: > [!type] … with the standard type list (note, tip, warning, …). Unknown types SHALL pass through. | SPEC §4.4 |
| FR-F-8 | MUST | The system SHALL parse tags #tag and #nested/tag (allowed chars: letters, digits-not-first, _, -, /); frontmatter tags: SHALL also index without leading #. | SPEC §4.5 |
| FR-F-9 | MUST | The system SHALL parse block IDs (^block-id at the end of a block) and make them addressable as [[Note#^block-id]]. | SPEC §4.6 |
| FR-F-10 | MUST | LaTeX ($inline$, $$display$$) and Mermaid fenced blocks SHALL pass through unmodified; rendering is the UI’s responsibility. | SPEC §4.7 |
| FR-F-11 | MUST | A round-trip “open → write-back unchanged” against an Obsidian sample vault SHALL produce byte-identical files when no edit is intended. | PRD §8, SPEC §13 |
FR-S — Search
Section titled “FR-S — Search”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-S-1 | MUST | The system SHALL provide lexical search via SQLite FTS5 (BM25) over title, body, and tags. | SPEC §5.2 |
| FR-S-2 | SHOULD | The system SHALL provide semantic search (KNN over notes_vss) when an embeddings provider is configured. | SPEC §5.2 |
| FR-S-3 | SHOULD | The system SHALL provide hybrid search (reciprocal rank fusion of lexical + semantic) when embeddings are configured. | SPEC §5.2 |
| FR-S-4 | MUST | When mode=hybrid or semantic is requested without embeddings configured, the system SHALL fall back to lexical, set mode_used: "lexical", and include embeddings_unavailable in warnings. | API §7.1, CLI §3.4 |
| FR-S-5 | MUST | Search SHALL accept structural filters composable with any mode: frontmatter equality/range/contains, tag:, path: glob, linksTo:[[X]], linkedFrom:[[Y]]. | SPEC §5.2 |
| FR-S-6 | MUST | Search results SHALL include { path, title, score, snippet, tags, matched_in[] }. | API §7.1 |
| FR-S-7 | MUST | Search SHALL paginate via opaque cursor; NDJSON streaming SHALL be available via Accept. | API §11 |
| FR-S-8 | MAY | Cross-vault search is (post-v1). | PRD §7 |
FR-L — Links & graph
Section titled “FR-L — Links & graph”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-L-1 | MUST | The system SHALL return inbound (backlinks) and outbound (forward) link sets for a given note, each with { src_path, dst_path, line, anchor, kind }. | API §5, CLI §3.2 |
| FR-L-2 | MUST | The system SHALL list unresolved wikilinks (targets that do not match any vault file or alias). | API §5, CLI §3.2 |
| FR-L-3 | MUST | The system SHALL list orphan notes (notes with no inbound and no outbound links). | API §5, CLI §3.2 |
| FR-L-4 | MUST | When a note is moved, the system SHALL by default rewrite incoming wikilinks to keep the graph consistent and report the count of rewrites. | API §6.5, SPEC §6.3 |
| FR-L-5 | SHOULD | The system SHALL detect tag co-occurrence and expose it via the graph queries used by the UI. | PRD §7 |
| FR-L-6 | MAY | A force-directed visual graph view is provided in a basic form only in v1. | PRD §7 |
FR-T — Tags
Section titled “FR-T — Tags”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-T-1 | MUST | The system SHALL list all tags with counts. | API §5, CLI §3.3 |
| FR-T-2 | MUST | The system SHALL list notes for a given tag, paginated. | API §5, CLI §3.3 |
| FR-T-3 | MUST | The system SHALL bulk-rename a tag across the vault, returning the affected paths and supporting dry_run. | CLI §3.3, MCP §3.2 |
FR-C — Canvas
Section titled “FR-C — Canvas”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-C-1 | MUST | The system SHALL read and write .canvas files conforming to JSON Canvas 1.0. | SPEC §4.8, API §8 |
| FR-C-2 | MUST | The system SHALL expose canvas patch ops: add_node, update_node, remove_node, add_edge, remove_edge, move_node. | CLI §3.5 |
| FR-C-3 | MUST | Unknown canvas fields SHALL be preserved on round-trip. | SPEC §4.8 |
| FR-C-4 | SHOULD | The web UI SHALL provide a read-only canvas viewer in v1; visual editing is (post-v1). | PRD §7 |
FR-B — Bases
Section titled “FR-B — Bases”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-B-1 | MUST | The system SHALL read .base YAML files (filters, formulas, properties, views). | SPEC §4.9, API §8 |
| FR-B-2 | MUST | The system SHALL execute a .base query against the vault index and return matching rows; NDJSON streaming SHALL be available. | API §8, CLI §3.6 |
| FR-B-3 | SHOULD | The system SHALL allow creating/updating a .base YAML file directly; row data continues to live in the linked notes’ frontmatter. | SPEC §4.9 |
| FR-B-4 | MAY | A visual .base builder UI is (post-v1). | PRD §7 |
FR-W — Watch & events
Section titled “FR-W — Watch & events”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-W-1 | MUST | The system SHALL watch the vault with chokidar, debouncing changes by 200 ms, and incrementally diff-and-upsert into the index. | SPEC §5.3 |
| FR-W-2 | MUST | The system SHALL emit note.created, note.updated, note.deleted, note.moved, index.reindexed events; each event SHALL carry an Actor (ui/api/cli/mcp/fs). | SPEC §7 |
| FR-W-3 | MUST | The system SHALL expose events via SSE at GET /v1/events, supporting query filters (paths, kinds) and Last-Event-ID resume. | API §9 |
| FR-W-4 | MUST | The system SHALL append events to a rolling .indx/events.log (NDJSON, rotated at 10 MB) for audit. | SPEC §7 |
| FR-W-5 | MUST | The system SHALL provide vault_reindex returning 202 Accepted with a job id; progress SHALL be observable via the event stream. | API §10 |
| FR-W-6 | MUST | The MCP server SHALL expose resources/subscribe over vault://<glob> URIs, emitting notifications/resources/updated on change. | MCP §4 |
FR-A — Authentication & authorization
Section titled “FR-A — Authentication & authorization”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-A-1 | MUST | The system SHALL accept static bearer tokens of format indx_<32 hex chars> configured via INDX_TOKEN (single) or INDX_TOKENS_FILE (JSON array). | SPEC §8.1, API §2 |
| FR-A-2 | MUST | Tokens SHALL support optional scopes: vault:read, vault:write, vault:admin, path:<glob>. | SPEC §8.1, API §2 |
| FR-A-3 | MUST | Requests without a valid token SHALL receive 401 unauthorized; requests with insufficient scope SHALL receive 403 forbidden. | API §4.1 |
| FR-A-4 | MUST | The MCP tool catalog advertised to a session SHALL be filtered by scope (e.g., a vault:read-only session SHALL NOT see write tools). | MCP §6.2 |
| FR-A-5 | MUST | The web UI SHALL use session cookies tied to the bearer token with SameSite=Strict. Cross-origin API requests SHALL require Authorization: Bearer …. | SPEC §8.2 |
| FR-A-6 | MAY | Per-user OIDC and per-user vaults are (post-v1). | PRD §5, SPEC §8.3 |
FR-API — HTTP API surface
Section titled “FR-API — HTTP API surface”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-API-1 | MUST | The HTTP API SHALL be served under /v1/*. | API §1 |
| FR-API-2 | MUST | Every response SHALL use the envelope { ok, data | error }. Errors SHALL be RFC 7807-shaped inside error and carry a stable machine code. | API §3, §4 |
| FR-API-3 | MUST | The API SHALL expose every error code listed in API.md §4.1 with the documented HTTP status. | API §4.1 |
| FR-API-4 | MUST | The API SHALL implement the resource map in API.md §5 for v1 (notes, search, links, tags, canvas, bases, vault admin, events, openapi). | API §5 |
| FR-API-5 | MUST | All list/search endpoints SHALL paginate via opaque cursor. Offset-style pagination SHALL NOT be exposed. | API §1, §11 |
| FR-API-6 | MUST | The API SHALL set ETag, Last-Modified, X-Indx-Actor, and X-Indx-Indexed-At on resource responses. | API §3 |
| FR-API-7 | MUST | The API SHALL serve a complete OpenAPI 3.1 document at GET /openapi.json reflecting the running server’s exact capabilities (feature-flagged ops appear iff configured). | API §14, SPEC §14 |
| FR-API-8 | MUST | URL-prefix /v1 SHALL be additive-only. Breaking changes SHALL ship under /v2. Paths scheduled for removal SHALL include a Deprecation: header and a Link: …; rel="deprecation". | API §15 |
| FR-API-9 | MUST | The API SHALL implement per-token sliding-window rate limiting (default 100 rps, 1000 burst), responding with 429 rate_limited and standard Retry-After / X-RateLimit-* headers. | API §13, SPEC §12 |
| FR-API-10 | MUST | The API SHALL honor Accept for content negotiation: application/json (default), application/x-ndjson (streamed lists), text/event-stream (events). | API §11 |
FR-CLI — Command-line surface
Section titled “FR-CLI — Command-line surface”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-CLI-1 | MUST | The CLI SHALL ship as a single Node binary indx and SHALL be installable via npm and bundled in the Docker image. | SPEC §1 |
| FR-CLI-2 | MUST | The CLI SHALL operate in two modes: --remote (talks to a running server, default if INDX_URL is set) and --local (operates directly against a vault directory). | SPEC §1, CLI §1 |
| FR-CLI-3 | MUST | Stdout SHALL always be valid JSON or NDJSON; stderr carries human-readable telemetry never required for correctness. The CLI SHALL emit no ANSI / no spinners by default. | CLI §1, §2.3 |
| FR-CLI-4 | MUST | The CLI SHALL implement the noun/verb surface in CLI.md §3 (note, link, tag, search, canvas, base, vault, serve, mcp, schema). | CLI §3 |
| FR-CLI-5 | MUST | The CLI SHALL expose the global flags in CLI.md §2.1, including --dry-run, --yes, --idempotency-key, --if-match, --ndjson. | CLI §2.1 |
| FR-CLI-6 | MUST | Exit codes SHALL match CLI.md §2.2 (0 success, 2 usage, 3 not found, 4 conflict, 5 unauthorized, 6 validation, 7 rate-limited, 8 upstream). | CLI §2.2 |
| FR-CLI-7 | MUST | The CLI SHALL never prompt interactively. Destructive verbs require --yes and otherwise exit 4. | CLI §1, §5 |
| FR-CLI-8 | MUST | Write verbs SHALL return the full new resource (read-after-write). | CLI §3.1 |
| FR-CLI-9 | MUST | indx schema openapi, indx schema mcp, indx schema patch-ops, indx schema events SHALL each return the live schema as JSON. | CLI §3.10 |
FR-MCP — Model Context Protocol surface
Section titled “FR-MCP — Model Context Protocol surface”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-MCP-1 | MUST | The MCP server SHALL be reachable over stdio (indx mcp serve --stdio) and over HTTP/SSE (POST /mcp on the main server). | MCP §6 |
| FR-MCP-2 | MUST | The server SHALL identify itself as { name: "indx", version, instructions } where instructions is the agent onboarding text. | MCP §2 |
| FR-MCP-3 | MUST | The tool catalog SHALL include the read, write, and admin tools listed in MCP.md §3, each with a Zod-derived JSON Schema. | MCP §3 |
| FR-MCP-4 | MUST | Tool inputs SHALL be flat JSON objects with required fields up front; tool outputs SHALL include both a structuredContent payload and a short content[].text deterministic summary. | MCP §3, §3.5 |
| FR-MCP-5 | MUST | Tool errors SHALL set isError: true and return a body of { code, message, details } matching the API error codes. | MCP §3 |
| FR-MCP-6 | MUST | Within a 60 s window, identical (tool, input) pairs SHALL be treated as replays for safe-marked tools. | MCP §3 |
| FR-MCP-7 | MUST | The server SHALL expose resources under the vault:// URI scheme (note, search, graph, tags, canvas, base) with resources/list, resources/read, and resources/subscribe. | MCP §4 |
| FR-MCP-8 | SHOULD | The server SHALL ship the prompt catalog in MCP.md §5 (vault_summary, daily_note, link_orphans, cleanup_unresolved_links, weekly_review). | MCP §5 |
| FR-MCP-9 | MUST | Every MCP tool SHALL map 1:1 to an HTTP endpoint and a CLI verb per MCP.md §7. | MCP §7 |
FR-UI — Web UI
Section titled “FR-UI — Web UI”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-UI-1 | MUST | The UI SHALL be served from / by the same Next.js process that serves /v1/* and /mcp. | SPEC §1 |
| FR-UI-2 | MUST | The UI SHALL provide: file tree, markdown editor (CodeMirror 6), search, and recent activity panel for v1. | PRD §12 |
| FR-UI-3 | MUST | The activity panel SHALL annotate each change with the Actor (who: agent etc.) drawn from the events stream. | PRD §6 (J4), SPEC §7 |
| FR-UI-4 | MUST | Every UI write SHALL go through @indx/core via the same code path as API/CLI/MCP — no UI-only side channels. | PRD §9 |
| FR-UI-5 | MUST | The UI SHALL be responsive enough to be usable on mobile-class viewports (no native mobile app in v1). | PRD §5 |
| FR-UI-6 | SHOULD | The UI SHALL provide a basic graph view, a tag explorer, and a canvas viewer in v1. | PRD §7, FR-C-4 |
| FR-UI-7 | MAY | Diff/rollback UI is (post-v1, planned for v1.1). | PRD §6 (J4), §12 |
| FR-UI-8 | MAY | Built-in chat assistant in the UI is out of scope. | PRD §5 |
FR-CONF — Configuration
Section titled “FR-CONF — Configuration”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-CONF-1 | MUST | The system SHALL accept the environment variables in SPEC.md §9.1, with documented defaults. | SPEC §9.1 |
| FR-CONF-2 | MUST | A .indx/config.json SHALL persist tokens (with scopes), markdown printer settings, embeddings enable flag, and ignore globs. | SPEC §9.2 |
| FR-CONF-3 | MUST | GET /v1/vault/config SHALL return the effective merged config (env + file). PUT /v1/vault/config SHALL patch and persist. | API §10, SPEC §9.2 |
| FR-CONF-4 | MUST | Embeddings SHALL be pluggable across vercel-ai-gateway, openai-compatible endpoints, and ollama; the product SHALL function fully without an AI key (semantic search disabled). | PRD §4, SPEC §9.1 |
| FR-CONF-5 | MUST | The system SHALL make zero outbound network calls unless a configured embeddings provider is used. | PRD §9, SPEC §12 |
FR-AI — Built-in AI runtime
Section titled “FR-AI — Built-in AI runtime”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-AI-1 | MUST | The system SHALL expose seven AI operations — summarize, ask, toc, relate, tag, metadata, extract — reachable identically from the HTTP API (/v1/ai/*), the CLI (indx ai *), and MCP (ai_* tools). | AI §1, §10 |
| FR-AI-2 | MUST | The AI runtime SHALL be opt-in: when no chat or embeddings provider is configured, AI tools SHALL NOT be advertised in /openapi.json or the MCP tool catalog and direct calls SHALL return 503 ai_unavailable. | AI §3, §9 |
| FR-AI-3 | MUST | The chat-model provider SHALL be pluggable across vercel-ai-gateway, openai-compatible endpoints, and ollama, configured via INDX_AI_PROVIDER / INDX_AI_MODEL (additive to the embeddings env vars in SPEC §9.1); when INDX_AI_PROVIDER is unset it SHALL inherit INDX_EMBEDDINGS_PROVIDER. | AI §3.2, SPEC §9.1 |
| FR-AI-4 | MUST | AI operations SHALL accept a structured scope selector (paths | glob | tag | query | note+include_linked); resolved scopes SHALL be intersected with INDX_AI_ALLOW_GLOBS / INDX_AI_DENY_GLOBS and excluded items SHALL be reported via the globs_excluded warning. | AI §4 |
| FR-AI-5 | MUST | ai_summarize SHALL return { summary, citations[], usage, warnings } with map-reduce summarization for scopes that exceed the prompt budget. | AI §5.1 |
| FR-AI-6 | MUST | ai_ask SHALL retrieve via the existing search infra (defaulting to hybrid when embeddings are configured, else lexical with embeddings_unavailable warning) and SHALL answer only from retrieved evidence; un-grounded answers SHALL be returned with confidence: "low" and unsupported_question warning. | AI §5.2 |
| FR-AI-7 | MUST | ai_toc SHALL support mode: "note" (single-note TOC) and mode: "moc" (folder/glob/tag MOC). With include_descriptions: false, mode: "note" SHALL be deterministic and SHALL NOT call the chat model. | AI §5.3 |
| FR-AI-8 | MUST | ai_toc materialization (write: { path }) SHALL go through core.vault.writeNote honoring if_match, if_not_exists, and the standard atomic-write pipeline; the response SHALL include written: { path, etag } (read-after-write). | AI §5.3, FR-E-1, FR-E-8 |
| FR-AI-9 | MUST | ai_relate SHALL discover candidate neighbors via the index (lexical / semantic / hybrid), optionally classify the relation via the chat model, and (with propose_links: true) return draft PatchOp[] per SPEC §6.2 without applying them. | AI §5.4, SPEC §6.2 |
| FR-AI-10 | MUST | Every AI output that references vault content SHALL include a citations[] array of { path, etag, anchor?, line?, span?, snippet? }; the runtime SHALL verify each citation post-hoc, dropping invalid ones with citation_invalid and surfacing etag drift as citation_drift. | AI §6 |
| FR-AI-11 | MUST | When cite: true (default) and zero citations survive verification, the operation SHALL fail with 422 ai_grounding_failed. | AI §6, §9 |
| FR-AI-12 | MUST | AI ops SHALL stream when negotiated: SSE on the API (Accept: text/event-stream), NDJSON on the CLI (--stream), notifications/progress on MCP. Providers without streaming SHALL fall back to a single response with the stream_unsupported warning. | AI §7 |
| FR-AI-13 | MUST | AI results SHALL be cached in .indx/ai-cache.db keyed by (op, provider, model, temperature, seed, max_output_tokens, json_schema, normalized_input, content_fingerprint). Cache hits SHALL return usage zeros and an ai_cache_hit warning. | AI §8.1 |
| FR-AI-14 | MUST | The standard Idempotency-Key SHALL apply to AI ops (24 h cache); same key + different body SHALL return 409 idempotency_key_reused. | AI §8.2, FR-E-7 |
| FR-AI-15 | SHOULD | When INDX_AI_DAILY_COST_USD is set, total spend per UTC day across tokens SHALL be capped; over-cap SHALL return 429 ai_quota_exceeded with Retry-After set to seconds until midnight UTC. | AI §8.3 |
| FR-AI-16 | MUST | AI errors SHALL include the codes ai_unavailable (503), ai_provider_error (502), ai_grounding_failed (422), ai_input_too_large (422), ai_quota_exceeded (429), in addition to the existing error envelope. | AI §9, API §4.1 |
| FR-AI-17 | MUST | The MCP tool catalog SHALL filter ai_* tools by configured provider and by token scope; AI tools requiring writes (ai_toc with write) SHALL NOT be advertised to a vault:read-only session. | AI §10.3, FR-A-4 |
| FR-AI-18 | MUST | Every AI invocation SHALL emit an ai.invocation event (op, actor, scope_paths, provider, model, usage, cache_hit, duration_ms, ok, error_code) on the standard event bus and into .indx/events.log; the event payload SHALL NOT include prompts or model outputs. | AI §12, FR-W-2, FR-W-4 |
| FR-AI-19 | MUST | GET /v1/ai/status (and equivalents) SHALL return { enabled, provider, model, embeddings, cache, spend_today_usd } without ever calling the upstream provider. | AI §10 |
| FR-AI-20 | MUST | The MCP server SHALL expose two read-only resource patterns when AI is enabled — ai://summary/<scope> and ai://moc/<scope> — that re-derive on subscribed scope changes via the existing event bus. | AI §10.3, FR-W-6 |
| FR-AI-21 | MUST | ai_tag SHALL suggest tags for a scope, biased to the existing vault vocabulary (vocabulary.use_existing defaults true, drawing from tag_list), with optional candidates/forbid/namespace constraints, max_per_note and min_confidence filters, and per-suggestion is_new flags. Returned tags SHALL be normalized and validated against SPEC §4.5; invalid candidates SHALL be dropped with tag_invalid warning. | AI §5.5 |
| FR-AI-22 | MUST | ai_tag with apply: true SHALL write through core.notes.patch per path (atomic per note), under apply_mode: "merge" (default — dedup-union with existing tags) or "replace"; inline #tag mentions in note bodies SHALL NOT be modified. | AI §5.5, §5.8, FR-E-4 |
| FR-AI-23 | MUST | ai_metadata SHALL accept a typed fields[] schema (string | number | boolean | date | datetime | list | enum, plus pattern, min, max, enum, required, default) and constrain model output via JSON Schema; the runtime SHALL re-validate post-generation, dropping invalid values with metadata_invalid and reporting un-extractable required fields with metadata_missing. | AI §5.6 |
| FR-AI-24 | MUST | ai_metadata apply modes (set_missing (default) | overwrite | merge) SHALL behave per AI §5.6; writes for tags/aliases/cssclasses SHALL preserve SPEC §4.1 Obsidian-conformant list shape regardless of declared type. | AI §5.6, FR-F-3 |
| FR-AI-25 | MUST | ai_extract SHALL accept a caller-supplied JSON Schema (2020-12 subset) plus optional schema_id, generate matching data, and re-validate before returning; an invalid input schema SHALL fail with 422 ai_schema_invalid; per-record validation failures SHALL drop with extract_invalid. | AI §5.7 |
| FR-AI-26 | MUST | ai_extract with destination: "frontmatter" SHALL require both destination_key and apply: true to write; without these the operation is read-only and returns drafts via proposed_patches. | AI §5.7 |
| FR-AI-27 | MUST | Multi-note apply ops (ai_tag, ai_metadata, ai_extract) SHALL: (a) write each note atomically via core.notes.patch, (b) honor per-path if_match, (c) order writes by sorted path, (d) report each path’s outcome in applied[] and failures in warnings[], (e) emit standard note.updated events plus one roll-up ai.invocation event listing applied_paths. Mid-op failure SHALL NOT roll back already-written notes. | AI §5.8, FR-E-1, FR-E-2, FR-W-2 |
| FR-AI-28 | MUST | Per-path if_match mismatches under apply: true SHALL surface as 409 ai_apply_conflict with details.failed[{ path, expected, actual }]. | AI §5.8, §9 |
| FR-AI-29 | MUST | Apply ops SHALL be content-idempotent: repeating an apply request after a successful run SHALL be a no-op (per apply_mode semantics) and SHALL surface apply_skipped_no_change per affected path. Combined with Idempotency-Key, repeats within 24 h SHALL return the cached response. | AI §5.8, FR-E-7 |
| FR-AI-30 | MUST | Apply ops SHALL include the new etag of each written note in applied[] (read-after-write), so the agent does not need a follow-up note_read. | AI §5.5–§5.7, FR-E-8 |
FR-OBS — Observability
Section titled “FR-OBS — Observability”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-OBS-1 | MUST | The system SHALL emit structured JSON logs to stdout via pino. Log level SHALL be controlled by INDX_LOG_LEVEL. | SPEC §11 |
| FR-OBS-2 | MUST | GET /v1/health SHALL return { status, vault, indexed_notes, last_reindex_at, uptime_s }. | SPEC §11, API §5 |
| FR-OBS-3 | MUST | GET /v1/vault/status SHALL return counts, last reindex, and embeddings status. | API §10 |
| FR-OBS-4 | SHOULD | The system SHALL support exporting OpenTelemetry traces via OTEL_EXPORTER_OTLP_ENDPOINT (off by default). | SPEC §11 |
| FR-OBS-5 | MUST | An audit log of vault events SHALL be written to .indx/events.log (NDJSON, rotated at 10 MB). | SPEC §7 |
FR-PKG — Schema-first contract
Section titled “FR-PKG — Schema-first contract”| ID | Priority | Requirement | Trace |
|---|---|---|---|
| FR-PKG-1 | MUST | A single Zod tree in @indx/shared SHALL be the source of truth; TypeScript types, JSON Schema (MCP), and OpenAPI 3.1 (HTTP) SHALL be derived. | PRD §9, SPEC §14 |
| FR-PKG-2 | MUST | Adding a new capability SHALL be one PR that (1) adds the Zod schema, (2) implements the operation in @indx/core, (3) wires it into the API route, the CLI verb, and the MCP tool via thin adapters. | SPEC §14 |
| FR-PKG-3 | MUST | All public schemas SHALL be discoverable at runtime via /openapi.json and indx schema * so a new agent can be productive in one round trip. | API §14, MCP §1 |
Traceability summary
Section titled “Traceability summary”| Source section | FR coverage |
|---|---|
| PRD §3 (Users) | informs personas in EPICS.md |
| PRD §4 (Goals) | FR-V-2, FR-V-3, FR-PKG-1, FR-CONF-4, FR-CONF-5 |
| PRD §5 (Non-goals) | excluded from FR; documented as MAY (post-v1) where relevant |
| PRD §6 (Journeys) | mapped 1-to-1 to epics in EPICS.md |
| PRD §7 (Functional scope) | FR-N, FR-S, FR-L, FR-T, FR-C, FR-B, FR-W, FR-A, FR-V-8 |
| PRD §8 (Quality bar) | covered in NFR.md |
| PRD §9 (Constraints) | FR-V-2, FR-E-1, FR-E-3, FR-E-7, FR-CONF-5, FR-PKG-1 |
| PRD §10 (Risks) | mitigations in FR-F-2, FR-F-11, FR-W-1, FR-W-5, FR-A-2 |
| PRD §11 (Open Q1–Q4) | FR-V-6, FR-CONF-4, FR-CLI-2, FR-F-2 |
| SPEC §1–§14 | mapped per row above |
| API §1–§15 | mapped per row above |
| CLI §1–§6 | mapped per row above |
| MCP §1–§9 | mapped per row above |
| AI §1–§16 | FR-AI-1 … FR-AI-30 |