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.

Filesystemuser tree · ordinary tools
→
Observerwatcher · reconciler · fingerprints
→
Extractorsbounded parsers · provenance
↓
CatalogSQLite WAL · FTS5 · graph
→
Domain servicespolicy · tasks · audit · events
↓ interface parity
HTML / HTMX
REST /api/v1
GraphQL
MCP
CLI / SDK

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.

DomainOwnsRepresentative operations
workspaceregistry, lifecycle, scope inheritanceregister, inspect, archive
filesystempaths, fingerprints, moves, exclusionsinventory, reconcile, resolve move
catalogobjects, versions, aliases, freshnessget, list, compare version
extractbounded parsers and provenanceextract, reindex, explain
searchFTS5 ranking, facets, permission filterquery, suggest, save search
graphtyped edges and evidenceneighbors, traverse, evidence
contextbundle assembly and budgetsbundle, estimate tokens
policyroles, scopes, deny-by-default evaluationexplain, evaluate, grant
task / workorderdurable jobs and approvalssubmit, approve, cancel, watch
audit / eventsledger, receipts, subscriptionsrecord, verify, stream
backup / exportsnapshots, restore, portabilitycreate, 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.

#InvariantWhat it forbids
01Non-destructive first contactModifying any file during attach or indexing.
02Local operationRequiring a network, account, or hosted model for core use.
03Path-independent IDsUsing absolute paths as primary identity.
04Separate derived stateTreating indexes, embeddings, or generated pages as source.
05No silent elevationAuthority inferred from a model, document, or tool description.
06Accountable mutationsWrites without idempotency, preconditions, or receipts.
07Provenance-aware intelligencePresenting inference or confidence as observed fact.
08Interface equivalenceAn adapter that bypasses policy or re-implements a domain rule.
09ExportabilityMetadata that cannot leave with documented schemas.
10Graceful degradationExtractor, 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.

LayerChoiceWhy
Daemon & CLIGo, standard library net/http, context, database/sqlSingle static binary, predictable concurrency, low runtime surface.
Renderinghtml/template + HTMXMeaningful server-rendered pages; JavaScript only where the browser must act.
CatalogSQLite (WAL, foreign keys) + FTS5One file, transactional, portable, already sufficient for graph and search.
Observationfsnotify + periodic reconciliationEvents as hints; inventory as truth.
VectorsOptional sqlite-vec equivalentReplaceable retrieval enhancement; never required for core features.
TelemetryOpenTelemetry, privacy-preserving by defaultOperational visibility without shipping workspace content.
TestingGo tests, Playwright or chromedp for browser surfacesSafety properties proven, not asserted.
Not the product: a mandatory cloud drive, a replacement filesystem, an opaque autonomous agent, a universal ERP, a speculative graph that can overwrite user documents, or a distributed database in disguise.

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.

jsonGET /health
{
  "status": "degraded",
  "catalog": { "state": "ok", "wal_bytes": 812344 },
  "watcher": { "state": "polling", "reason": "event_limit" },
  "extract": { "queued": 42, "failed": 3 },
  "freshness_floor_seconds": 30
}