Architecture
Files own their content. Everything else is a projection.
Intralock does not replace the filesystem or hide it behind a database. It observes ordinary files, derives a catalog, and exposes governed operations over that catalog. Delete the derived state and nothing of the user's is lost.
01 — System context
One daemon, one catalog, many adapters.
A single-host installation runs intralockd, one SQLite catalog per profile, extractor workers, a change observer, and the HTTP server. The daemon owns catalog transactions and per-workspace locks; nothing else writes directly.
Domain services never import routing types, templates, or model SDKs. They consume a typed request context, actor, workspace scope, and validated input.
02 — Domains
Stable responsibilities, explicit contracts.
Each domain package exports commands, queries, errors, schemas, and events. A capability is a declared operation with a risk class, scope requirement, and result contract — not a web endpoint that happens to work.
| Domain | Owns | Representative operations |
|---|---|---|
| workspace | registry, lifecycle, scope inheritance | register, inspect, archive |
| filesystem | paths, fingerprints, moves, exclusions | inventory, reconcile, resolve move |
| catalog | objects, versions, aliases, freshness | get, list, compare version |
| extract | bounded parsers and provenance | extract, reindex, explain |
| search | FTS5 ranking, facets, permission filter | query, suggest, save search |
| graph | typed edges and evidence | neighbors, traverse, evidence |
| context | bundle assembly and budgets | bundle, estimate tokens |
| policy | roles, scopes, deny-by-default evaluation | explain, evaluate, grant |
| task / workorder | durable jobs and approvals | submit, approve, cancel, watch |
| audit / events | ledger, receipts, subscriptions | record, verify, stream |
| backup / export | snapshots, restore, portability | create, verify, restore |
03 — Consistency
A file change commits on disk, not in a vector store.
The catalog is an asynchronously reconciled projection. It can lag; it must never pretend otherwise. Every response carries enough freshness metadata for a client to decide what to trust.
The watcher treats filesystem notifications as hints, not truth. Events are debounced and coalesced per workspace; a periodic inventory scan closes missed-event gaps; journal continuity is proven or the catalog is rebuilt.
Extraction may run concurrently, but catalog mutations are short, bounded transactions. Writes use same-filesystem temporary files, atomic replacement, and expected-version preconditions. A high-risk write rechecks the source before applying.
Optional sync, if ever enabled, is an outbox/inbox replication layer. A remote copy has no right to overwrite a locally edited file; conflicts become review items, and sensitive files stay excluded even when a replica asks.
Freshness fields
- observed_at
- When the filesystem was actually seen.
- indexed_at
- When the catalog committed the observation.
- content_mtime
- Source modification time.
- content_hash
- Digest of the bytes that were read.
- watcher_sequence
- Monotonic event position per workspace.
- catalog_version
- Schema and write generation.
04 — Invariants
Ten constraints, each with a negative test.
Every architecture decision names the invariant it strengthens, its tradeoff, its owner, and the test proving it does not break one. An exception requires a written decision record, a user-facing warning, a migration plan, and a sunset.
| # | Invariant | What it forbids |
|---|---|---|
| 01 | Non-destructive first contact | Modifying any file during attach or indexing. |
| 02 | Local operation | Requiring a network, account, or hosted model for core use. |
| 03 | Path-independent IDs | Using absolute paths as primary identity. |
| 04 | Separate derived state | Treating indexes, embeddings, or generated pages as source. |
| 05 | No silent elevation | Authority inferred from a model, document, or tool description. |
| 06 | Accountable mutations | Writes without idempotency, preconditions, or receipts. |
| 07 | Provenance-aware intelligence | Presenting inference or confidence as observed fact. |
| 08 | Interface equivalence | An adapter that bypasses policy or re-implements a domain rule. |
| 09 | Exportability | Metadata that cannot leave with documented schemas. |
| 10 | Graceful degradation | Extractor, model, or browser failure collapsing core navigation. |
05 — Stack
Boring where it matters.
The reference implementation deliberately chooses a small set of mature components. No Node.js in the production runtime; no mandatory vector database; no distributed system on day one.
| Layer | Choice | Why |
|---|---|---|
| Daemon & CLI | Go, standard library net/http, context, database/sql | Single static binary, predictable concurrency, low runtime surface. |
| Rendering | html/template + HTMX | Meaningful server-rendered pages; JavaScript only where the browser must act. |
| Catalog | SQLite (WAL, foreign keys) + FTS5 | One file, transactional, portable, already sufficient for graph and search. |
| Observation | fsnotify + periodic reconciliation | Events as hints; inventory as truth. |
| Vectors | Optional sqlite-vec equivalent | Replaceable retrieval enhancement; never required for core features. |
| Telemetry | OpenTelemetry, privacy-preserving by default | Operational visibility without shipping workspace content. |
| Testing | Go tests, Playwright or chromedp for browser surfaces | Safety properties proven, not asserted. |
06 — Failure
Degrade in the open.
A failed extractor marks the object unreviewed rather than hiding it. A missing vector index falls back to lexical search. A stopped daemon leaves static pages and files readable. A stale catalog says so in the response. The user never loses navigation because an enhancement failed.
Health is a first-class surface: intralock doctor --full reports queue backlogs, event-coverage gaps, schema version, and disk reserve — the same signals the UI shows.
{
"status": "degraded",
"catalog": { "state": "ok", "wal_bytes": 812344 },
"watcher": { "state": "polling", "reason": "event_limit" },
"extract": { "queued": 42, "failed": 3 },
"freshness_floor_seconds": 30
}