Commit Graph

3 Commits

Author SHA1 Message Date
xarmian 4f277f836a fix(apps): an app upload is charged against the plan's storage cap (BUG-3653 part B) (#2119)
* fix(apps): an app upload is charged against the plan's storage cap (BUG-3653)

App uploads were capped only per install (2,000 files, 1 GiB), so on
Pad Cloud an app could add bytes past the workspace owner's storage_bytes
limit that a human upload would be refused for.

- FenceSpec.StorageLimit makes BeginFenced take the owner's storage
  plan-limit lock before its install read, as the lock's ordering rule
  requires. FencedTx.CheckStorageLimit charges bytes under it and returns
  ErrAppStorageLimit, which carries no totals, like the item cap.
- Admission checks the cap at the declared size before any byte is read,
  so the common refusal writes no blob. The row insert charges it
  authoritatively, and an owner change mid-check is retried
  (ErrStorageOwnerMoved, now exported for this caller).
- The server answers 403 storage_limit_reached.
- appstore sets the flag from Options.PlanLimit, so self-hosted is unchanged.
- The boundary allow-list takes the read-only storage helpers and the
  advisory lock, each with what it writes.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* docs(apps): the fence's lock-order comments name the storage lock an app upload takes first (BUG-3653)

Codex round 1: two comments said the install FOR SHARE (fenced_tx.go) and
the seq lock (fenced_attachments.go) come first, which a StorageLimit
spec now precedes.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(apps): an app upload whose row never commits reclaims its blob (BUG-3652, BUG-3653)

With #2118 merged, the app door gets the same reclaim as the human
doors. PendingUpload.Close, when the row never committed (its insert was
refused, as by the storage cap after an admission another upload raced,
or the caller's re-check refused it before Insert), hands the canonical
blob to Options.Reclaimer while the hash guard is still held. The server
implements it with reclaimRefusedBlob on the FS backend, which keeps the
blob when another row or writer shares the hash.

Options.Reclaimer is an interface, not a func field: the appstore
boundary guard refuses function values in app code outright (rule 9),
and refuses interface calls only where an internal/store type could
answer them (rule 4). The server's implementation reads row existence
for the hash and deletes a blob file; it writes no row.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

* fix(apps): a pending upload attempts its blob reclaim once, cleared before the call (BUG-3652)

Codex round: the storage key was cleared after the reclaimer returned.
It is now captured and cleared first, so a second Close can never retry
the reclaim after the hash guard was released, whatever the first
attempt did.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
2026-10-13 15:44:44 -04:00
xarmian b0dc7d51e5 feat(apps): the app API admits a person's delegated token (TASK-3399, SPEC-6 U5b-2) (#1782)
* feat(apps): the app API admits a person's delegated token (fresh person, membership and credential checks, the lesser of consented and offered access), attributes their writes to them, and opens delegated sign-in (TASK-3399, U5b-2)

* test(apps): the app API for a person: identity, the lesser access, per-request person checks, the person's visibility, and the fresh-context re-check; the delegated author is the person (TASK-3399, U5b-2)

* test(apps): a delegated upload re-admits the person before its row, and records them as the uploader (TASK-3399, U5b-2)

* fix(apps): U5b-2 codex r1: every app write re-admits its actor after reading its body and before its write (one helper the upload shares); a move during the body refuses the write (TASK-3399, U5b-2)

* fix(apps): U5b-2 codex r2: the app item list windows over rows the actor may see, filtered in SQL like the human list, so has_more and next_offset count nothing hidden (TASK-3399, U5b-2)

* fix(apps): U5b-2 codex r3: re-admission's final step re-checks the workspace and the actor's role, not only the credential (TASK-3399, U5b-2)

* test(apps): a delegated app write's event names the install (U10d) and credits the person, never the bot (TASK-3399, U5b-2 x U10d)
2026-10-04 22:57:52 -04:00
xarmian 37066f97be feat(apps): app attachment routes: item-scoped upload, metadata, and a streamed download re-admitted before its first byte (TASK-3401, SPEC-6 U6c) (#1776)
* feat(apps): app attachment routes: item-scoped upload, metadata and a streamed download re-admitted before its first byte (TASK-3401, SPEC-6 U6c)

* test(server): an app attachment read refuses a subject whose membership no longer reaches the item (TASK-3401, U6c)

* test(server): U6c tests reach the item re-check, the upload's edit right and the download's type decision (TASK-3401, U6c)

* test(server): a deletion before the first byte reaches the attachment re-check (TASK-3401, U6c)

* fix(apps): U6c codex r1: authorize before storage, a transport write deadline, the credential last in re-admission, and a live companion check in the fence (TASK-3401, U6c)

* fix(apps): U6c codex r2: a truncated upload is 400, a re-admission fault is 500 not 401, HEAD on the download, and unknown variants refused (TASK-3401, U6c)

* fix(apps): U6c codex r3: the upload re-admits the subject before its row (StageUpload/Insert, no store callback), and variant is null on originals (TASK-3401, U6c)

* fix(apps): U6c codex r4: a token-state or settings read failure during admission is a fault (500), not a denial (TASK-3401, U6c)

* fix(apps): U6c codex r5: a credential that dies mid-upload answers 401 invalid_token, a permission change 403 (TASK-3401, U6c)

* fix(apps): U6c codex r6: the served variant is re-checked at the gate, and a settings read fault at the first gate is a 500 (TASK-3401, U6c)

* test(server): a viewer's upload is refused before its body is read (TASK-3401, U6c)

* test(server): account for the app upload's raw body reader in the body-reader census (TASK-3401, U6c)
2026-10-04 15:14:27 -04:00