feat(submit): give the upload lane a lifecycle — withdraw + admin delete (#76)
Nothing ever removed a submission: users could not retract a pending row, no
route deleted blobs or rows, and the reaper never touches uploads — so every
upload accumulated on the 5 GiB PVC forever and the only cleanup was SQL or
kubectl against the store.
- Blobs.Delete on both transports (local: RemoveAll of the id-namespaced dir,
id re-validated at the boundary; S3: idempotent object DELETE).
- Store: DeleteSubmission (admin, any status) and DeletePendingSubmission
(owner+pending CAS — a reviewed row can never be withdrawn out from under
its build).
- Manager.Delete / Manager.Withdraw delete the ROW first (under the CAS for
withdraw) and the blob after, so a live row can never point at a reaped
blob; a cleanup failure names the orphan explicitly instead of failing mute.
- API: DELETE /me/submissions/{id} (withdraw, app tier) and
DELETE /api/v1/submissions/{id} (admin) both return the row as it was;
audit events submission.withdraw / submission.delete; openapi documents both
paths; admin route pinned in the admin-only table.
- Panel: two-step withdraw on a pending row (frees the pending slot and the
storage budget); two-step delete on every admin row; zh/en copy; wire tests.
Unit: submit (withdraw happy path / wrong owner / reviewed row / no transport /
blob-cleanup failure), local+S3 delete idempotence, api handlers (200/404/409/
503 + route tier); pgint: withdraw CAS + admin delete exactly-once.
go vet/go test/gofmt clean; panel vitest 120 + typecheck green.
This commit is contained in:
20 files changed
+828
-45
No files matched your search
@@ -4355,6 +4355,41 @@ paths:
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
|
||||
/api/v1/me/submissions/{id}:
|
||||
delete:
|
||||
tags: [submissions]
|
||||
operationId: withdrawSubmission
|
||||
summary: Withdraw your own pending submission (user side; user-directed lane over §16).
|
||||
description: >-
|
||||
Retracts the caller's own submission while it is still pending review:
|
||||
the row and its uploaded build context are deleted, freeing the pending
|
||||
slot and the per-user storage budget for a fresh submission. A reviewed
|
||||
submission is frozen (409 — its build may already be consuming the
|
||||
context), and a submission the caller does not own reads back as 404, so
|
||||
this endpoint cannot probe or clear another user's uploads.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ accessJWT: [] }]
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: string } }
|
||||
responses:
|
||||
'200':
|
||||
description: The withdrawn submission, as it was before the deletion.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Submission' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'409':
|
||||
description: Submission has already been reviewed and cannot be withdrawn.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
|
||||
# ------------------------------------------------------ external: admin ----
|
||||
/api/v1/servers/{name}:
|
||||
patch:
|
||||
@@ -4750,3 +4785,35 @@ paths:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
|
||||
/api/v1/submissions/{id}:
|
||||
delete:
|
||||
tags: [submissions]
|
||||
operationId: deleteSubmission
|
||||
summary: Retire a submission outright — row and uploaded context (admin; user-directed lane over §16).
|
||||
description: >-
|
||||
Removes the submission and its uploaded build context, any status — the
|
||||
lane's only lifecycle valve, and the path that reclaims a rejected or
|
||||
consumed upload from the uploads PVC. The reviewer identity is recorded
|
||||
in the audit event, not on the (now deleted) row. Deleting an approved
|
||||
submission whose build is still running fails that build's context
|
||||
fetch; the admin has explicitly chosen to retire the artifact.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: admin
|
||||
security: [{ accessJWT: [] }]
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: string } }
|
||||
responses:
|
||||
'200':
|
||||
description: The deleted submission, as it was before the deletion.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Submission' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
Reference in new issue
Block a user