Development Log: Rust Registry v1, Hybrid Viewer Samples, and Watermark Round Trips

This is a development log, not a release announcement. The work is sitting in three stacked pull requests, and I want the public record to say exactly what moved forward before it gets flattened into a changelog line. The short version is that the Rust implementation is now much closer to being a credible protocol path instead of a parallel experiment: the Rust Axum registry passes the existing v1 conformance harness, the browser inspector has public hybrid-decrypt sample tooling, and the Rust workspace tests are green after fixing two format watermark regressions.

The three PRs are deliberately stacked. The registry compatibility work is first, the hybrid viewer docs and sample tooling sit on top of that, and the watermark round-trip fixes sit on top of both. That shape keeps each reviewable piece narrow while still telling the real story: registry interoperability, browser inspection, and watermark correctness all have to line up before Oversight can claim a Rust-first verification path with a straight face.

The Rust registry now speaks the v1 read surface

The biggest change is in PR #5: the Rust registry now implements the missing read-only and beacon-facing pieces of the registry v1 surface. The Axum server exposes /.well-known/oversight-registry, signed evidence bundles, transparency-log head/proof/range reads, beacon callback endpoints, and semantic candidate listing. The browser-facing CORS policy is constrained to the public viewer/site origins and to GET/OPTIONS, which is the right shape for an inspector that needs to read evidence without turning registry mutation routes into browser APIs.

The interop bug in this layer was canonicalization. Python-signed manifests already carry fields such as canonical_content_hash and l3_policy. The Rust manifest model did not include those fields, so a Rust verifier could deserialize a Python manifest, drop signed fields, re-canonicalize a different object, and then reject the signature. That is exactly the kind of cross-language bug a protocol implementation has to catch early. The Rust manifest model now carries those fields, and it keeps a legacy fallback path for older manifests that predate them.

The concrete validation result is the important part: a live Rust registry server passed the existing registry v1 conformance harness with 33 checks passing and 0 failing. The harness is still the acceptance gate. A spec is useful, but a spec plus a test that another implementation can run is the thing that makes federation less aspirational.

The browser inspector has public hybrid samples

The next layer is PR #6. The browser inspector already matters because it gives recipients a way to inspect sealed artifacts without installing the full sender toolchain. The follow-up work documents that the viewer can decrypt both OSGT-CLASSIC-v1 and OSGT-HYBRID-v1, and it adds public tooling to make that claim reproducible.

The sample generator, tools/gen_hybrid_sample.py, creates a tutorial hybrid .sealed file and a public demo identity using the same hybrid DEK-wrap construction as the production path. The Node smoke harness, tools/test_hybrid_decrypt_node.mjs, exercises the viewer decrypt path outside the browser and takes explicit --viewer-dir, --sealed, and --identity arguments. That matters because public docs should not depend on my local filesystem layout. A tutorial sample needs to be generated and tested by someone else on a clean checkout, not by accidentally reading a file that only exists on my machine.

This is also where the project discipline shows up. The public tooling was scrubbed before push so it does not carry local path assumptions or internal host references. That is a small detail in the diff and a big detail in practice. A public protocol repo should be boring to clone: public inputs, public examples, public failures.

The Rust workspace went green

After the registry PR, the broader Rust workspace still had failures in oversight-formats. That follow-up became PR #7. Two issues were hiding there, one in text watermark composition and one in image LSB scheduling.

The text issue was layer ordering. L2 encodes bits in trailing whitespace at the physical end of each line. L1 inserts zero-width frames into the text stream. If L2 runs before L1, later frame insertion can move those trailing spaces away from the physical line endings, and extraction no longer sees the L2 pattern where it expects it. The fix is simple but important: semantic L3 substitution runs first, L1 zero-width insertion runs second, and L2 trailing whitespace runs last. The invariant is now clear: L2 is the last text operation because it is a line-ending signal.

The image issue was a deterministic collision. Blind LSB embedding chooses pseudo-random pixel positions from the mark ID and image dimensions. The previous scheduler could choose the same pixel more than once, which meant a later payload bit could overwrite an earlier payload bit. The fix is to treat the schedule as a unique set of pixel coordinates. The luma-editing helper also now verifies the final rounded Y-channel parity after small RGB adjustments instead of assuming a single channel increment flipped the bit. That makes the test about the actual encoded signal, not about the helper's intent.

The validation command that matters here is:

cargo test --workspace --manifest-path oversight-rust/Cargo.toml

That command now passes across the Rust workspace, including the oversight-formats unit tests and doctests. It is not glamorous work, but it is the kind that prevents a protocol implementation from accumulating "known failing" tests in the corners.

Why these changes belong together

Registry conformance says the Rust operator can speak the public protocol. Hybrid viewer samples say a recipient can inspect modern sealed artifacts with public, reproducible fixtures. Watermark round-trip fixes say the Rust format layer still does what the protocol claims when the layers are composed. Those are three different surfaces, but they share one standard: the implementation should be testable from the outside, and the tests should describe behavior a real integrator depends on.

This also tightens the Python-to-Rust migration path. I am not interested in replacing a working Python reference with a Rust rewrite just because Rust is fashionable. The Rust path earns its place when it does something measurable: pass the same conformance harness, verify the same signed manifests, serve the same evidence bundles, and keep the same watermark semantics under test. These PRs move in that direction.

What this is not

This is not a v1.0 declaration. It is not a production migration plan for registry operators. It does not say the wire format is frozen forever. The remaining work is still real: migration tooling from the Python registry, longer-running deployment tests, more operator-path hardening, and a wire-format stability declaration that is boring enough to trust. The right claim today is narrower: the Rust registry now passes the v1 harness, the browser inspector has public hybrid sample tooling, and the Rust workspace is back to green.

The PR stack is public:

That is enough progress to write down, and not enough to overstate. Good protocol work usually looks like this: fewer assumptions, more fixtures, more tests, and a smaller gap between the document and the thing a verifier can actually run.