Conversation
Tie-collapse at the top serial deduplicated on document alone, so two records naming the same document with different g= keys read as agreeing duplicates and selection picked a generation arbitrarily. A rotation caught mid-flight is a contest: the one-shot RRset rule has no lineage evidence to order the generations, so it must refuse rather than pick. This matches the reference verifier (best_of_document treats a non-unique undominated set as contested) and the canonical classifyRecords export will inherit exactly this behaviour, so adopting it now prevents a verdict flip reading as a regression later. HostnameBinding gains an optional contested flag; the directory renders it as the contested status. Found by the demo app fixing the same gap in its own copy after the onomancy session confirmed the reference semantics; the new test is a candidate conformance vector.
parseRecord now refuses what the canonical parser refuses: a g= that does not decode to exactly 32 bytes (the same lax bug class the demo fixed writer-side), and records over 255 characters. parseRecord is exported so vectors can drive it. tests/ vendors the shared ONO0 conformance vectors (rev 2, authored by keyhive-todo-app-demo; provenance sha in the header): all 36 applicable vectors pass, including the three deferral vectors added after running rev 1 against canonical semantics exposed that the demo's resolver had no skew deferral at all - a live vulnerability their 37/37 self- agreement could never have found, since both TS copies shared the gap. The u64-adjacent vector now carries an explicit nowMs; the boundary vector pins <= as selecting. nextSerial vectors are publisher-side and out of scope here.
The ratified grammar (specs/serialization.md) requires decoders to reject a key field that does not decompress, even where it is never verified against: a 32-byte string that cannot denote a key is not the canonical encoding of anything, and parsers that disagree about whether such a record exists diverge on every selection it feeds. Both downstream TS parsers had the gap - 29/29 vector agreement was agreement on it - found by onomancy's reference harness rejecting the shared fixtures, whose fill(32,k) keys were mostly not points. The check is RFC 8032 decompression in bare BigInt, because this library imports only React: no crypto dependency, and WebCrypto import is async where this parser is sync. Verified against the reference validity table for all seventeen fill fixtures (now a permanent test), and the live brooklynzelenka.com record still parses. Vendors conformance vectors rev 3 (point-valid fixtures, two non-point parse vectors, nowSeconds boundary note): 39/39, no skips. Rev history: rev 1 caught this parser's lax g= and 255 limit; rev 2's canonical referee caught the demo's missing deferral; rev 3's reference harness caught point validity in both parsers and in the fixtures themselves. Each round's bug was invisible to the previous round's process.
Adversarial review round two, against the point-validity batch. atob is forgiving - unpadded input and nonzero trailing bits in the final character decode identically - so one record had many spellings: a parser differential against the reference decoder (which requires canonical padding and rejects set trailing bits), and a phantom-contest vector inside this library, since the tie-set keys on the g= spelling and two spellings of one key read as a rotation. base64ToBytes now round-trips through btoa and rejects non-canonical spellings, making key and spelling bijective, which fixes both at one site. The ratchet remembers the highest serial ACCEPTED; a contested set was refused. Admitting its serial made a zone that heals a rotation without bumping the serial read replayed forever on stale chains. Gated. Also: the sqrt(-1) adjustment constant is hoisted (was ~175us recomputed on half of valid points), isCurvePoint returns false on wrong-length input instead of throwing, Ono0Record is exported beside parseRecord, OnomancyRuntime documents that custom implementations MUST set contested (it is the only carrier of same-document rotation ties), and the replay-preempts-contested ordering is documented as deliberate. The mathematics itself survived review: 6318-case differential fuzz against an independently written checker, zero divergences.
Mutation testing proved two blind spots: a wire-order-first document selection passed all 39 tests (expected.document was never asserted), and a truncated vector file passed vacuously. The runner now asserts the selected document identity via the meta's alias map, pins the rev 3 per-kind vector counts, asserts deferred against its documented default-zero rather than only when present, no longer drops a '0' clock to a falsy check, and passes the clock as BigInt end to end (OnomancyRuntimeOptions.now may now return bigint) instead of losing precision near 2^64. New pins, each verified to fail against a mutant dist: non-canonical base64 spellings are malformed not aliases; x=0 with the sign bit set is rejected (the one RFC 8032 rule the fill(k) table cannot reach); a contested serial does not enter the ratchet. Also a stale-dist warning for local runs, and the test header no longer claims dist is React-free (it is not; the isolation gate allows exactly React).
There was a problem hiding this comment.
🔵 Needs a closer look
It includes a broad refactor of binding/runtime behavior plus new exported utilities and dependency/API shifts that warrant final human validation.
Pull request overview
This PR “pushes functionality down” into the @inkandswitch/onomancy Wasm-backed module (notably ONO0 parsing/record selection), and promotes previously app-local directory/namestore utilities into the library so consumers share one set of rules.
Changes:
- Migrate TXT record parsing / RRset selection / deferral / contest rules to
@inkandswitch/onomancy@0.3.0, updating the runtime and verified-directory mapping accordingly. - Promote shared directory utilities (flat namestore writes, directory composition, reverse-binding designation combinator) into
src/exports. - Add/expand behavior-pin tests and a hermetic ONO0 conformance-vectors suite; update the component-test app for the flat layout and injected helpers.
File summaries
| File | Description |
|---|---|
| tests/verified-directory.test.mjs | Updates behavior pins and adds new contested/ratchet regression tests. |
| tests/ono0-conformance-vectors.jsonl | Adds shared ONO0 parse/classify/nextSerial conformance vectors (rev 4). |
| tests/namestore.test.mjs | Adds tests for promoted namestore helpers, reverse-binding combinator, and composed publish behavior. |
| tests/conformance-vectors.test.mjs | New hermetic test runner that replays ONO0 vectors at module and runtime-mapping levels. |
| tests/automerge-directory.test.mjs | New pins ensuring reserved keys are refused and non-entry shapes are skipped. |
| src/onomancy/verified-directory.ts | Carries contested bindings through, avoids ratcheting contested serials, and uses shared bareId. |
| src/onomancy/runtime.ts | Replaces local RRset parsing/selection with onomancy.classifyRecords + docAnchorBytes mapping. |
| src/onomancy/reverse-binding.ts | New designation combinator requiring reverse-binding certificate claims (injected check). |
| src/onomancy/index.ts | Updates exports/types for new runtime surfaces and reverse-binding utilities. |
| src/index.ts | Promotes compose/namestore exports and re-exports bareId. |
| src/directory/namestore.ts | New shared helpers for flat namestore writes with reserved-path refusal and legacy cleanup. |
| src/directory/compose.ts | Moves to local types import and changes publish to attempt both writes via allSettled. |
| src/directory/automerge-directory.ts | Adds entry-id and entry-shape filters and expands reserved-key guarding to .well-known/*. |
| pnpm-lock.yaml | Locks @inkandswitch/onomancy to 0.3.0 and updates lockfile entries accordingly. |
| package.json | Bumps package version and adds @inkandswitch/onomancy>=0.3.0 peer/dev dependency. |
| apps/component-test-app/src/onomancyStub.ts | Updates stub to supply classifier + anchor decoder while still faking only resolution. |
| apps/component-test-app/src/nameResolution.ts | Switches name parsing to onomancy Name and updates namestore walk for flat layout + migration fallback. |
| apps/component-test-app/src/localDirectory.ts | Removes the localStorage directory implementation (no longer used). |
| apps/component-test-app/src/App.tsx | Removes local fallback directory and uses promoted bindEdge for flat namestore writes. |
| apps/component-test-app/package.json | Bumps app dependency on @inkandswitch/onomancy to 0.3.0. |
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
src/directory/compose.ts:70
- The comment says "failures surface together", but the implementation rethrows only the first rejection reason and drops any additional failures (e.g., if both publishes reject). Either aggregate errors (e.g., AggregateError) or adjust the comment to match the current behavior.
- Files reviewed: 19/20 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Brooklyn Zelenka <hello@brooklynzelenka.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mostly pushing functionality down into the wasm package, and reusing them here