ditdash U-K1/K2/K3: federation discovery, link custody, remote rendering #9

Merged
jsutter merged 4 commits from ditdash-u-k into main 2026-10-12 06:25:55 +00:00
Owner

K1: the discovery document + guarded fetch (private-range refusals). K2 (sensitive): the sealed link-state cookie, rotation and revocation, storage-scope zero-remote-bytes, the WS-0 pulled-blocks convention. K3: remote rendering + JWT validation (the DIT-SES-004 zone rule) + the frozen degradation vocabulary (critic-fixed: unreachable lines render only for the unreachable state, mutation-proven). Critic: PASS-WITH-FINDINGS, fixed.

K1: the discovery document + guarded fetch (private-range refusals). K2 (sensitive): the sealed link-state cookie, rotation and revocation, storage-scope zero-remote-bytes, the WS-0 pulled-blocks convention. K3: remote rendering + JWT validation (the DIT-SES-004 zone rule) + the frozen degradation vocabulary (critic-fixed: unreachable lines render only for the unreachable state, mutation-proven). Critic: PASS-WITH-FINDINGS, fixed.
Every instance serves its discovery document at the RFC 8615 well-known
path, pre-auth beside /healthz: schema 1, slug/name/logo/accent, the
host-derived api_base, the master issuer, the public federation client
id, and the served API versions. Never a secret, never a fabricated
issuer (absent resolves null plus a named gap).

The linkage fetch obeys the frozen guard contract: https only; every
resolved address checked against private/loopback/link-local/multicast/
unspecified/CGNAT/reserved families (the dial uses the checked set —
one resolution, no rebinding window); a redirect cap of 2 with each hop
re-validated; a 256 KiB response cap; an 8 s deadline. Refusals carry
cause+fix and render identically on the page and the twin.

The home page's operator-only link section carries the address form, the
config document's known-partner quick links, and the report panel
(name, issuer domain, logo, accent, API versions) — the report renders
before any sign-in affordance (FR-33's ordering; the U-K2 flow panel
appends after it).

ResolvedMsp gains issuer + federation_client_id, env-first over the
embedded copy (the dynamic-nix deploy drops dit.env, so config/msp.json
is the shipping channel); a set-but-malformed pair fails the boot like
the bindings precedent.

Self-hosts in CI: the guarded-fetch suite drives a real in-process
loopback listener through the fixture resolve policy (the one test-only
origin allowance; production never constructs it).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
The link: an operator confirms a verified address (the K1 report renders
first; no device code exists before that confirm) → ditdash starts the
RFC 8628 device authorization grant at the remote's issuer under its
public federation client id — read-only scope, no offline access → the
operator signs in at the remote → the access token lands ONLY inside the
ChaCha20-Poly1305-sealed HttpOnly `ditdash-link` cookie. No server-side
copy exists: the in-memory pending-flow map holds a device code, never a
token that outlives the Set-Cookie response, and the refresh token a
remote returns anyway is discarded and counted, never stored or logged.

Custody contract (every clause pinned by tests): the sealed payload binds
the local username as AEAD associated data (another identity's cookie
refuses cryptographically and is cleared); a cleartext key tag separates
"tampered" from "cookie key rotated?"; Max-Age = min(token exp, 24 h);
the key reads only from DITDASH_FEDERATION_COOKIE_KEY (64 hex) — absent
or malformed refuses every link action with the named text BEFORE any
flow step, and there is no plaintext or env-key fallback. The manifest
carries the secret ref PULLED with dated re-add notes (an unseeded ref
aborts every deploy — the WS-0 empty-resolved refusal), so the fail-closed
refusal is the live behavior until the Operator seeds the custody.

Surfaces: the switcher strip (local + linked tiles; use selects the plain
context cookie), the verification panel (verification_uri + user_code,
one poll per render, report-above-code ordering), the operator-only unlink,
and the three actions rows (login/logout/use with availability + blocking
reason) on the msp resource's page and twin — verbs absent below the
operator role, never present-but-denied (a viewer's POST 404s). The
masking proof is mechanical: the stub remote's transfer record shows the
token crossing the wire, and the full response corpus, every rendered
body, the twins, and the state-dir tree carry zero occurrences.

Manifest: the read-egress wildcard is PULLED with the same dated notes —
the deployed platform predates the multi-entry/wildcard egress.read shape
(runs 843-872 class); the guards in src/federation.rs remain the boundary
and every fetch fails closed until the wildcard lands.

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
The linked-msp render space: /msp/{m}/… renders live from the remote's
/api/v1 twins with the sealed cookie's token, and /api/v1/… gains the
forward-auth-bypassed bearer arm (E-6 §D8) — the same Identity value the
proxy-header path builds, so every existing denial applies unchanged.

- src/remote.rs: issuer discovery is zone-checked (DIT-SES-004 shape,
  the jwks_uri must sit inside the issuer's own zone), a stale JWKS
  refetches once, exp/iss/aud checked; one guarded request per resource,
  zero cache; envelope passthrough with links.ui rebased under the
  local /msp/{m} space.
- Degradation vocabulary frozen: 401/403 = the remote refused this
  request — re-verify your access at <remote>; 404 = the remote's own
  absence; unreachable = probe failed at <now> + unreachable since
  <browser-recorded last contact> (no prior contact recorded when none);
  never a cached-green paint.
- tests/remote.rs: the stub remote self-hosts K3's own bearer arm with a
  fixture JWKS + signed fixture JWTs; the U-K2 storage-scope walk rides
  the remote render (zero remote bytes at rest).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
remote_page rendered unreachable_lines for ANY non-fresh state, so a
401/403 refusal (and a 404 absence) also read 'unreachable…'. The outage
lines now render only when fetch.state == 'unreachable'; refused/absent
keep the state badge and the cause+fix line. The revoked-group page test
asserts the unreachable lines' absence.

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
deepnet/ditdash!9
No description provided.