Merge pull request 'Map A17 onto Outline's actual primitives, and record the MCP decision' (#23) from a17-outline-mapping into main
CI / checks (push) Successful in 1m2s

Reviewed-on: dongar/nisse#23
Reviewed-by: Don Garrett <dgarrett@acm.org>
This commit was merged in pull request #23.
This commit is contained in:
dongar committed 2026-09-08 19:38:29 +00:00
commit dd22eda528
1 file changed
+50
+50
View File
@@ -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)