Skip to content

Responses grow server-first: align rules 1 and 6 with already_present - #6

Merged
constantfold merged 1 commit into
mainfrom
responses-grow-server-first
Sep 16, 2026
Merged

constantfold merged 1 commit into
mainfrom
responses-grow-server-first

Conversation

@jakozaur

@jakozaur jakozaur commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Upstream: QuesmaOrg/trajectories-research#333, the protocol-text half. The client half is QuesmaOrg/quesma-shipper#27.

What changes

Docs only. No schema or fixture bytes change, and go test ./... passes.

  • Compatibility rule 1 becomes "responses grow server-first, requests client-first". The shipper ignores response fields it does not know; a new required header name or provider dialect is still refused, so those ship client-first as before.
  • Rule 6 becomes "V2 upload requests are strict, responses are tolerant".
  • The /v2/uploads/authorize response description and the rules intro say the same thing in one sentence each.
  • The "no upload status" limitation names already_present as its single exception.
  • CHANGELOG gets a Changed entry under Unreleased.

Why

The v0.1.0 schema already defines the already_present ticket, but the published rules 1 and 6 still say the client rejects unknown response fields. quesma-shipper#27 makes the client tolerant, so once it merges the client contradicts the published text. Upstream fixed the wording in the same PR that added the field; that edit never reached this repo.

Example

A server may now add a field to a ticket, say "region": "eu-west-1", before the fleet updates. An old shipper on v0.1.0 semantics would fail every authorize call; a shipper on these semantics ignores the field and still validates the URL, key, headers and length before sending a byte.

Release

None needed. Documentation only; consumers keep v0.1.0 and no fixture or schema moves.

🤖 Generated with Claude Code

…sent answer

The schema has carried the already_present ticket since v0.1.0, yet rules 1 and 6 still
required the shipper to reject unknown response fields. Port the wording from
trajectories-research #333: the server decodes requests strictly, the client ignores response
fields it does not know and validates every field it acts on. Documentation only.
@jakozaur
jakozaur force-pushed the responses-grow-server-first branch from 6baabf1 to 6f4015f Compare September 16, 2026 14:50
@jakozaur
jakozaur marked this pull request as ready for review September 16, 2026 14:52
@constantfold
constantfold force-pushed the responses-grow-server-first branch from 15aebc7 to 6f4015f Compare September 16, 2026 15:04
@constantfold
constantfold merged commit d3d7f9b into main Sep 16, 2026
5 checks passed
@constantfold
constantfold deleted the responses-grow-server-first branch September 16, 2026 15:05
mieciu pushed a commit to QuesmaOrg/quesma-shipper that referenced this pull request Sep 21, 2026
…he archive (#27)

Upstream:
[QuesmaOrg/trajectories-research#333](QuesmaOrg/trajectories-research#333)

Port of trajectories-research #333, the first of four upstream PRs that
were merged on 2026-09-02 and 2026-09-03 but missed the public import
(the snapshot was cut between #339 and #333). Stack, in upstream merge
order: this PR, then #28 (#321), #29 (#318), #30 (#340).

## What changes

- Any `fingerprints.json` that cannot be loaded (parse, schema,
checksum, another install, oversize, unreadable) is discarded with one
warning and the run continues from an empty store. The first flush
replaces the file. Before, the daemon refused to run and pointed at
`state prune` or `state reset`.
- Every authorize request is answered against the archive: when the
control plane reports `already_present: true` for a stored source hash,
the shipper skips the PUT. A lost store now costs a re-hash, not a
re-upload.
- The authorize response is no longer strict-decoded, so it can grow
server-first. The closed required-header set is unchanged.

## Example

An install whose state file was truncated by a full disk:

```
state: fingerprints.json unloadable (parse: unexpected end of JSON input); starting from an empty store
```

The next flush authorizes every object; the control plane answers
`already_present` for the ones the archive holds under the same hash,
and only the new ones are uploaded.

## Port notes

- The wire-side `AlreadyPresent` ticket field already came in with the
shipper-protocol module, so this PR adds only the client behaviour
around it.
- Needs the control plane to answer `already_present`; without it the
client still works, it just uploads everything as before.

## Compatibility

Server side: the control plane and the fleet manager both gained the
`already_present` answer in the same upstream PR, and that code is on
trajectories-research main. The fleet manager HEADs each key and answers
`already_present` only when the stored `source-hash` metadata matches; a
failed HEAD logs once and authorizes the object as new. HeadObject is
covered by the existing `s3:GetObject` grant. Whether the deployed
dogfooding instances run a build with it is not verified here.

Client matrix:

| client | server | result |
|---|---|---|
| main today | answers `already_present` | works: main already decodes
that shape and skips the PUT, but does not mark the audit line |
| this PR | never answers `already_present` | works: full tickets,
unchanged path |
| main today | adds any other new response field | every authorize
fails: main decodes with unknown fields disallowed |
| this PR | adds any other new response field | works: unknown fields
ignored, every field acted on is still validated |

Protocol text gap: the public shipper-protocol v0.1.0 `PROTOCOL.md`
still states rule 1, "the client decodes every response with unknown
fields disallowed", and rule 6, "V2 upload messages are strict both
ways", while its schema already carries the `already_present` ticket.
Upstream #333 amended both rules to "responses grow server-first,
requests client-first"; that amendment is ported in
[QuesmaOrg/shipper-protocol#6](QuesmaOrg/shipper-protocol#6),
a docs-only change with no release needed.

## Verification

`make check` (fmt, vet, deadcode, licenses, race) passes on this branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants