Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .agents/skills/verify-openstorage/features/commit-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A user commits files to a branch without git: `POST /api/v1/repos/commit-pack` t
- `cp-readback-git` shows the same commit and bytes in a fresh clone, with the old tip as its parent.
- `cp-pack` stores one new pack file for the repo.
- `cp-no-change` answers `412` `precondition_failed` with `no changes to commit` for a commit that changes nothing, and leaves the tip alone.
- `cp-stale-guard` answers `412` `precondition_failed` for a stale `expected_target_sha`, and leaves the tip alone.
- `cp-stale-guard` answers `409` `ref_guard_failed` for a stale `expected_target_sha`, with `current_sha` = the tip, and leaves the tip alone.
- `cp-new-branch` creates `feature` from `main` with `base_branch` and deletes a path with `operation: "delete"`.
- `cp-unborn` makes the first commit of an empty repo (`result.old_sha` is 40 zeros).

Expand Down Expand Up @@ -93,16 +93,18 @@ env -i PATH="$PATH" "$OSV_BIN" list-packs --repo "$REPO_ID" --data-dir "$OSV_WOR
"$OSV" eq "$RUN" cp-no-change-message 'no changes to commit' "$("$OSV" json "$OSV_EVIDENCE/cp-no-change.body" result.message)"
```

- **Refuse a stale guard.** Send a commit whose `expected_target_sha` is `SEED`. The status is `412`, `result.status` is `precondition_failed`, and the tip is still `NEW`.
- **Refuse a stale guard.** Send a commit whose `expected_target_sha` is `SEED`. The status is `409`, `code` is `ref_guard_failed`, `current_sha` is `NEW`, `result.status` is `conflict`, and the tip is still `NEW`.

```sh
sed "s/@SEED@/$SEED/" >"$OSV_EVIDENCE/cp-stale.ndjson" <<'NDJSON'
{"metadata":{"target_branch":"main","commit_message":"Stale","author":{"name":"Demo","email":"demo@example.com"},"expected_target_sha":"@SEED@","files":[{"path":"stale.txt","content_id":"s","operation":"upsert"}]}}
{"blob_chunk":{"content_id":"s","data":"c3RhbGUK","eof":true}}
NDJSON
"$OSV" api "$RUN" cp-stale --token rw --expect 412 POST /api/v1/repos/commit-pack \
"$OSV" api "$RUN" cp-stale --token rw --expect 409 POST /api/v1/repos/commit-pack \
-H 'Content-Type: application/x-ndjson' --data-binary "@$OSV_EVIDENCE/cp-stale.ndjson"
"$OSV" eq "$RUN" cp-stale-status precondition_failed "$("$OSV" json "$OSV_EVIDENCE/cp-stale.body" result.status)"
"$OSV" eq "$RUN" cp-stale-code ref_guard_failed "$("$OSV" json "$OSV_EVIDENCE/cp-stale.body" code)"
"$OSV" eq "$RUN" cp-stale-current "$NEW" "$("$OSV" json "$OSV_EVIDENCE/cp-stale.body" current_sha)"
"$OSV" eq "$RUN" cp-stale-status conflict "$("$OSV" json "$OSV_EVIDENCE/cp-stale.body" result.status)"
"$OSV" api "$RUN" cp-tip-final --token rw --expect 200 GET '/api/v1/repos/branch?name=main'
"$OSV" eq "$RUN" cp-tip-unchanged "$NEW" "$("$OSV" json "$OSV_EVIDENCE/cp-tip-final.body" branch.head_sha)"
```
Expand Down Expand Up @@ -139,7 +141,7 @@ NDJSON
- `data` is the base64 of the file bytes. `content_id` ties chunks to a `files` entry. An empty file sends one chunk, `{"data":"","eof":true}`. A `delete` sends no chunk.
- `target_branch` is a short name. A `refs/` prefix is refused.
- Write the frames with a quoted heredoc. An unquoted one lets the shell expand `$` inside the JSON.
- A stale `expected_target_sha` (or the deprecated `expected_head_sha`) answers HTTP `412` with `result.status` `precondition_failed`. A `409` with `result.status` `failed` means a lost race with another writer, which this recipe cannot force.
- A stale `expected_target_sha` (or the deprecated `expected_head_sha`) answers HTTP `409` with `code` `ref_guard_failed`, `guard`, `current_sha` and `result.status` `conflict`. A `409` with `result.status` `failed` and no `code` means a lost race with another writer, which this recipe cannot force.
- Auth failures on commit-pack answer in the `result` envelope (`{"result":{"status":"forbidden",...}}`), not in the `{"error":...}` body of read endpoints.
- Without `committer`, the committer is the author.
- `list-packs` reads the blob store only. Run it with `env -i PATH="$PATH"` so an inherited `OPENSTORAGE_S3_URL` does not point it at another store.
7 changes: 5 additions & 2 deletions docs/explanation/domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ Derived from SDK behavior and the real consumer (`just-code-storage`):
- `commit-pack`, `diff-commit`, `restore-commit`: `expected_head_sha` vs current branch tip.
- `merge`: `expected_target_sha` vs target branch tip.
- notes write/delete: `expected_ref_sha` vs notes ref tip.
- On mismatch: fail with HTTP 409/412 and a `result` object
`{success: false, status: "conflict"|"precondition_failed", branch, old_sha, new_sha}`.
- On mismatch: fail with HTTP 409, code `ref_guard_failed`, `guard` and `current_sha`.
The commit writes keep their `result` object
`{target_branch, branch, old_sha, new_sha: "", success: false, status: "conflict"}`
with `commit: null`, and the notes writes keep
the note envelope with `result.status` `conflict`.
Clients (e.g. `just-code-storage` push) map this to git's "failed to push some refs"
UX, so the shape is load-bearing.
2. **Root-commit signaling.** For a first commit on an unborn branch, `result.old_sha` is
Expand Down
8 changes: 6 additions & 2 deletions docs/explanation/streaming-commit-protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ Lines 2..N — one or more blob chunk frames per `upsert` file, in order:
does not exist and `base_branch` is given, branch from that tip; otherwise create a
root commit.
2. CAS check: a stale `expected_target_sha` (or the deprecated `expected_head_sha`) gets
HTTP 412 `precondition_failed` and moves no ref.
HTTP 409 and moves no ref. The body is the failure envelope (`result.status`
`conflict`) plus the problem members `code: ref_guard_failed`, `guard` and
`current_sha`, sent as `application/problem+json`
([details](../reference/http-api.md#commit-write-guards)).
3. Write blobs, build the tree on the base tree (upserts + deletes), create the commit,
and atomically advance the ref (respecting JWT ref policies). If the new tree equals
the base tree (or the empty tree on an unborn branch), the server answers 412
Expand Down Expand Up @@ -158,7 +161,8 @@ When another writer moves the branch after the server wrote the commit (a lost C
`commit-pack` and `diff-commit` answer 409 with `result.status: "failed"`. `commit` and
`result.new_sha` name the commit that the branch did not get, and `result.old_sha` is the
tip the server built on. `restore-commit` answers a lost CAS with 409 `conflict` and no
`commit`.
`commit`. A stale guard also answers 409 on all three routes, and its envelope carries
the problem members of §1 step 2.

The envelope must be returned as the JSON body on any of the statuses
`[400,401,403,404,408,409,412,422,429,499,500,502,503,504]`. Clients parse the body on
Expand Down
Loading
Loading