2026-09-23·by Sijie Wang#standmeet#architecture#design

confusables

Confusables — terms that look alike and aren't

Parent: architecture

The vocabulary collisions in this system, each split precisely. When a doc says one of these words unqualified, check here.

1. The two MCP planes

termour rolewhat it isauth
capability planeMCP hostour agent's tools: mcp-servers/ stdio children + owner-attached ext-mcp, via mcpclient/capregnone — process-local (capsocket)
service handle (as-MCP)MCP serverthe outward /mcp/* facade (as-mcp-facade): the owner's own AI pushes corpus (the thesis loop), other people's agents readSigv1

Same protocol, opposite directions: on the service handle the arrow points into us (we serve); on the capability plane the arrow starts from our agent (we host and consume).

Rule: never say bare "MCP" in engineering docs — say capability plane or service handle. (Third-party MCP servers that design docs mention in passing — e.g. job-loop's Playwright MCP, which the owner's Claude consumes — live entirely outside our box and are neither plane.)

2. The two plugin axes (and connectors are NOT MCP)

  • MCP capability plugins (mcp-capability-plugins) — tools for our agent; speak the MCP wire protocol; manifest + Origin trust tiers.
  • Connector plugins (connector-plugins) — credentialed external integrations (calendar/mail/…); speak contracts (CalendarProxy), NOT MCP; consumers get a call handle, never creds. A capability may Require a connector (connector-deps) — that dependency edge is the only place the two axes touch.

3. Connector: action mode vs sync mode

Same Hub, two directions: action = proxy calls out at session time (booking, mail); sync = ingest data in at curation time (the destiny of Obsidian vault sync). An "installed connector" tells you nothing about which until you look at the mode.

4. ext-mcp vs mcp-servers/

Both live on the capability plane; they differ only in Origin: mcp-servers/ are builtin (ship with the repo, loaded at boot), ext-mcp are owner-registered third-party servers. Same protocol, different trust tier (mcp-capability-plugins).

5. Skills vs capabilities

A capability is a tool the agent can call. A skill is an authored artifact (managed like prompts: library/CRUD/attach-to-role), rendered to SKILL.md, exposed through 3-level progressive disclosure (skills-progressive-disclosure) — it rides on capabilities (skill_use, skill_run_script), it isn't one.

6. The writings estate vs the corpus three tiers

Two separate data estates that both look like "the content" — since 2026-07-09 both live in the one corpus_notes table, told apart by the genre column (backend/db/schema.sql):

  • corpus = genre raw → wiki → output (+ subjectivity) + the note_refs edge table (renamed from wiki_refs) (three-tier-corpus-promotion);
  • writings = genre='writing' + writing_refs — standalone long-form posts, its own ref rebuild. The vault sync (writings-import-export) used to touch writings only; since SyncVault it routes every top-level folder to a genre — wiki / subjectivity / raw / writing (backend/internal/corpus/obsidian/sync.go:2, sync_classify.go:64); "wiki" in this wiki's name and "wiki" the corpus tier are also not the same thing.

7. Setup token vs owner keypair

Setup token = first-run instance claim (boot-printed, sha256 into instance_settings, consumed once). Keypair = ongoing Sigv1 auth for the service handle, created later via the admin API. Bootstrap vs operation — unrelated lifecycles (owner-keypair-auth).

8. Access code = invitation (deliberately ONE thing)

Not a confusable to split but one to collapse: the job-loop design unified them — one access_codes table, no parallel "Invitation" concept. If you see "invitation" anywhere, it IS an access code (acl-and-quota-granularity).

9. Frozen vs live (the ACL timing split)

Global capability settings are live (flip → running sessions die). Role and code layers are frozen at issue into the RoleSnapshot. "The owner changed X, why does the visitor still see the old rules?" — because that layer freezes; "why did the session die instantly?" — because that one doesn't (role-snapshot-frozen, acl-and-quota-granularity).

10. corpus.search vs corpus_search — a dot and an underscore, two backends

corpus.search (dot) is the owner/admin op behind /api/admin/corpus/{genre}/search. It calls repo.Search → Postgres SearchNotes, and never touches Meili. corpus_search (underscore) is the visitor agent's tool; it goes through the corpus lister, which uses Meili when configured and falls back to Postgres FTS when not.

This cost real time: attaching the lexical index to production and then verifying on the admin surface produced results byte-identical to before, which reads exactly like "the change did nothing". Measuring a retrieval change on one of these tells you nothing about the other (corpus-retrieval).

11. Authoritative vs partial import — the same endpoint, opposite deletion semantics

POST /api/admin/obsidian/import has a SyncMode. Its zero value is partial: upsert only, nothing pruned. Authoritative means this upload IS the whole vault, so anything vault-imported and absent from the batch is deleted — which is what the owner's directory-picker sends.

The flag is a plain form value, so it is dropped by omission rather than by error, and a full-vault sync then silently degrades into add-only. The reverse mistake is worse: a nine-file partial upload sent as authoritative would prune the other thousand.

12. publish (vault) vs published (corpus) vs show_as_source (retrieval)

Three near-synonyms on three different layers. publish: true is frontmatter the owner writes in their vault. published is the corpus column it maps to, and it gates the public reader pages only. Retrieval does not consult it — an entry can be retrievable to a coded visitor while being invisible on the public web, which is the whole point of codes. show_as_source is separate again: whether a note may be cited in an answer.

"Is this note public?" is therefore three questions, and the vault's own frontmatter schema knew about none of them for months — publish was not in its allow-list, so every commit adding it was blocked.

confusables