Basekick-Labs / Basekick-Labs/arc

ListStaged reports partials its own DeleteStaged refuses

Open
#772 1 comment 0 reactions 0 assignees View on GitHub
good first issue
Dominant language
Go
Stars
677
Forks
53
Avg merge
9h 14m
Merged PRs (30d)
164

Description

Split out of #762 (item 3). The first half is sized for a first contribution; the second half is a design call and is explicitly out of scope here.

## Problem

`internal/storage/local.go` `ListStaged` (line 1163) walks for `*.part`, strips `PartSuffix`, and reports the result as the key the partial belongs to. `DeleteStaged` (line 1044) resolves that key through `stagedPath` (line 985), which validates the key body. Two consequences:

1. A partial whose key fails `validateKeyBody` (a legacy partial of a key that is illegal today) is reported by `ListStaged` and then refused by `DeleteStaged`: the enumerator's output is not accepted by its own deleter. `reclaimStagedPartials` (`internal/api/databases.go:790-809`) pipes one into the other, so `DROP DATABASE` logs a warning for that file on every run and never reclaims it.
2. A *committed* object whose real name ends in `.part` (legal before #744) is reported as a partial of a different key, and `DeleteStaged` on that key removes the committed object:
```
ListStaged("") -> "db/cpu/2026/09/12/13/legacy" (a key no object has)
DeleteStaged("db/cpu/2026/09/12/13/legacy") -> nil (removed legacy.part)
```

## What to change (this issue: consequence 1)

Make `ListStaged`'s contract match its consumer: validate the stripped key with the same rule `stagedPath` applies and do not report entries that `DeleteStaged` would refuse. Those files are not lost from view: `ListUnusable` (#765) already reports what `List` drops, and a `.part` file with an illegal key body is dropped by `List`; add a test asserting that the same file shows up there. Pin the invariant with a test: every key `ListStaged` returns is accepted by `DeleteStaged`.

## Out of scope (consequence 2, needs a design call)

On disk, a staging partial for key `k` and a committed object named `k.part` are the same path, so no enumerator can tell them apart by name. Fixing that means an unambiguous staging spelling (a distinct prefix or directory for partials, the way `.sync-staging/` is for edge sync), which touches the write path. Mention it in your PR description; do not attempt it there.

## Scope

One file plus tests. `go test ./internal/storage/...`. Release-notes entry per CONTRIBUTING.

Contributor guide

Open the contributing guide

Research direction

Start in internal/storage/local.go with ListStaged, stagedPath, and DeleteStaged, then inspect ListUnusable and reclaimStagedPartials in internal/api/databases.go. Run go test ./internal/storage/... and add tests showing illegal-key partials are omitted from ListStaged, reported by ListUnusable, and that every returned key is accepted by DeleteStaged. Finish with the required release-notes entry from CONTRIBUTING; leave the naming collision out of scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.