feat(spec-039): Webmention support — recv + send + discovery + idempotency #41

Open
anuna-02 wants to merge 4 commits from hence/plan-IMPL-039 into main
anuna-02 commented 2026-05-09 14:04:42 +00:00 (Migrated from codeberg.org)

Summary

Implements all four flows of SPEC-039 (Webmention) as a sibling to SPEC-038 RSS — pragmatic v1 executed without running DESIGN-039's Tier 2 design phase. Plan: plans/IMPL-039-webmention.spl.

SPEC-039 stays at status: strawman. The strawman explicitly says "Do not implement against this version." This PR ships against it anyway with default-leaning answers for the six deferred ADRs (flow coverage = all four; static receive = recipe; moderation = hybrid defeasible-rule; storage = central JSONL; identity = URL-only; idempotency = persisted JSONL log). DESIGN-039 surveys / cross-model review / threat model formalisation are still open work — they should run in a follow-up cycle to graduate the spec.

What's wired

  • ReceivePOST /webmention on zetl serve (gated on [webmention].enabled). Source-fetch reuses SPEC-038's SSRF-safe transport; pure-core verification (REQ-3903) and hybrid moderation (REQ-3905). Persists accept → received.jsonl, queue → queue.jsonl.
  • Sendzetl build walks dist/, extracts external links, computes the idempotency diff against sent.jsonl, discovers each target's endpoint, and POSTs new + changed pairs. Removed-link re-POST per REQ-3907.
  • Discovery<link rel="webmention" href="..."> injected via vault.webmention_endpoint in default / docs / minimal / fountain themes.
  • CLIzetl webmention {list,accept,reject,status} with --json output.
  • Storage — central JSONL at .zetl/webmentions/{received,sent,queue}.jsonl; tombstones are append records.
  • Oracle resistance (REQ-3909) — same response shape for public / private / capability / non-existent targets; no 404 leakage; capability tokens never echoed.
  • Threat model — T1 SSRF, T2 rate-limit (60/min per host, 1000/min global), T3 size cap (1 MiB inherited), T7 replay (dedup key), T8/T9 oracle resistance.

Non-additive changes

  • ureq promoted from semantic feature to default deps (the receive + send transports use it).
  • New field vault.webmention_endpoint: Option<String> on VaultContext.
  • Templates in all four built-in themes pick up a guarded <link rel="webmention"> line.

Test plan

  • Unit tests for pure core (verify, extract, discover, diff, moderate) — 75 in cargo test --lib webmention
  • Integration tests — tests/webmention_integration.rs covers HP-3901/3905/3906/3908, T1/T8 probes, replay (T7), idempotency zero-POST property, link-removal re-POST (HP-3904)
  • Full suite green: 2971 passed
  • Feature builds: --features reason, --features history, --all-features clean
  • cargo fmt clean, cargo clippy --lib --tests clean on webmention modules
  • Operator validation — needs a real-vault [webmention] config + a counterparty to exchange POSTs with (webmention.rocks fixture run is documented as v2 work)
  • Build-mode send hook is unconditional when [webmention].send_enabled = true; verify on staging vault before flipping the flag in production

Follow-ups (filed as comments, not blockers)

  • DESIGN-039's research / synthetic-user / cross-model review tasks remain open work
  • webmention.rocks conformance corpus mapping (NFR-3905)
  • h-card identity extraction (ADR-3905 → v2)
  • Static-build receive recipe documentation (ADR-3902 → docs PR)
  • zetl webmention send <page> manual escape-hatch (currently stubbed)
  • SPL fact emission on accept/reject so zetl reason explain accept-mention X Y works (REQ-3914)

🤖 Generated with Claude Code

