Skip to content

[Product Gap] Complete tenant-safe document lifecycle and artifact deletion #263

Description

@seonghobae

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:

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:

  1. authoritative privacy/security/test-evidence parent fix(security): harden audit pseudonymization and refresh Netty evidence #270;
  2. 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;
  3. implement the remaining user-facing lifecycle/API/UX slice without duplicating fix(security): require signed tenant claims on admin endpoints #268;
  4. 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:read permission for converted-PDF download.
  • Require explicit job:delete permission for user-facing deletion and admin:write for 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_id is 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 delete sequence.
  • Use one durable deletion state machine or transactional/outbox-equivalent contract with at least:
    • deletion_requested
    • metadata_tombstoned
    • artifact_cleanup_pending
    • artifact_cleanup_completed
    • artifact_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 SUCCEEDED jobs 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, and X-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, and Deleted; 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

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

Slice D3 — integrated recovery/release acceptance

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_case names, including deletion_request, deletion_receipt, artifact_cleanup_task, job_tombstone, and audit_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/

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions