Repository navigation
[Product Gap] Complete tenant-safe document lifecycle and artifact deletion #263
Description
Buyer-visible outcome
Complete the Clearfolio document lifecycle so an authorized user can download a converted PDF and request deletion with an accessible, truthful, retryable result. No step may cross tenant boundaries, expose object existence, reuse a deleted identifier, or report completion while a confidential artifact remains available.
Live status — 2026-08-09
This issue is no longer an all-greenfield lifecycle plan. The implementation is split across active, unmerged work and remaining product UX/integration work:
- parent fix(security): harden audit pseudonymization and refresh Netty evidence #270 is actively aligning the direct conversion-job download route with the existing signed artifact-token, revocation, single-Range, integrity and read-audit authority. It has observed a real RED regression for the former permission-only byte path; fix(security): harden audit pseudonymization and refresh Netty evidence #270 is still unmerged and evolving.
- fix(security): require signed tenant claims on admin endpoints #268 already implements most of the lower lifecycle/security substrate described below on its branch: signed administrative tenant authorization, tenant-scoped mutation contracts, immutable/non-reusable job identity and generation fences, durable deletion receipts, restart/replay validation, cleanup retry/fairness evidence, privacy-safe audit pseudonymization and low-cardinality deletion metrics. It remains
ACTIVE_PR, not protected-main behavior, and is dependency-gated behind fix(security): harden audit pseudonymization and refresh Netty evidence #270. - fix(accessibility): make asynchronous viewer actions nested-safe #264 contains the nested-safe accessible busy-state primitive required by the user-facing delete flow. It also remains dependency-gated behind fix(security): harden audit pseudonymization and refresh Netty evidence #270.
- the remaining buyer-facing lifecycle gap after those branches integrate is the end-to-end user deletion/download experience and its exact integration/acceptance evidence, not a duplicate reimplementation of fix(security): require signed tenant claims on admin endpoints #268's lifecycle substrate.
- PR docs: establish canonical product, architecture, and acquisition spine #305 adds canonical architecture/data-model/API/threat/fidelity/migration/release documentation and marks these capabilities as
ACTIVE_PRrather than shipped until integration.
Therefore do not open duplicate PRs for the immutable identity/deletion-receipt/recovery work already owned by #268. First integrate/reconcile the active stack, then implement only the remaining bounded lifecycle slices against the new protected-main baseline.
This issue replaces the corrupted and closed PR #175. Current dependency order is:
- authoritative privacy/security/test-evidence parent fix(security): harden audit pseudonymization and refresh Netty evidence #270;
- reconcile/integrate fix(accessibility): make asynchronous viewer actions nested-safe #264 accessibility and fix(security): require signed tenant claims on admin endpoints #268 tenant-safe lifecycle/security descendants on the new protected-main parent;
- implement the remaining user-facing lifecycle/API/UX slice without duplicating fix(security): require signed tenant claims on admin endpoints #268;
- run integrated protected-main lifecycle, accessibility, security, restart/recovery and release acceptance.
Do not revive or depend on superseded #266.
Security and authorization contract
- Require explicit
artifact:readpermission for converted-PDF download. - Require explicit
job:deletepermission for user-facing deletion andadmin:writefor administrative deletion. - Resolve tenant and subject from the signed production authentication boundary; client-supplied unsigned demo headers are not production credentials.
- Enforce same-tenant ownership before reading artifact bytes, returning filenames, disclosing job existence, changing lifecycle state, or scheduling cleanup.
- Use non-enumerating denial responses and do not leak cross-tenant filenames, status, artifact metadata, checksums, or storage paths.
- Record privacy-safe, domain-separated pseudonymous audit evidence for allowed, denied, missing, accepted, cleanup-pending, cleanup-failed, and completed decisions without raw subject identifiers, tokens, filenames, or document content.
- Treat every page, header, log field, export, and audit event as tenant-scoped output.
- Dedicated tenant permission and signed artifact-delivery authority are separate controls; an authorized tenant request must not bypass token integrity, expiry, scope, document/tenant/checksum binding, issued-ledger membership, revocation, Range, or read-audit semantics where the byte-delivery contract applies.
End-to-end deletion lifecycle contract
The repository-level tenant predicate is necessary but not sufficient. Artifact cleanup and worker scheduling after a scoped state mutation must remain safe under concurrency and identifier reuse.
- A conversion
job_idis immutable and MUST NOT be reusable after deletion. Persist/reserve a tombstone or versioned lifecycle identity so a later tenant cannot create a different object under the same identifier. - Do not implement deletion as an untracked
repository delete -> best-effort artifact deletesequence. - Use one durable deletion state machine or transactional/outbox-equivalent contract with at least:
deletion_requestedmetadata_tombstonedartifact_cleanup_pendingartifact_cleanup_completedartifact_cleanup_failed
- Bind cleanup work to an immutable deletion receipt containing tenant, job identifier, object version/generation, artifact digest or controlled pending/confirmed-absence state, request identifier, and audit correlation identifier. The worker must reject a stale receipt rather than deleting a newer artifact.
- Prevent a same-identifier cross-tenant replacement between authorization, repository mutation, artifact cleanup, and retry enqueue.
- An accepted retry must enqueue exactly the lifecycle generation that was atomically transitioned. A stale or replaced generation must not be processed.
- Artifact cleanup failure must not be swallowed and reported as completed deletion. Retain retryable durable evidence and expose only truthful pending/failure/completion state.
- Repeated identical DELETE requests must produce the same intended effect without duplicate cleanup or contradictory responses.
- A completed deletion must make job metadata, artifact bytes, signed links, viewer bootstrap and retained token/read evidence unavailable or retained only according to the documented security/retention policy.
#268 is the current implementation owner for these lower-layer invariants. This issue remains the umbrella product acceptance authority until the integrated user flow is complete.
Download contract
- Render Download only for
SUCCEEDEDjobs with an authorized artifact resource. - Serve a real PDF fixture with expected media type, safe
Content-Disposition, exact digest, canonical zero/single-range semantics,Cache-Control: no-store, andX-Content-Type-Options: nosniff. - Never reveal the original filename or artifact existence before tenant authorization.
- Require canonical signed artifact delivery where applicable; revocation and controlled read-audit evidence remain part of the byte authority.
- Revoke or invalidate all issued artifact links when deletion is requested according to the lifecycle policy; no link may continue serving bytes after cleanup completion.
Accessible UX contract
- Render Delete with a document-specific accessible name.
- Require explicit confirmation and support cancellation without a network request.
- Apply the shared, nested-safe busy-state helper from fix(accessibility): make asynchronous viewer actions nested-safe #264 after that primitive integrates.
- Distinguish
Deleting…,Cleanup pending,Deletion failed, andDeleted; do not collapse them into a false success state. - Restore button text, accessible name, disabled state, focus, session history, and KPI state on success, authorization denial, server error, cleanup retry, and network error.
- Keep filenames containing markup inert text.
- Provide keyboard, touch, screen-reader, print/PDF, and exact-value evidence for lifecycle status.
Remaining bounded implementation sequence
Integration gate — active substrate
- Merge/fix fix(security): harden audit pseudonymization and refresh Netty evidence #270 through exact-head security/review/protection gates.
- Reconcile fix(security): require signed tenant claims on admin endpoints #268 and fix(accessibility): make asynchronous viewer actions nested-safe #264 against the new protected-main parent; preserve only their effective product deltas and reacquire exact-head/base-sensitive evidence.
- Do not copy their implementations into a new branch to avoid the dependency gate.
Slice D1 — user-facing deletion API contract
- Expose a tenant-authorized deletion request/result that uses the integrated immutable-generation/deletion-receipt service instead of bypassing it.
- Define stable accepted/pending/failed/completed response semantics and idempotency behavior.
- Revoke/invalidate artifact access according to the accepted lifecycle without racing cleanup.
Slice D2 — accessible product flow
- Add confirmation, pending/failure/completion UI, focus restoration, exact-value status and real DOM/browser tests on the integrated fix(accessibility): make asynchronous viewer actions nested-safe #264 busy-state primitive.
- Keep byte-download and deletion behavior truthful under retries/network errors.
Slice D3 — integrated recovery/release acceptance
- Prove restart after accepted deletion, cleanup failure/retry, duplicate DELETE, stale generation, cross-tenant UUID probes, artifact-link invalidation, and buyer-visible recovery states on one exact integrated protected head.
- Run the canonical fidelity/migration/release authorities from PR docs: establish canonical product, architecture, and acquisition spine #305 after they are integrated; if docs: establish canonical product, architecture, and acquisition spine #305 is not yet on protected main, equivalent evidence remains required rather than skipped.
Each remaining slice must be independently reviewable and must not mix unrelated dependency, scheduler, or release-control changes.
Verification
Authorization and concealment
- Missing, malformed, expired, future-dated, invalid-signature, wrong-permission, and cross-tenant signed claims.
- Artifact-byte, filename, digest, status, and object-existence non-disclosure.
- Same-tenant success and cross-tenant/missing equivalence.
- Missing/revoked/mismatched artifact token cannot be bypassed through a convenience download route.
Concurrency and lifecycle
- Replace the same UUID with another tenant immediately before and immediately after the scoped mutation; no replacement metadata or artifact may be deleted, retried, or enqueued.
- Attempt identifier reuse after tombstoning; creation must fail closed.
- Race duplicate DELETE requests; exactly one lifecycle transition/receipt identity may be authoritative.
- Crash after receipt/tombstone acceptance but before artifact deletion, then restart and resume cleanup.
- Artifact store timeout, permission failure, missing artifact, corrupted metadata, and stale generation receipt.
- Accepted retry records controlled audit evidence and enqueues exactly once; missing/not-eligible/stale-generation paths never enqueue replacement work.
- Verify signed artifact links stop serving according to deletion-state policy and never after cleanup completion.
Product and quality
- Browser/DOM tests for confirmation cancellation, pending, completion, retryable failure, focus restoration, duplicate activation prevention, and accessible busy state.
- Real PDF download fixture with expected headers, digest, bytes, signed token, Range, revocation and read-audit evidence.
- Production statement coverage 100% and branch coverage 100% for owned code.
- Complete beginner-readable public Javadocs/docstrings.
- Property and concurrency tests for lifecycle monotonicity and identifier non-reuse.
- Exact-head CI, Security Scan, SAST, fuzzing, CodeRabbit/OpenCode/Noema review, zero valid unresolved threads, independent write-authorized approval, and canonical changelog/ADR/release evidence.
Modular and MSA boundary
- Keep repository, state store, artifact store, deletion receipt/outbox, audit sink, and worker behind versioned interfaces.
- Standalone in-memory mode must remain functional, while durable adapters explicitly implement the same tenant predicate, generation, tombstone, receipt/outbox and recovery semantics.
- Preserve the versioned naruon/MSA integration boundary and do not couple the viewer to a single external host or storage vendor.
- Database objects use descriptive two-or-more-word
snake_casenames, includingdeletion_request,deletion_receipt,artifact_cleanup_task,job_tombstone, andaudit_event.
Scope boundary
Do not add generated repair scripts, duplicate the authentication/lifecycle layer, weaken branch protections, use a global lookup followed by an unscoped mutation, swallow cleanup failures, reuse deleted identifiers, race the active #270/#268/#264 writers, or treat advisory/model/status evidence as protected-branch approval.
References
Fielding, R., Nottingham, M., & Reschke, J. (2022). HTTP semantics (RFC 9110). Internet Engineering Task Force. https://www.rfc-editor.org/rfc/rfc9110
OWASP Foundation. (2023). OWASP API Security Top 10—2023: API1:2023 Broken object level authorization. https://owasp.org/API-Security/editions/2023/en/0x11-t10/
Activity
[-][Product Gap] Rebuild tenant-safe viewer download and deletion flow[/-][+][Product Gap] Complete tenant-safe document lifecycle and artifact deletion[/+]