diff --git a/docs/Architecture-Build-Spec.md b/docs/Architecture-Build-Spec.md index 54e8b71..9c43c9a 100644 --- a/docs/Architecture-Build-Spec.md +++ b/docs/Architecture-Build-Spec.md @@ -622,6 +622,56 @@ rests on. **Deferred:** the operator-writable procedures collection. The interface is defined here; the content and the C2 binding list arrive with the policy taxonomy (Part H). +**Concrete mapping onto Outline.** Observed in the running instance: a **collection** +carries per-user permissions with three tiers — visible-to (read), read-write, and +manager. Permissions are collection-scoped, not per-document. +- Therefore **one collection per writing principal**, with read membership granted to + everyone else who needs it: `nisse-ops-world` (write: `nisse-ops`), + `nisse-dev-world` (write: `nisse-dev`), `procedures` (write: operator). +- **What this enforces, precisely:** no *other agent* can write a given agent's world + model — the collision that would otherwise be silent and unattributable. It does not + enforce operator-versus-agent exclusion, because the operator holds manager on their + own wiki and can always grant themselves write. That half is procedural: the + break-glass routine stops the agents first, so concurrent writers do not exist while + editing. +- **Assign per user, not per group.** Outline groups appear to be internal to Outline + rather than federated with LLDAP (A8), so using them would put membership in a second + place that nothing syncs. With three principals the manual mapping is trivial and + obvious; revisit only if the cast grows. +- **Each agent's API token must belong to that agent's own Outline user.** Outline API + keys are per-user, so the token *is* the identity every permission above evaluates + against. A token minted from the operator's account — or an admin's — silently voids + the entire boundary while appearing to work. +- **Identify collections and documents by ID, from config and state.** Collection IDs + come from env (`NISSE_OUTLINE_*_COLLECTION`), never discovered from "whatever the + token can write": a permission change must not silently redefine what an agent + considers its own. Document IDs are recorded in the state store (A2) when created and + used for updates, which also makes writing idempotent — a retry after a crash updates + the document instead of creating a second one. +- **Revision reads, not conditional writes.** Because break-glass stops the agents, + compare-and-swap is unnecessary; what is needed is the ability to *observe* that a + document changed since the agent last read it, feeding the A3 divergence check. + Whether Outline exposes a revision identifier or only a modified timestamp is + unverified — either satisfies this. + +#### Considered and deferred: Outline's MCP server + +Outline can expose search, read, create and edit over MCP (remote, Streamable HTTP, +enabled workspace-wide). **Not adopted for the retrieval path**, because A17's +enforcement depends on this library sitting *between* the model and the store: staleness +marking, snapshotting into the canonical log, binding procedures to action classes, and +gating a failed confirm-first read all happen to a retrieval before the model sees it. +MCP's purpose is the opposite — direct model access — and a procedure the model may +decline to fetch does not constrain it (A12). + +It remains a reasonable candidate for the *discretionary* search tool later, and unlike +the vendor agent SDKs A13 rejects, MCP is a neutral standard rather than lock-in: one +client would serve many servers if others are ever added. Before adopting it, verify +that it honours the key owner's **collection** permissions rather than mere workspace +membership, and that the workspace-level toggle does not force broader exposure than +intended. + + --- ## PART B — THE DEV AGENT / `nisse-dev` (build first)