Skip to content

Commit 5e299c4

Browse files
frristclaude
andauthored
feat!: align removal capabilities with the blob-removal RFC (#49)
Splits the provider leg out of `/blob/remove` into a new `/blob/release` binding, per the blob-removal RFC ([fil-one/RFC#13](fil-one/RFC#13), merged): `/blob/remove` and `/blob/abort` are now space-subject client verbs whose arguments no longer carry the space (it is the invocation subject), while `/blob/release` carries `{space, digest}` under the provider subject, matching `/blob/allocate` and `/blob/accept`. Cause references say `/blob/add` throughout — the `/space` command prefix died with the UCAN 1.0 transition. Defines the RFC's named errors where every service can share them: `BlobAccepted` (reject refused because **the invoking space** accepted the blob — the guard is space-scoped, not digest-scoped, so another tenant's acceptance never strands a parked allocation) and `MissingCause` (abort's cause is missing or doesn't resolve to a known `/blob/add` task). ## Landing order **This PR is the root of the removal chain** — fil-forge/piri#30, fil-forge/sprue#33, fil-forge/smelt#19, and fil-forge/ingot#40 all pin this branch's head (`3e5e6ba`) and re-pin to the merge commit once this lands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 7fc3b2c commit 5e299c4

9 files changed

Lines changed: 352 additions & 105 deletions

File tree

commands/blob/abort.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,36 @@ package blob
55
import (
66
"github.com/fil-forge/libforge/commands"
77
"github.com/fil-forge/ucantone/binding"
8+
"github.com/fil-forge/ucantone/errors"
89
"github.com/fil-forge/ucantone/ucan/command"
910
)
1011

1112
type AbortOK = commands.Unit
1213

13-
// Abort (/blob/abort) abandons an in-flight upload of a PARKED blob — one
14-
// that was allocated and uploaded (HTTP PUT) but never accepted. It is the
15-
// client-facing abandon verb: an upload ends in exactly one of
16-
// `/blob/accept` (commit) or an abort that the upload service translates
17-
// into `/blob/reject` on the storage node holding the blob.
14+
// Abort (/blob/abort) abandons an in-flight upload of a PARKED blob —
15+
// allocated but never accepted, whether or not the bytes ever reached the
16+
// storage node. It is the client-facing abandon verb: an upload ends in
17+
// exactly one of `/blob/accept` (commit) or an abort that the upload service
18+
// translates into `/blob/reject` on the storage node holding the allocation.
1819
//
1920
// Served by the upload service (subject = the space). A parked blob has no
2021
// registration or acceptance to look the storage node up by, so the service
21-
// recovers it from the Cause receipt chain and forwards a `/blob/reject`.
22-
// Blobs with an acceptance are released via `/blob/remove` instead.
22+
// recovers it from the Cause receipt chain and forwards a `/blob/reject`
23+
// (Cause itself is not forwarded — it is routing metadata, meaningless to
24+
// the node). A missing or unknown Cause fails with MissingCause. Blobs the
25+
// space has accepted are released via `/blob/remove` instead; if the node
26+
// refuses the translated reject with BlobAccepted, the service surfaces that
27+
// named failure in the abort receipt. The abort mutates no upload-service
28+
// state, so a failed abort is safely retryable.
2329
//
2430
// Idempotent: aborting an unknown or already-rejected blob succeeds.
2531
// The receipt carries no payload (Unit).
2632
var Abort = binding.Bind[*AbortArguments, *AbortOK](command.MustParse("/blob/abort"))
33+
34+
// MissingCauseErrorName is the stable receipt-failure name when an abort's
35+
// Cause is missing or does not resolve to a known `/blob/add` task —
36+
// without it the upload service cannot recover which storage node holds the
37+
// parked blob.
38+
const MissingCauseErrorName = "MissingCause"
39+
40+
var ErrMissingCause = errors.New(MissingCauseErrorName, "abort requires the cause of the /blob/add task that parked the blob")

commands/blob/abort_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,31 @@ import (
1414
// Round-trips AbortArguments through cbor.
1515
func TestAbortArgumentsRoundTrip(t *testing.T) {
1616
in := blob.AbortArguments{
17-
Space: testutil.RandomDID(t),
1817
Digest: testutil.RandomMultihash(t),
1918
Cause: testutil.RandomCID(t),
2019
}
2120
var buf bytes.Buffer
2221
require.NoError(t, in.MarshalCBOR(&buf))
2322
var out blob.AbortArguments
2423
require.NoError(t, out.UnmarshalCBOR(&buf))
25-
require.Equal(t, in.Space, out.Space)
2624
require.Equal(t, in.Digest, out.Digest)
2725
require.Equal(t, in.Cause, out.Cause)
2826
}
2927

28+
// Round-trips ReleaseArguments through cbor.
29+
func TestReleaseArgumentsRoundTrip(t *testing.T) {
30+
in := blob.ReleaseArguments{
31+
Space: testutil.RandomDID(t),
32+
Digest: testutil.RandomMultihash(t),
33+
}
34+
var buf bytes.Buffer
35+
require.NoError(t, in.MarshalCBOR(&buf))
36+
var out blob.ReleaseArguments
37+
require.NoError(t, out.UnmarshalCBOR(&buf))
38+
require.Equal(t, in.Space, out.Space)
39+
require.Equal(t, in.Digest, out.Digest)
40+
}
41+
3042
// Round-trips RejectArguments through cbor.
3143
func TestRejectArgumentsRoundTrip(t *testing.T) {
3244
in := blob.RejectArguments{

commands/blob/cbor_gen.go

Lines changed: 117 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

commands/blob/gen/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func main() {
3333
blob.AddArguments{},
3434
blob.AddOK{},
3535
blob.RemoveArguments{},
36+
blob.ReleaseArguments{},
3637
blob.AbortArguments{},
3738
blob.RejectArguments{},
3839
blob.ReplicateArguments{},

0 commit comments

Comments
 (0)