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.

This issue replaces the corrupted and closed PR #175. Implement only after the reusable nested-safe busy-state contract in #264 and the signed administrative authorization boundary in #266 are integrated or cleanly superseded on protected main.

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.

End-to-end deletion lifecycle contract

The current repository-level tenant predicate is necessary but not sufficient. Artifact cleanup and worker scheduling occur after the scoped state mutation and are keyed only by jobId; therefore the product must make post-mutation side effects safe under concurrency and identifier reuse.

  • A conversion job_id is immutable and MUST NOT be reusable after deletion. Persist a tombstone or versioned lifecycle record 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 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 storage generation, 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. Return a truthful accepted/pending or failed result and retain retryable audit evidence.
  • 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, revocation records, and viewer bootstrap unavailable according to the documented retention policy.

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, range semantics, Cache-Control: no-store, and X-Content-Type-Options: nosniff.
  • Never reveal the original filename or artifact existence before tenant authorization.
  • Revoke or invalidate all issued artifact links when deletion is requested; 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: make asynchronous viewer actions accessible and nested-safe #264.
  • 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.

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.

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 and cleanup receipt may be created.
  • Crash after tombstone 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 the actor fingerprint and enqueues exactly once; missing/not-eligible/stale-generation paths never enqueue.
  • Verify signed artifact links stop serving after deletion 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, and bytes.
  • Production statement coverage 100% and branch coverage 100%.
  • 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 unresolved threads, independent write-authorized approval, and CHANGELOG.md/ADR updates.

Modular and MSA boundary

  • Keep the 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 must explicitly implement the same tenant predicate, generation, tombstone, and outbox semantics.
  • Preserve the versioned naruon/MSA integration boundary and do not couple the viewer to a single external host or storage vendor.
  • Database objects must use descriptive two-or-more-word snake_case names, for example deletion_request, deletion_receipt, artifact_cleanup_task, job_tombstone, and audit_event.

Scope boundary

Do not add generated repair scripts, duplicate the authentication layer, weaken branch protections, use a global lookup followed by an unscoped mutation, swallow cleanup failures, or treat an advisory bot response 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