Map A17 onto Outline's actual primitives, and record the MCP decision
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 58s

Observed rather than assumed, after checking the running instance:
collections carry per-user permissions in three tiers and permissions
are collection-scoped, not per-document. So one collection per writing
principal, with read membership for everyone else.

- States precisely what that enforces: no other agent can write a
  given agent's world model. It does not enforce operator-versus-agent
  exclusion, since the operator holds manager on their own wiki; that
  half is procedural, via the break-glass routine that stops the
  agents first. Two different strengths, so the spec no longer implies
  one guarantee covers both
- Per-user grants, not groups: Outline groups are not federated with
  LLDAP, so using them would put membership somewhere nothing syncs.
  Three principals make the manual mapping trivial
- Outline API keys are per-user, so each agent's token must belong to
  that agent's own Outline user. A token minted from an admin account
  voids every boundary above while appearing to work — the same shape
  as the Gitea permission surprise
- Collections identified by ID from config, documents by ID recorded
  in the state store, which also makes writes idempotent across a
  retry
- The conditional-write requirement softens to a revision *read*:
  break-glass stops the agents, so compare-and-swap is unnecessary and
  only divergence detection is needed for A3

Also records Outline's MCP server as considered and deferred. A17's
enforcement depends on this library sitting between the model and the
store; MCP's purpose is direct model access, and a procedure the model
may decline to fetch does not constrain it. Still a candidate for the
discretionary search tool, with two things to verify first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dongarandClaude Opus 5 committed 2026-09-08 19:36:39 +00:00
1 parent 9362a134c9
commit 2352a84004
1 file changed
+50
+50
View File
@@ -622,6 +622,56 @@ rests on.
**Deferred:** the operator-writable procedures collection. The interface is defined **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). 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) ## PART B — THE DEV AGENT / `nisse-dev` (build first)