Load the site. Expect: page title editor-web — Cinderdoc, a My documents panel, and the
empty-state line No live local documents yet. Create one to set its fuse and save an encrypted (verified live, 2026-08-08T01:45Z).
.cdoc.
cinderdoc-monorepo
Derived from: commit a95853c (dev), 2026-08-07, against the live production deployment at
Flows
check each step and final checkCold load and the sign-in gate
Proves the editor loads cold, shows the recipient-bound identity gate, and drafts stay local until a real signed-in Save.
Persona / precondition: any visitor, signed out, fresh private/incognito tab, no cached session, at https://cinderdoc.thewolf.tech.
Look at the identity panel. Expect: heading Sign in to open recipient-bound Cinderdocs,
explanation Sign in to prove which verified recipient email is requesting the key., and two
buttons Continue with Google and Continue with GitHub (verified live, 2026-08-08T01:45Z).
Without signing in, click + Create Cinderdoc and type a title and body. Expect: you can
compose a draft locally — nothing is sent yet — but no Save can complete while signed out (to
confirm — POST /api/keyvault/documents requires Bearer + the v2 client header per
[sensitive content omitted]
{"error":"authentication_required"}`, never a silent save).
Reload the page. Expect: still signed out, identity panel still shown — nothing from step 3
persisted a session.
[sensitive content omitted]sessionStorage before you ever sign in (tokens are documented to live in memory only —
[sensitive content omitted]
API identity and version boundary (read-only spot-check)
Proves the deployed keyvault API itself enforces the auth/version gate, independent of the UI — every check below is an unauthenticated GET and changes nothing.
Persona / precondition: a terminal with curl. No account needed.
Run curl -s https://cinderdoc.thewolf.tech/api/keyvault/health. Expect:{"ok":true,"service":"keyvault","apiVersion":"2.0.0","minClientVersion":"2.0.0"} (verified
live, 2026-08-08T01:50Z).
[sensitive content omitted]
Expect: body {"error":"authentication_required"} and status 401 (verified live,
2026-08-08T01:50Z).
Run curl -s -w '\n%{http_code}\n'.
https://cinderdoc.thewolf.tech/api/keyvault/documents/00000000-0000-0000-0000-000000000000/status
Expect: body {"error":"not_found"} and status 404 (verified live, 2026-08-08T01:50Z).
Run curl -s -o /dev/null -w '%{http_code}\n' http://cinderdoc.thewolf.tech/. Expect: 302
redirecting to the https:// origin — plain HTTP is never served content directly (verified
live, 2026-08-08T01:50Z).
Run curl -s -m 5 http://cinderdoc.thewolf.tech:8787/health and the same against port 5404.
Expect: both hang and time out — keyvault has no public port of its own; it is reachable
only through Caddy's /api/keyvault/* proxy (verified live, 2026-08-08T01:50Z — both timed out
after 5s with no response).
Final check: none of the five calls above needed a credential or changed any resource —
confirming this whole flow is safe to re-run at any time, including in production.
Sign in, create, set the fuse, and save a recipient-bound Cinderdoc
Proves the primary authoring path end to end: identity, the fuse (expiry) control, the honesty copy shown together, and a real key-custody save.
Persona / precondition: signed out, your test Google or GitHub account.
Click Continue with Google (or Continue with GitHub) and complete the provider's real
consent screen. Expect: you land back on https://cinderdoc.thewolf.tech showing Signed in and a
as {your email}Sign out control (to confirm — requires a real OAuth round trip;
[sensitive content omitted]
[sensitive content omitted]
Click + Create Cinderdoc, then click into the title and body fields before typing anything.
Expect: placeholder text reads Counter-offer — 123 Elm St (title) and a body placeholder
describing a $1,240,000 purchase offer — both disappear once you type your own text.
Open the fuse control. Expect: heading When does this document burn out?, subtext The, and exactly four duration presets:
load-bearing control. This is the whole promise.1 hour,24 hours, 7 days, 30 days — there is no sub-hour preset in this UI.
Pick 1 hour and confirm the fuse. Expect: both honesty strings render, visibly separate,
never merged into one claim: This document self-destructs. Once it expires — or you destroy it and
early — its encryption key is deleted forever and it can never be decrypted again by anyone,
including us.Screenshot & recording protection is a deterrent, not a promise. It can't
stop an external camera, a rooted device, hardware capture, or someone retyping what they see.
Click Save. Expect: a real POST /api/keyvault/documents round trip succeeds and the
document appears under All live cinderdocs (to confirm — requires an authenticated write, out
of scope for this pass's read-only checks; units/editor-web/DOGFOOD.md's recorded transcript
of the same saveDocument code path returned {"ok":true,"docId":"...","grantId":"...", with no plaintext anywhere in the container).
"container":{...}}
Click Export. Expect: a browser download named <docId>.cdoc; opened as JSON it has exactly
the fields v, docId, alg, nonce, ciphertext, policy — no title or body field
anywhere (units/editor-web/README.md's .cdoc container contract).
Final check: search the exported .cdoc file's raw text for your typed title or body.
Expect: no match anywhere in the file — it is ciphertext-only.
Reopen: the author gets no bypass
Proves reopening runs the identical opening sequence for everyone, including the document's own author — there is no owner fast path.
Persona / precondition: F3 completed; the saved document still listed under All live cinderdocs.
Click the saved document to reopen it. Expect: a fresh grant → key → decrypt round trip runs
(not a cached copy) and your typed content reappears exactly as saved.
Sign out, sign back in with the SAME account, and reopen the same document again. Expect:
identical success — units/editor-web/src/lib/reopen-flow.ts has no owner/author parameter, anddescribeReopenDenial is keyed only by error code, never by who is asking.
While signed in, call GET with no
https://cinderdoc.thewolf.tech/api/keyvault/documents/<your docId>/statusAuthorization header (curl or dev tools). Expect: 401 — status is not free to
{"error":"authentication_required","accessMode":"recipient-bound"}
[sensitive content omitted]GET, safe to run yourself.
Final check: the reopened content is byte-identical to what you typed in F3 — no truncation,
no stray characters from the encrypt/decrypt round trip.
Wrong recipient and signed-out reopen are denied
Proves the recipient list is the real access boundary — holding the exact .cdoc file is never enough on its own.
Persona / precondition: the .cdoc exported in F3; a second Google or GitHub account whose email was never added as a recipient.
Sign out of the F3 account. Expect: the dashboard returns to Sign in to open, and the F3 document is no longer listed as openable in this browser
recipient-bound Cinderdocs
profile (the local index is device-local metadata only).
Sign in with the second account and attempt to open the F3 .cdoc. Expect: the denial This (the exact
verified email is not on this Cinderdoc's recipient list. No key was fetched.REOPEN_DENIAL_COPY.recipient_not_authorized string in units/editor-web/src/lib/copy.ts) —
never any document content on screen.
Sign out entirely and attempt the same .cdoc again. Expect: the denial Sign in with a
Google or GitHub account whose verified email is on the recipient list. No key was fetched.
(the authentication_required variant of the same copy table) — a different message from step
2 because the failure reason is genuinely different (no session at all, vs. a session with the
wrong email).
Final check: at no point in this flow does any plaintext title or body appear on screen for the
second account or the signed-out state.
Send the Cinderdoc to a recipient
Proves the ciphertext-only email relay: a real recipient gets added to the document and receives a real message, and the server never keeps a copy of the attachment.
Persona / precondition: F3's document still live (unexpired), signed in as its owner, a second real email inbox you can check.
Open the live document and choose Send Cinderdoc. Expect: heading Send this encrypted and the disclosure
.cdocCinderdoc relays the encrypted .cdoc to your recipients and does not
store the attachment on its server. Resend and recipient mail providers process the email in
transit and may retain it under their own policies.
Type the second email into the recipient field (placeholder reader@example.com,). Expect: the reminder
another@example.comRecipient binding does not stop an authorized is visible before you send.
recipient from sharing what they can access.
Click Send. Expect: Sent to 1 recipient. and the second inbox receives a real email fromCinderdoc <documents@mail.cinderdoc.thewolf.tech>, subject {your email} sent you a Cinderdoc,
with the .cdoc attached and the line Sign in with this verified email: {the recipient's (to confirm — requires an authenticated multipart POST and a real inbox check;
email}
[sensitive content omitted]
From the recipient's inbox, download the attachment, open a fresh signed-out browser, and sign
in as the recipient. Expect: the document opens for real — the recipient is now genuinely on
the server-side list, not just holding a copy of the file.
Final check: if you can see the Resend dashboard, confirm the message shows one to address
only, no CC/BCC — recipients never see each other's addresses.
Explicit Destruct is irreversible
Proves the manual key-destruction path is immediate, final, and independent of the expiry clock.
Persona / precondition: signed in as the owner of a live document — use a fresh throwaway one, not F3's, since this step cannot be undone.
Open a live document you own and choose the destruct control. Expect: modal headingDestruct "{title}"?, eyebrow Burn the fuse now, and the finality copy Destroying this
document deletes its encryption key forever. It can never be decrypted again by anyone,
including us. Every exported copy everywhere becomes permanently unreadable.
Click Keep document (cancel). Expect: nothing happens — the document is still live
afterward.
Reopen the destruct modal and confirm for real. Expect: the dashboard row immediately shows
the document burned out (no Open/Export actions) — matching keyvault's real POST →
/documents/:id/destroy{"destroyed":true} behavior recorded inunits/keyvault/DOGFOOD.md.
Try to reopen the destroyed document anyway. Expect: This document has expired or been (
destroyed. Its key is gone; it can never be opened again.REOPEN_DENIAL_COPY.denied_expired)
— never a partial success.
Final check: if you have API access, call destroy on the same document a second time.
Expect: still {"destroyed":true} — destroy is documented idempotent
(units/keyvault/README.md).
Expiry burns the document out automatically
Proves the fuse is a real, enforced deadline, not just a countdown UI, even if nobody manually destroys the document.
Persona / precondition: signed in; a throwaway document.
Create a document, set the fuse to 1 hour — the shortest this UI offers (the fuse slider
bottoms out at 1 hour in units/editor-web/src/lib/fuse-duration.ts's FUSE_SLIDER_HOURS;
there is no sub-hour option in the editor). Save it, then wait out the hour (or come back to
this step later) and check the dashboard. Expect: the document now renders burned out — no
Open/Export actions.
Faster alternative (mirrors this project's own drills): if you have API access, create a
document with expiresAt a few seconds out and request its key once before and once after that
moment passes. Expect: the pre-expiry request succeeds and the post-expiry one returns 410 — the same behavior recorded in
{"error":"denied_expired"}units/keyvault/DOGFOOD.md's
transcript and re-proven one layer up inunits/server-deploy/DEPLOYMENT.md#sd-t007--key-destruction-at-expiry-drill-deployed-instance-level.
Check GET .../documents/<docId>/status for the expired document (with a valid session).
Expect: {"expired":true,...}.
Final check: attempt to reopen the expired document. Expect: the identical denial as F7's
destroyed-document case — expiry and explicit destroy converge on one honest message, This
document has expired or been destroyed. Its key is gone; it can never be opened again.
Legacy-document compatibility (pre-v2 documents)
Proves a document created before the Auth+Send v2 cutover still honestly opens under the old rule until it expires, and is visibly marked as weaker than a recipient-bound one.
Persona / precondition: a .cdoc created before the 2026-07-25 v2 cutover (see F12). None exists in this checkout — run this flow only if such a fixture is still available; otherwise record it as not reachable.
Open a legacy (pre-v2) .cdoc. Expect: badge Legacy access — not recipient-bound and
warning `This Cinderdoc predates recipient binding. Until it expires, anyone holding the file
[sensitive content omitted]
legacy fixture exists in this checkout to verify against directly).
Try Send Cinderdoc on it. Expect: refused with To send with recipient binding, re-save — HTTP
this legacy Cinderdoc as a new document.409 legacy_document_not_sendable at the API
[sensitive content omitted]
Confirm an old client with no X-Cinderdoc-Client header hitting create/destroy/grant/key/send
now gets refused outright. Expect: HTTP 426 with body {"error":"client_upgrade_required", (to confirm
"message":"This Cinderdoc requires a newer app before it can be opened.",
"minClientVersion":"2.0.0","downloadUrl":"https://releases.cinderdoc.thewolf.tech"}
— this needs a real write against production with a deliberately old header, out of scope for
[sensitive content omitted]
Final check: the legacy document, once past its own expiresAt, denies exactly like every
other expired document — no special-cased legacy leniency after expiry.
Download and verify the desktop tester viewer (macOS)
Proves the unsigned tester distribution channel is honest about its own trust status and lets a tester verify integrity before running anything.
Persona / precondition: a macOS machine, a browser, at https://releases.cinderdoc.thewolf.tech.
Load the releases page. Expect: title Cinderdoc Viewer — tester release, eyebrowCINDERDOC VIEWER · TESTER CHANNEL, heading Open documents without leaving expiry behind.
(verified live, 2026-08-08T01:53Z).
Wait for the page's own checksum check to finish. Expect: the status line becomes A tester, a
build is available. Verify its checksum before installing.Download Cinderdoc Viewer link appears, and the shown SHA-256 reads
(.dmg)a6b61cd02aa1d5c1c4f154e1b3b8ed0d5793313bc55174b2bb62cecc1d55322a (verified live,
2026-08-08T01:53Z, by fetching /artifacts/CinderdocViewer.dmg.sha256 directly).
Download the DMG yourself and run shasum -a 256 on it. Expect: your local hash matches the
value shown on the page exactly.
Read the installation note on the page. Expect: it states plainly macOS warns because this — it never tells you to ignore Gatekeeper
tester build is not yet notarized by Apple. That is useful information about the build's current
distribution status, not a warning to dismiss.
(verified live, 2026-08-08T01:53Z).
Move the app to Applications, try to open it, then follow the page's own steps: System Settings →
→ Privacy & SecurityOpen Anyway for Cinderdoc Viewer, authenticate, then Open.
Expect: the app launches only after that explicit second confirmation.
Final check: run curl -s https://releases.cinderdoc.thewolf.tech/artifacts/latest.json.
Expect: 404 — there is deliberately no auto-updater manifest yet (verified live,
2026-08-08T01:56Z; units/server-deploy/runbooks/RELEASES.md's stated boundary).
Desktop viewer: opening sequence and honest denials
Proves the packaged viewer enforces the same fixed opening sequence and honesty copy as the web spec — capture protection is verified before any key request, and every denial states the true reason.
Persona / precondition: the viewer installed in F10; a .cdoc you can open on this Mac, and one whose policy requires block-supported-capture (which macOS cannot provide — only Windows can).
Launch Cinderdoc Viewer cold, no document open. Expect: heading Open a protected, a capability line for this Mac's detected tier, and a button
document.Open .cdoc… — no
leftover watermark or content chrome from any earlier session
(units/protected-viewer-desktop/src/lib/empty-view.ts).
Click Open .cdoc… and pick a document whose policy accepts detect-and-shield (macOS's tier).
Expect: an opening-sequence panel titled Opening {filename} with the note The key is, then the real decrypted content renders with a visible
fetched only after this device passes.
watermark.
Open a document whose policy requires block-supported-capture. Expect: a denial whose body
ends with The reason is this device's capability, not your access. — never a message implying
you personally lack permission.
With a document open, trigger macOS's screen-capture UI yourself (Cmd+Shift+5). Expect:Covered — screen capture detected with footer SHIELD ACTIVE · screen capture detected ·; content restores only after the capture UI is gone. This is the one step in
content masked
this whole plan a human should run for real — the project's own build deliberately never
triggered this real OS UI itself after an earlier attempt seized the operator's live screen
(see the incident note in units/protected-viewer-desktop/DOGFOOD.md), proving the mechanism
with an injected signal instead.
Open an expired or destroyed document (reuse one from F7 or F8). Expect: Burned out. The framed as
key is gone.This is the guarantee, working / Self-destruct isn't an error state.
The document did exactly what it promised.
Final check: throughout this flow, confirm the honesty boundary is never upgraded to a stronger
claim: Screenshot & recording protection is a deterrent, not a promise. It can't stop an external
camera, a rooted device, hardware capture, or someone retyping what they see.
Operator: confirm what's actually deployed
Proves the live deployment's own version signals are internally consistent, and surfaces a real gap this plan found between this checkout's git history and what's actually running — every step is an unauthenticated GET, safe to re-run anytime.
Persona / precondition: none.
Run curl -sI https://cinderdoc.thewolf.tech/. Expect: server: Caddy, HTTP/2 200, and alast-modified header (verified live, 2026-08-08T01:50Z: last-modified: Sat, 25 Jul 2026).
02:48:07 GMT
Compare that date against this checkout's own history: run git -C. Expect (currently fails — this is the
~/Projects/cinderdoc-monorepo log -1 --format='%h %ci'
defect, not a testing mistake): the checkout should be at least as new as what's live. Instead
the checkout's newest commit is a95853c, dated 2026-07-11 — two weeks OLDER than production's
2026-07-25 bundle. This checkout's own _handoff/cinderdoc-mono.md (written 2026-07-19) even
states production was still one epic behind at that time; production has since moved past both
this checkout and that handoff note without either being updated. Treat this checkout as a
historical snapshot, not a live mirror, until _build/sync-monorepo.sh is re-run from the real
workspace.
Run curl -s https://cinderdoc.thewolf.tech/api/keyvault/health. Expect: apiVersion andminClientVersion both 2.0.0 (verified live, 2026-08-08T01:50Z), confirming the Auth+Send v2
[sensitive content omitted]
nothing in this checkout's git log or units/server-deploy/DEPLOYMENT.md narrates that cutover
happening.
Run curl -s -o /dev/null -w '%{http_code}\n'. Expect:
https://releases.cinderdoc.thewolf.tech/artifacts/latest.json404 — no
auto-updater manifest is served, by design (verified live, 2026-08-08T01:56Z).
Final check: none of the four checks above required a credential or changed anything —
confirming this flow is safe to run as a recurring health check.
Not covered and why
- The GitHub sign-in path, walked separately from Google: structurally symmetric per
[sensitive content omitted]
here — worth a dedicated pass with a GitHub test account.
- Real Windows on-hardware
block-supported-capturevalidation: the dev host is macOS;
units/protected-viewer-desktop/README.md discloses this is compile/mock-tested only
(cargo check --target x86_64-pc-windows-gnu), never run on real Windows hardware.
- Apple notarization / a signed updater: the tester DMG is deliberately unsigned and
/latest.json deliberately 404s (F10's final check); there is nothing to test here until that
ships.
- Multi-recipient stress (the 20-recipient / 10 MiB raw
.cdoccaps on Send): not exercised at
the edge in this pass.
- Authenticated Coolify console operations (redeploy, DNS, env-var rotation, real ACME
certificate issuance): human-attended only, per every unit's own "Honest deferral list".
- Postgres swap / horizontal scaling: named as future options in
units/keyvault/README.mdand
units/server-deploy/README.md; v1 is single-container/single-SQLite by design.
- Backup/restore/rollback drills (
units/server-deploy/DEPLOYMENT.md's SD-T005–T018): already
have real, recorded, dated transcripts in that file from this project's own operational history;
re-running them destructively against the live production stack is out of scope for a user-test
pass and was not repeated here.
- KIT_FINDINGS.md / STATE.md /
_pipeline/: intentionally scrubbed from this monorepo mirror by
_build/sync-monorepo.sh (by design, not a defect) — this checkout cannot surface the workspace's
own process-defect ledger; consult /Users/wolf/Projects/cinderdoc directly for that.
Results
| Flow | Verdict | Failures → tickets |
|---|---|---|
| F1 sign-in gate | ||
| F2 API boundary | ||
| F3 create/save | ||
| F4 reopen, no bypass | ||
| F5 wrong recipient | ||
| F6 send | ||
| F7 destruct | ||
| F8 expiry | ||
| F9 legacy | ||
| F10 viewer download | ||
| F11 viewer opening sequence | ||
| F12 deployment consistency |