Skip to content

Admin Phase 3, slice 1: staged-edits API over ADMIN_DB + insert-only action-audit tripwire#55

Merged
MartinMontero merged 3 commits into
mainfrom
claude/wcjbt-credential-health-check-64ymr3
Jul 11, 2026
Merged

Admin Phase 3, slice 1: staged-edits API over ADMIN_DB + insert-only action-audit tripwire#55
MartinMontero merged 3 commits into
mainfrom
claude/wcjbt-credential-health-check-64ymr3

Conversation

@MartinMontero

Copy link
Copy Markdown
Owner

What does this PR do?

The first server slice against the now-provisioned ADMIN_DB (#54) — three commits, one logical change each:

  • f0d78a2data layer (worker/admin/staging.ts): list (metadata only — never draft bodies), get, create, update, abandon over staged_edits; every mutation batches its row atomically with an INSERT into admin_action_audit (detail column carries validated-enum tokens only, never free text). 30-day TTL slides on update, deliberately not on abandon; no hard DELETE. AdminEnv/worker Env gain the optional ADMIN_DB binding (absent ⇒ 503, fails closed).
  • 0441251routes (/api/admin/staging*, five of them): the superadmin management gate generalizes to requireActor(minRole) — one gate, both auth paths (cookie+CSRF / per-request NIP-98 with replay burn), per-request role re-derivation, own admin-staging rate bucket. Scoping: admins touch only their own drafts (cross-author reads as 404 — no existence oracle); superadmins list/read/abandon any draft but can never update another author's words (explicit 403). Closed drafts reject with 409. The NIP-98 u tag for get covers the query string.
  • e8d703btests: 17 new tests pinning the journeys by name (J1–J6), including the static insert-only tripwire extending the 0002 audit contract to admin_action_audit across every worker/admin module.

Built under a build→verify loop with executed evidence for every claim: full suite 352/352, verify:all green, wrangler deploy --dry-run resolves env.ADMIN_DB, and a live wrangler dev smoke against a local D1 (migration applied; unauth → generic 401 with zero set-cookie; unknown route → 404).

Not in this slice (next): enforcement-engine wiring for drafts, the publish→PR path, console UI, moderation routes.

Type of change

  • New catalog entry
  • Fix/update to an existing entry
  • New or updated recipe
  • Build flow / docs
  • Enforcement engine / tooling
  • Other (admin Worker API + tests)

Required checks

  • npm run check passes
  • npm run enforce passes (zero new dependencies)
  • npm test passes — 352/352 (+17 this slice)
  • npm run build succeeds (full verify:all green)

Security posture

  • Fails closed everywhere: missing/unreachable ADMIN_DB is an explicit 503, never an empty 200; non-member 401 is indistinguishable from bad-signature 401; cross-author 404 is indistinguishable from nonexistent.
  • admin_action_audit is INSERT-only by contract, pinned statically in tests — an editable audit trail is theater.
  • Nothing logged by the slice; audit rows carry public identifiers and enum tokens only.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP


Generated by Claude Code

claude added 3 commits July 11, 2026 07:43
…ce 1a)

worker/admin/staging.ts: the D1 layer for staged catalog/content edits over
the separate ADMIN_DB (migrations-admin/0001) — list (metadata only, never
draft bodies), get, create, update, abandon. Every mutation batches its row
with an INSERT into admin_action_audit atomically; the audit detail column
carries tokens derived from validated enums only, never free text. Rows age
out via a 30-day TTL that slides on update and deliberately not on abandon;
there is no hard DELETE. Like roster.ts, every DB failure throws so the
routes surface an explicit 5xx.

AdminEnv + the worker Env gain the optional ADMIN_DB binding (absent ⇒ the
staging routes fail closed with 503).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP
…se-3 slice 1b)

Five routes: list (GET), get (GET ?id), create/update/abandon (POST). The
superadmin-only management gate generalizes to requireActor(minRole) — one
gate, both auth paths (cookie + per-request NIP-98), per-request role
re-derivation, CSRF on every mutation, its own admin-staging rate bucket;
requireSuperadmin stays as a thin wrapper so the roster routes are unchanged.

Scoping: admins operate on their OWN drafts — another author's draft reads as
404, indistinguishable from nonexistent (no oracle). Superadmins list/read/
abandon any draft (janitorial) but can never update another author's words
(explicit 403; they can already see the row, so honesty beats a fake 404).
Closed drafts (abandoned / future pr-opened) reject mutation with 409. The
NIP-98 u-tag for get covers the query string — the id is part of what was
signed. Missing ADMIN_DB ⇒ explicit 503; unreachable ⇒ 503, never an empty
200 pretending the store is fine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP
…t pin

17 tests pinning the LOOP.md journeys by name: J1 cookie happy path; J2
fails-closed matrix (401 non-member == 401 bad signature, 503 missing AND
unreachable ADMIN_DB, 403 CSRF, 403 cross-site, 429 rate-limited, 404 wrong
method); J3 author scoping incl. the superadmin update-others 403; J4
validation + lifecycle (400s write zero audit rows; closed drafts 409;
expired rows invisible); J5 exact audit rows per mutation + the static
tripwire extending the 0002 contract to admin_action_audit across every
worker/admin module; J6 per-request NIP-98 with replay burn + u-tag
mismatch. Same harness discipline as the other admin suites: in-memory
fakes, real schnorr, the real AdminCoordinator, the compile-time deps seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVPyHAtQ1cdK76RpFasafP
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
wecanjustbuildthings e8d703b Jul 11 2026, 07:49 AM

@MartinMontero
MartinMontero merged commit 0f61600 into main Jul 11, 2026
14 checks passed
@MartinMontero
MartinMontero deleted the claude/wcjbt-credential-health-check-64ymr3 branch July 11, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants