5 Commits
Author SHA1 Message Date
dtourolle 7eb5c175af docs: tag the untagged verifications, and record the UR-007 conflict
CI / fmt, clippy, test (pull_request) Failing after 54s
CI / static musl binary (pull_request) Skipped
CI / advisories and licences (pull_request) Successful in 39s
Five tests and the schema constant implemented requirements without
carrying a tag, so those requirements read as uncovered when they were
not. No behaviour changes here — every edit is a comment.

Also documents `static` as a real tier rather than an exemption: it is
already in `ci_executable_tiers` and its checks run in CI, and it exists
because DR-007's single binary and DR-012's licence policy are properties
of the build that a unit test could only assert as theatre.

The UR-007 note records a cross-repo status conflict rather than
resolving it. `jRay` JR-025 is `Done` and claims UR-007, but the fetch
path that would exercise it (`jRay` JR-031) is still `Planned`. Either
JR-025 is scoped to selection alone or it over-claims; until that is
settled neither register should be trusted for UR-007 coverage.

The gate reports 0 orphan tags and 19/19 UR coverage.

TRACES: DR-001, DR-014, UR-015, UR-016, UR-018 | SR-003, SR-004
2026-07-31 16:26:10 +02:00
dtourolleandClaude Opus 5 4afa36e7a2 feat(licence): contributed manifests are CC0 1.0
Contributed manifests were in no declared condition at all, which left §9a
replication with no grant flowing through it: peers mirror each other's
catalogues wholesale, and every hop of that was unlicensed.

CC0 rather than a share-alike licence, because a share-alike works by asserting
a right in the data and then conditioning its use. The position in
docs/legal-posture.md §3 is that presence timings are facts rather than
protectable expression — asserting copyright in them in order to license them
would contradict that argument in the same repository, and that contradiction is
worth more to an opponent than the licence is worth to us. CC0 also waives the
sui generis database right by name, closing the EU-specific residual exposure
from the contributor's side.

The grant is taken at token issuance, and that is not incidental. There are no
accounts, so there is no sign-up to attach terms to, and a manifest arrives over
POST /manifests with no channel to negotiate over. Acquiring the contribute
capability is the only moment a grant can be made, so POST /tokens now returns
the licence and its terms alongside the token — a licence the server publishes
but never delivers is one no contributor agreed to.

The test pins the scope limit as well as the identifier. Bounding the grant to
the manifest is the half that can fail silently: a reworded term reading onto
the underlying work would purport to grant what no contributor can.

Also records the settled code-licence position across all four repositories in
the legal posture, correcting an earlier claim there that the plugin and
extraction repos declared nothing. Both already carried LICENSE files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: UR-019 | PR-006
2026-07-31 10:43:43 +02:00
dtourolleandClaude Opus 5 545c7d92a2 Federation: replicate content, re-derive judgement (UR-008)
CI / fmt, clippy, test (push) Failing after 1m20s
CI / static musl binary (push) Has been skipped
CI / advisories and licences (push) Successful in 25s
Implements §9a. The replication surface is four reads and no writes: a change
feed, fetch by content_id, a batch have, and a human-facing peer directory —
plus a capabilities endpoint carrying the accepted envelope versions, which
lets a client discover a schema mismatch in one request instead of a 400 per
manifest across a library sweep.

Pull, never push: a pulling server chooses what it ingests and when. Push would
let any peer inject work into the validation queue — the same abuse surface as
anonymous upload, at higher volume.

Nothing inherits a peer's judgement. A pulled manifest runs the full §6 stage 1
and 2 validation and this server's own cast check, and the fetched body must
hash to the content_id that was asked for — the check that stops an
intermediary or a misbehaving peer substituting content under a trusted id.
A peer's retraction flags for review rather than delisting, because
auto-delisting would hand every peer a remote delete primitive; only the opt-in
per-peer abuse channel delists, because a takedown propagating at the speed of
manual review is the wrong failure mode for that one case.

A test caught a real bug in the first cut: the feed cursor was a ULID, and
ULIDs are only monotonic *between* milliseconds — two generated in the same
millisecond carry independent random components and can sort opposite to write
order. A peer resuming from `seq > cursor` would then silently skip an entry:
replication losing manifests with no error anywhere. The cursor is now an
AUTOINCREMENT integer, and the test asserts strict monotonicity rather than
merely sortedness.

Peer administration is deliberately not an API. §9a requires that a peering
exist only because an operator typed a URL, so nothing a remote server returns
can establish or widen one; there_is_no_endpoint_that_creates_a_peering asserts
that absence rather than trusting it.

212 tests. Coverage 25/32 (78%).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: UR-008 | PR-006
2026-07-31 09:28:32 +02:00
dtourolleandClaude Opus 5 88c7264094 Ship the SR-003 schema bump: jmanifest_version 2
CI / fmt, clippy, test (push) Failing after 1m22s
CI / static musl binary (push) Has been skipped
CI / advisories and licences (push) Successful in 26s
Moves the exchange envelope to version 2 in lockstep with the truth file's
schema_version, per SR-003's requirement that breaking changes be batched and
ship together rather than piecemeal. The plugin had already moved to
schema_version 2; the server declaring 1 while accepting the new fields
defeated the point of having a version at all.

Flag day, not dual-accept (JR-003): version 1 is now rejected outright. All
three components are pre-release, and a v1 read path would be the one nobody
exercises, so it is the one that would rot while being dragged through every
later change to the reader. A pipeline still emitting v1 is incompatible until
updated — stated plainly rather than papered over with a shim nobody tests.

scenes become objects carrying belief and route (extraction AR-017) instead of
float pairs. Belief is bounded to [0, 1] rather than merely stored: §5a's
Threat 1 argument rests on every accepted value being bounded, and an unbounded
float is a 64-bit channel however harmless it looks. route is a closed enum, so
an invented value cannot be stored.

UR-018 is the requirement with the trap in it, and the reason content_id.rs is
untouched by this commit: belief is a producer-side estimate that may
legitimately differ between pipeline versions for identical timings, so
including it in the canonical form would give two servers different ids for the
same content — the exact failure mode §9a quantises centiseconds to avoid,
reintroduced one field along. It replicates as an attribute, exactly as
audio_signature does. The golden vector still passes unchanged, which is the
evidence rather than the claim.

191 tests. UR-015..018 move from Planned to Done; coverage 24/32 (75%).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

TRACES: UR-014, UR-015, UR-016, UR-017, UR-018 | SR-003
2026-07-31 09:13:14 +02:00
dtourolleandClaude Opus 5 a848750a65 Initial implementation: core vertical slice
CI / fmt, clippy, test (push) Failing after 2m46s
CI / static musl binary (push) Has been skipped
CI / advisories and licences (push) Successful in 4m22s
Implements the core of SPEC.md — the manifest exchange, less audio-tier
matching (§3) and federation (§9a), both of which the spec sequences as
later work.

- §2 Jmanifest format and series bundles
- §3 cut matching: exact / runtime / loose tiers
- §4 API, less POST /manifests/search
- §5 rate limiting; §5a trust model, anonymous bearer tokens
- §6 upload validation, all four stages
- §7 relational storage, no JSON blob on the write path
- §8 Rust + Axum + SQLite, single serialized writer, in-process job queue
- §9a content addressing, computed on upload

Reconciled against the system spec:

- anneal_sec removed, withdrawn upstream by AR-012/AR-013. Presence follows
  track extent, so a track survives its own gaps and there is nothing to
  anneal. Its successor extinction_sec and the new gallery_scope are accepted
  and stored; scope enters the §7 ranking. A manifest still carrying
  anneal_sec is a hard 400, not silently ignored — it came from a pipeline
  whose window semantics differ from what this server assumes.
- Audio signature: media under 120 s now emits no signature at all, matching
  scene-actor-extraction IR-007. The earlier §3 draft allowed a shortened
  window under 150 s, which was the weaker rule — a caller-varying length is
  the property SR-004 forbids.
- UR IDs regularised to UR-nnn; docs/requirements.md registers 32
  requirements, each tracing to an SR-nnn or PR-nnn.

189 tests: unit, end-to-end through the real router, and an injection suite
covering SQL, JSON, header and Unicode payloads. Writing that suite found two
real gaps, both fixed here: compatibility homoglyphs passed the §5a character
class, and a one-frame audio signature was accepted on a feature-length item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:14:02 +02:00