## Summary Implements all four flows of SPEC-039 (Webmention) as a sibling to SPEC-038 RSS — pragmatic v1 executed without running DESIGN-039's Tier 2 design phase. Plan: `plans/IMPL-039-webmention.spl`. **SPEC-039 stays at `status: strawman`.** The strawman explicitly says "Do not implement against this version." This PR ships against it anyway with default-leaning answers for the six deferred ADRs (flow coverage = all four; static receive = recipe; moderation = hybrid defeasible-rule; storage = central JSONL; identity = URL-only; idempotency = persisted JSONL log). DESIGN-039 surveys / cross-model review / threat model formalisation are still open work — they should run in a follow-up cycle to graduate the spec. ### What's wired - **Receive** — `POST /webmention` on `zetl serve` (gated on `[webmention].enabled`). Source-fetch reuses SPEC-038's SSRF-safe transport; pure-core verification (REQ-3903) and hybrid moderation (REQ-3905). Persists accept → `received.jsonl`, queue → `queue.jsonl`. - **Send** — `zetl build` walks `dist/`, extracts external links, computes the idempotency diff against `sent.jsonl`, discovers each target's endpoint, and POSTs new + changed pairs. Removed-link re-POST per REQ-3907. - **Discovery** — `<link rel="webmention" href="...">` injected via `vault.webmention_endpoint` in default / docs / minimal / fountain themes. - **CLI** — `zetl webmention {list,accept,reject,status}` with `--json` output. - **Storage** — central JSONL at `.zetl/webmentions/{received,sent,queue}.jsonl`; tombstones are append records. - **Oracle resistance (REQ-3909)** — same response shape for public / private / capability / non-existent targets; no 404 leakage; capability tokens never echoed. - **Threat model** — T1 SSRF, T2 rate-limit (60/min per host, 1000/min global), T3 size cap (1 MiB inherited), T7 replay (dedup key), T8/T9 oracle resistance. ### Non-additive changes - `ureq` promoted from `semantic` feature to default deps (the receive + send transports use it). - New field `vault.webmention_endpoint: Option<String>` on `VaultContext`. - Templates in all four built-in themes pick up a guarded `<link rel="webmention">` line. ### Test plan - [x] Unit tests for pure core (verify, extract, discover, diff, moderate) — 75 in `cargo test --lib webmention` - [x] Integration tests — `tests/webmention_integration.rs` covers HP-3901/3905/3906/3908, T1/T8 probes, replay (T7), idempotency zero-POST property, link-removal re-POST (HP-3904) - [x] Full suite green: 2971 passed - [x] Feature builds: `--features reason`, `--features history`, `--all-features` clean - [x] `cargo fmt` clean, `cargo clippy --lib --tests` clean on webmention modules - [ ] **Operator validation** — needs a real-vault `[webmention]` config + a counterparty to exchange POSTs with (webmention.rocks fixture run is documented as v2 work) - [ ] Build-mode send hook is unconditional when `[webmention].send_enabled = true`; verify on staging vault before flipping the flag in production ### Follow-ups (filed as comments, not blockers) - DESIGN-039's research / synthetic-user / cross-model review tasks remain open work - webmention.rocks conformance corpus mapping (NFR-3905) - h-card identity extraction (ADR-3905 → v2) - Static-build receive recipe documentation (ADR-3902 → docs PR) - `zetl webmention send <page>` manual escape-hatch (currently stubbed) - SPL fact emission on accept/reject so `zetl reason explain accept-mention X Y` works (REQ-3914) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This pull request has changes conflicting with the target branch.
  • src/web/context.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin hence/plan-IMPL-039:hence/plan-IMPL-039
git switch hence/plan-IMPL-039

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 hence/plan-IMPL-039
git switch hence/plan-IMPL-039
git rebase main
git switch main
git merge --ff-only hence/plan-IMPL-039
git switch hence/plan-IMPL-039
git rebase main
git switch main
git merge --no-ff hence/plan-IMPL-039
git switch main
git merge --squash hence/plan-IMPL-039
git switch main
git merge --ff-only hence/plan-IMPL-039
git switch main
git merge hence/plan-IMPL-039
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
anuna-research/zetl!41
No description provided.