You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Attachment bytes are only ever removed by an explicit deleteAttachment(fileId) call. Normal app flows delete records — and nothing notices when the last record referencing a file goes away. The bytes (and the file's _attachment@1 metadata records) persist indefinitely. A second orphan source: putAttachment() stores bytes first, then creates the metadata record (packages/core/src/stack.ts:604–613) — a failure between the two leaves bytes with no record at all.
For a personal data stack whose pitch includes user ownership of a portable store, unbounded invisible growth of unreachable blobs is a real defect, not a nicety.
Decided direction
Explicit owner-invoked sweep — not automatic refcounting. Auto-delete on last dissociate/record-delete is racy without #46's transactions, couples every record write to blob lifecycle, and — decisively — breaks the recoverability model: #60 makes soft-deleted records restorable, and an undeleted record must find its attachments intact.
A file's own _attachment@1 metadata records do not count as references (else nothing is ever garbage) — but recent ones defer collection: a grace period covers the legitimate upload→associate window the spec already acknowledges (§Attachments, access "between upload and record association"). Concretely: skip files whose newest metadata record is younger than the grace period; likewise skip bare bytes newer than it (the upload-crash orphan, once bytes are old enough to be clearly abandoned).
API: Stack.collectAttachmentGarbage(opts?: { graceMs?: number, dryRun?: boolean }): Promise<{ deleted: FileId[], reclaimedBytes: number }> (naming open). Owner-only on ScopedStack. dryRun matters — the first thing anyone does with a GC is ask what it would delete.
Wire: optional owner-only POST /attachments/gc for served stacks; scheduling/automation is a server concern, out of scope for core.
Sequencing
Blocked by #63. GC's correctness is exactly "what counts as a reference"; running it while content-held references are invisible deletes files that are still in use. #63 must land first.
Problem
Attachment bytes are only ever removed by an explicit
deleteAttachment(fileId)call. Normal app flows delete records — and nothing notices when the last record referencing a file goes away. The bytes (and the file's_attachment@1metadata records) persist indefinitely. A second orphan source:putAttachment()stores bytes first, then creates the metadata record (packages/core/src/stack.ts:604–613) — a failure between the two leaves bytes with no record at all.For a personal data stack whose pitch includes user ownership of a portable store, unbounded invisible growth of unreachable blobs is a real defect, not a nicety.
Decided direction
Explicit owner-invoked sweep — not automatic refcounting. Auto-delete on last dissociate/record-delete is racy without #46's transactions, couples every record write to blob lifecycle, and — decisively — breaks the recoverability model: #60 makes soft-deleted records restorable, and an undeleted record must find its attachments intact.
Which yields the load-bearing rule:
file-reffield (file-ref field kind: content-held file references are invisible to the reference checks the spec says exist #63). Only hard deletion (or the record never existing) removes a reference. This is the same principle as Record-levelwritestays one bit — but everything it permits must be recoverable, and irreversible verbs get carved out #59/Undelete API: the spec promises soft-delete recovery, nothing implements it #60: nothing reachable from a recoverable state gets destroyed._attachment@1metadata records do not count as references (else nothing is ever garbage) — but recent ones defer collection: a grace period covers the legitimate upload→associate window the spec already acknowledges (§Attachments, access "between upload and record association"). Concretely: skip files whose newest metadata record is younger than the grace period; likewise skip bare bytes newer than it (the upload-crash orphan, once bytes are old enough to be clearly abandoned).Stack.collectAttachmentGarbage(opts?: { graceMs?: number, dryRun?: boolean }): Promise<{ deleted: FileId[], reclaimedBytes: number }>(naming open). Owner-only onScopedStack.dryRunmatters — the first thing anyone does with a GC is ask what it would delete.deleteAttachmentpath so the reference check runs once more per file at deletion time (belt and suspenders against races; Single-writer storage model: durability fixes for adapter-local + spec/README guidance on multi-app topology #45's single-writer rule plus Split SQLite support: native record-adapter-sqlite for Node, browser-only record-adapter-sqljs, and a StackTokenStore interface in core #46's transactions make the residual window negligible at this scale).queryAllPages), and the blob adapter needs alistFiles()-shaped capability to find bare-bytes orphans —BlobAdaptercurrently has no enumeration; content-addressed disk storage can enumerate cheaply. Adapter contract addition, capability-flagged.POST /attachments/gcfor served stacks; scheduling/automation is a server concern, out of scope for core.Sequencing
Blocked by #63. GC's correctness is exactly "what counts as a reference"; running it while content-held references are invisible deletes files that are still in use. #63 must land first.
Work items
BlobAdapter.listFiles()(capability-flagged) —blob-adapter-disk,MemoryAdapterequivalentcollectAttachmentGarbage()with grace period, dry-run, soft-delete-counts rule, exhaustive walkScopedStackwrapper + optional wire endpoint (+ PATCH contract mismatch: spec defines content-only merge patch, APIAdapter sends record-field envelopes #52 fixture if the endpoint ships)file-refcontent reference protects (file-ref field kind: content-held file references are invisible to the reference checks the spec says exist #63); fresh upload survives grace; dry-run deletes nothingRefs #63 (blocking), #60 (soft-delete-counts rationale), #59 (recoverability model), #50 (exhaustive pagination), #46 (transactions), #45 (single-writer context), #52 (endpoint fixture).