Files
shard-wiki/docs/access-model-blueprint.md
tegwick a00a457f44 Establish clean starting point: workplan, access model, INTENT amendments
Add SHARD-WP-0001 workplan backed by the StateHub yawex-requirements
workstream (6 design tasks, all todo). Includes access-model blueprint,
NetKingdom integration requirements draft, and INTENT amendments for
open-by-default authorization delegated to external identity providers.
2026-06-08 12:23:54 +02:00

8.9 KiB

Architecture blueprint — shard-wiki access & history model

Status: draft for review · Date: 2026-06-08 · Resolves pending decision "Scope of a minimal access model in shard-wiki core".

This blueprint settles how shard-wiki handles access control and change history. It realizes the INTENT principles Open by default, progressively governed, History as the safety net, and Authorization in core, authentication delegated.


1. The core idea: one core, a ladder of modes

shard-wiki ships one authorization core. What changes between deployments is how much of it is switched on. No re-architecture is needed to climb the ladder — only configuration and the presence (or absence) of an external identity provider.

This mirrors the NetKingdom capability-progression ladder ("capability-driven, not scale-driven"): start lightweight, expand when the need appears.

Mode Identity provider Who can read/write External deps Analogue
L0 — Open (c2) none (everyone is anonymous) everyone reads + writes none Ward Cunningham's original c2 wiki
L1 — Attributed local/optional open writes, but edits are attributed when an identity is presented none classic "sign your edits" wiki
L2 — Authenticated user-engine (OIDC via net-kingdom lightweight) authenticated principals; simple read-all / write-authenticated user-engine + net-kingdom (OIDC/PKCE) small team wiki
L3 — Role/Group user-engine memberships per-shard/per-namespace roles (reader/author/maintainer) user-engine groups departmental wiki
L4 — Multi-tenant enterprise user-engine + net-kingdom expanded (Keycloak/SAML) per-tenant isolation, per-page ACL, SSO/MFA, audit full IAM stack enterprise-grade

Invariant: L0 must always be reachable with zero external dependencies. Access control is additive; removing the identity provider degrades gracefully back down the ladder, it never bricks the wiki.


2. Why history is the floor, not access control

In L0 the wiki trusts every writer. The protection against accidental loss, vandalism, or mistakes is therefore not gatekeeping but complete, recoverable history:

  • Every write is a Git commit on the information space's coordination layer (per INTENT's Git-addressable coordination principle).
  • Any revision of any page is restorable; deletion is a commit, never destruction.
  • History exists identically in all modes — L4's access control hardens the wiki but the recoverability guarantee is the same one that makes L0 safe to run open.

This is the inversion of yawex, which gated with htpasswd and kept only a single page~ backup. We keep the openness, replace the gate with history, and make the gate optional.


3. Component model

                 ┌─────────────────────────────────────────────┐
                 │                 shard-wiki                   │
   request  ───► │  ┌────────────┐   ┌──────────────────────┐   │
   (principal?)  │  │  Policy     │   │  Authorization core  │   │
                 │  │  Enforcement├──►│  (PDP): capability   │   │
                 │  │  Point (PEP)│   │  decisions per       │   │
                 │  └─────┬───────┘   │  page / shard / tenant│  │
                 │        │            └──────────┬───────────┘  │
                 │        ▼                       │              │
                 │  ┌────────────┐         ┌──────▼───────────┐  │
                 │  │ Shard       │         │ IdentityProvider │  │
                 │  │ adapters    │         │   (interface)    │  │
                 │  │ (+capability│         └──────┬───────────┘  │
                 │  │  profile)   │                │              │
                 │  └─────┬───────┘                │              │
                 │        ▼                         │             │
                 │  Git-backed coordination journal │             │
                 └──────────────────────────────────┼─────────────┘
                                                     │ (L2+ only)
                            ┌────────────────────────▼───────────────────┐
                            │  user-engine  (accounts, memberships,       │
                            │  profiles, audit, events)                   │
                            │        backed by net-kingdom IAM            │
                            │  (OIDC/PKCE lightweight · Keycloak/SAML exp) │
                            └─────────────────────────────────────────────┘

Owned by shard-wiki core:

  • Principal — a resolved actor: anonymous, or an identity token + claims from the provider. Core never stores credentials.
  • IdentityProvider interface — a thin pluggable contract. The null provider (everyone = anonymous) is the L0 default and ships in-core.
  • Authorization core (PDP) — pure capability decisions: given (principal, action, target page/shard/tenant), return allow/deny. Actions: read, write, patch, merge, administer. Layered on each adapter's capability profile (a shard that can't write can't be written regardless of policy).
  • Policy Enforcement Point (PEP) — wraps every adapter operation; calls the PDP.
  • Tenant boundary — a root entity is the unit of multi-tenancy. Shards attach to a root; an L4 tenant maps to a root entity (or a set of them).
  • Change history — Git commits on the coordination journal; the recovery substrate.

Delegated to the identity provider (L2+):

  • Authentication (who you are) — net-kingdom IAM (OIDC/PKCE → Keycloak/SAML).
  • Identity lifecycle, user directory, credentials, secrets — net-kingdom / user-engine.
  • Memberships, groups, profiles, org structure, audit sink — user-engine.

4. Request flow (L4, fully governed)

  1. Caller presents an OIDC token (issued by net-kingdom IAM) to shard-wiki.
  2. The configured IdentityProvider validates the token and resolves a Principal (subject + tenant + group/role claims), enriching via user-engine memberships.
  3. The PEP intercepts the requested action on a target page/shard.
  4. The PDP decides using: tenant isolation → shard role bindings → optional per-page ACL → adapter capability profile.
  5. On allow, the adapter performs the op; the write lands as a Git commit and an audit event is emitted to user-engine.
  6. On deny, the op is refused; nothing mutates.

The same code path at L0: step 1 is skipped, the null provider returns anonymous, the PDP's open policy allows read+write, the write still lands as a Git commit. History is identical; only the gate differs.


5. Design rules

  • Fail open only at L0, fail closed at L2+. The mode is explicit configuration, never inferred from whether the provider happens to be reachable (a flaky IAM must not silently open an enterprise wiki). If an L2+ deployment loses its provider, it denies, it does not fall back to open.
  • Authorization is pure and offline-capable. Once a Principal is resolved, decisions need no network call — role/ACL data is carried on the Principal or cached, so a federated read of a projected page doesn't require a live IAM round-trip per page.
  • Per-page ACL is opt-in (L4). Default scoping is per-shard / per-namespace (L3). Per-page ACL is supported but off by default to avoid yawex's per-directory AccessControl sprawl.
  • Provenance carries authorization context. A federated page records not just its source shard and freshness but the authorization context under which it was read, so the union view never leaks content a principal couldn't see at source.

6. Open questions (carry into requirements)

  1. Token format/claims contract with net-kingdom (OIDC scopes, tenant claim name).
  2. user-engine membership/role query shape and caching/TTL.
  3. Audit event schema shard-wiki emits to user-engine.
  4. How tenant ↔ root-entity mapping is configured and discovered.
  5. Whether L1 "attributed but open" is worth shipping or we jump L0 → L2.

See requirements-user-engine-netkingdom.md for the concrete asks these imply.