Spec review fixes: retryability, check order, fetch hardening, vector backfill #5

Open
arne wants to merge 1 commit from spec-review-fixes into main
Owner

Fixes from a full review of SPEC.md. Spec-first per CLAUDE.md: vectors and prose updated together, Go reference implementation and all three example clients match. go test ./..., go vet, Python (20/20 vectors), Node (20/20), bash (9 pass / 11 by-design skips) all green.

Wire-observable changes

  • New error code 502 actor-doc-unavailable for "could not obtain a valid actor doc" (network failure, non-200, oversized, unparseable, §5.2.1 rejection). Retryable; bad-signature now only means crypto failure or an uncanonicalizable sender. Previously a transient fetch failure was reported as bad-signature, which senders classify as permanent — and the Go actor cache had already diverged from spec by returning unknown-key here.
  • 409 duplicate-id is delivery confirmation (§7.6/§8). The receiver only dedups against durably persisted messages, so a 409 proves prior acceptance; the common cause is a retry after a lost 2xx. The outbox now marks these rows delivered instead of failed-permanent. §8 gains a "Sender action" column.
  • §7 reordered: the timestamp window (now §7.3) runs before key resolution (§7.4) and signature verification (§7.5), so stale envelopes are rejected without network I/O or crypto. Vector 15 pins the precedence.
  • Missing/repeated/undecodable Posta-Signature specced as 400 malformed-envelope at §7.1 step 3 (what the handler already did; now normative with vectors).
  • Redirects forbidden both directions: 3xx on actor-doc GET is a fetch failure, 3xx on POST is a permanent delivery failure. Both Go HTTP paths use no-redirect clients.
  • All length limits are bytes (§3), inReplyTo capped at 256, over-limit fields are malformed-envelope, raw non-ASCII/space path bytes are malformed-path (url-canonical vectors 26–27).
  • 401 responses carry WWW-Authenticate: Posta per RFC 9110 §15.5.2.

Spec-text fixes

  • §5.3 caps actor-doc Cache-Control: max-age at the timestamp window. This was a real contradiction: §9 promised key removal takes effect within the window, but a CDN holding the doc longer would keep serving the removed key. 304 revalidation now explicitly resets the freshness clock.
  • §12 additions: SSRF on actor-doc fetches (implemented in the daemon: connect-time non-public-address check, AllowPrivateNetworks override), fetch amplification + negative caching (now a spec-level SHOULD in §7.4), duplicate-JSON-key parser divergence, and the transferable-authorship / no-forward-secrecy story summarized from CONTEXT.md.
  • Display-field limits bind the publisher; consumers truncate instead of rejecting (kind-length rejection removed from DecodeActorDoc, per the render-only rule). Duplicate key ids in a doc are a MUST-reject. Avatars must be https and inherit the §13.3.3 fetch policy.
  • §14.2 notes inner verification fails for backfilled broadcasts once the author rotates the signing key away.
  • §6.2.2 documents the inReplyTo cross-sender collision ambiguity; §11 uses KiB consistently; §5.1 specifies 405 for undefined methods.

Vectors and tooling

  • Eight new envelope vectors (13–20): every §8 error code is now exercised — missing/garbage signature header, check-order precedence, payload-too-large, duplicate-id, unknown-key, actor-doc url mismatch, malformed timestamp.
  • Vector schema gains actorDoc (§7.4 key resolution) and alreadyAccepted (§7.6 replay), documented in §15. Go/Python/JS harnesses run the full §7 sequence in spec order.
  • gen-vectors now also emits the previously hand-maintained 08/09 (byte-identical), so regeneration produces the complete set.

Two judgment calls to review: the outbox now treats 429 as retryable (same bug family as the 409 misclassification, though not strictly in the review findings), and the example-size canary moved from ~150 to ~200 lines since Python landed at 166 implementing genuinely more of §7.

🤖 Generated with Claude Code

Fixes from a full review of SPEC.md. Spec-first per CLAUDE.md: vectors and prose updated together, Go reference implementation and all three example clients match. `go test ./...`, `go vet`, Python (20/20 vectors), Node (20/20), bash (9 pass / 11 by-design skips) all green. ## Wire-observable changes - **New error code `502 actor-doc-unavailable`** for "could not obtain a valid actor doc" (network failure, non-200, oversized, unparseable, §5.2.1 rejection). Retryable; `bad-signature` now only means crypto failure or an uncanonicalizable `sender`. Previously a transient fetch failure was reported as `bad-signature`, which senders classify as permanent — and the Go actor cache had already diverged from spec by returning `unknown-key` here. - **`409 duplicate-id` is delivery confirmation** (§7.6/§8). The receiver only dedups against durably persisted messages, so a 409 proves prior acceptance; the common cause is a retry after a lost 2xx. The outbox now marks these rows `delivered` instead of `failed-permanent`. §8 gains a "Sender action" column. - **§7 reordered**: the timestamp window (now §7.3) runs before key resolution (§7.4) and signature verification (§7.5), so stale envelopes are rejected without network I/O or crypto. Vector 15 pins the precedence. - **Missing/repeated/undecodable `Posta-Signature`** specced as `400 malformed-envelope` at §7.1 step 3 (what the handler already did; now normative with vectors). - **Redirects forbidden both directions**: 3xx on actor-doc GET is a fetch failure, 3xx on POST is a permanent delivery failure. Both Go HTTP paths use no-redirect clients. - **All length limits are bytes** (§3), `inReplyTo` capped at 256, over-limit fields are `malformed-envelope`, raw non-ASCII/space path bytes are `malformed-path` (url-canonical vectors 26–27). - **401 responses carry `WWW-Authenticate: Posta`** per RFC 9110 §15.5.2. ## Spec-text fixes - **§5.3 caps actor-doc `Cache-Control: max-age` at the timestamp window.** This was a real contradiction: §9 promised key removal takes effect within the window, but a CDN holding the doc longer would keep serving the removed key. 304 revalidation now explicitly resets the freshness clock. - **§12 additions**: SSRF on actor-doc fetches (implemented in the daemon: connect-time non-public-address check, `AllowPrivateNetworks` override), fetch amplification + negative caching (now a spec-level SHOULD in §7.4), duplicate-JSON-key parser divergence, and the transferable-authorship / no-forward-secrecy story summarized from CONTEXT.md. - **Display-field limits bind the publisher**; consumers truncate instead of rejecting (kind-length rejection removed from `DecodeActorDoc`, per the render-only rule). Duplicate key `id`s in a doc are a MUST-reject. Avatars must be `https` and inherit the §13.3.3 fetch policy. - **§14.2** notes inner verification fails for backfilled broadcasts once the author rotates the signing key away. - §6.2.2 documents the `inReplyTo` cross-sender collision ambiguity; §11 uses KiB consistently; §5.1 specifies 405 for undefined methods. ## Vectors and tooling - Eight new envelope vectors (13–20): every §8 error code is now exercised — missing/garbage signature header, check-order precedence, `payload-too-large`, `duplicate-id`, `unknown-key`, actor-doc url mismatch, malformed timestamp. - Vector schema gains `actorDoc` (§7.4 key resolution) and `alreadyAccepted` (§7.6 replay), documented in §15. Go/Python/JS harnesses run the full §7 sequence in spec order. - `gen-vectors` now also emits the previously hand-maintained 08/09 (byte-identical), so regeneration produces the complete set. Two judgment calls to review: the outbox now treats 429 as retryable (same bug family as the 409 misclassification, though not strictly in the review findings), and the example-size canary moved from ~150 to ~200 lines since Python landed at 166 implementing genuinely more of §7. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Wire-observable changes, vectors first per repo discipline:

- New error code 502 actor-doc-unavailable for "could not obtain a valid
  actor doc" (network failure, non-200, oversized, unparseable, §5.2.1
  rejection). Retryable; bad-signature now only means crypto failure or
  an uncanonicalizable sender. Resolves an existing spec/code divergence
  where the actor cache returned unknown-key for fetch failures.
- 409 duplicate-id is defined as delivery confirmation (§7.6/§8); the
  outbox marks it delivered instead of failed-permanent. §8 gains a
  sender-action column.
- §7 reordered: timestamp window (now §7.3) runs before key resolution
  (§7.4) and signature (§7.5), so stale envelopes are rejected without
  network I/O or crypto. Vector 15 pins the precedence.
- Missing/repeated/undecodable Posta-Signature header specced as 400
  malformed-envelope at §7.1 step 3 (matching the handler).
- Redirects forbidden both directions: 3xx on actor-doc GET is a fetch
  failure, 3xx on POST is a permanent delivery failure. Both Go HTTP
  paths use no-redirect clients.
- All length limits are bytes; inReplyTo capped at 256; over-limit
  fields are malformed-envelope; raw non-ASCII/space path bytes are
  malformed-path (url-canonical vectors 26-27).
- 401 responses carry WWW-Authenticate: Posta per RFC 9110.

Spec-text fixes:

- §5.3 caps actor-doc Cache-Control max-age at the timestamp window;
  a longer upstream TTL silently voided the §9 key-removal guarantee.
  304 revalidation resets the freshness clock.
- §12: SSRF guard on actor-doc fetches (implemented in the daemon with
  a connect-time non-public-address check and AllowPrivateNetworks
  override), fetch amplification + negative caching, duplicate JSON
  key parser divergence, transferable authorship / no forward secrecy.
- Display-field limits bind the publisher; consumers truncate instead
  of rejecting (kind-length rejection removed from DecodeActorDoc).
  Duplicate key ids in a doc are a MUST-reject. Avatars must be https
  and inherit the §13.3.3 fetch policy.
- §14.2 notes inner verification fails for backfilled broadcasts after
  the author rotates the signing key away.

Vectors and tooling:

- Eight new envelope vectors (13-20) cover every §8 error code:
  missing/garbage signature header, check-order precedence,
  payload-too-large, duplicate-id, unknown-key, actor-doc url
  mismatch, malformed timestamp.
- Vector schema gains actorDoc and alreadyAccepted (documented in
  §15); Go/Python/JS harnesses run the full §7 sequence in spec order.
- gen-vectors now also emits the previously hand-maintained 08/09
  (byte-identical), so regeneration produces the complete set.
- Outbox additionally treats 429 as retryable (same bug family as the
  409 misclassification); example-size canary bumped to ~200 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin spec-review-fixes:spec-review-fixes
git switch spec-review-fixes

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff spec-review-fixes
git switch spec-review-fixes
git rebase main
git switch main
git merge --ff-only spec-review-fixes
git switch spec-review-fixes
git rebase main
git switch main
git merge --no-ff spec-review-fixes
git switch main
git merge --squash spec-review-fixes
git switch main
git merge --ff-only spec-review-fixes
git switch main
git merge spec-review-fixes
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
posta/spec!5
No description provided.