Skip to content

fix(anchor): parse Rekor v2's response shape, not v1's - #9855

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
fix/rekor-response-shape
Jul 29, 2026
Merged

fix(anchor): parse Rekor v2's response shape, not v1's#9855
loopover-orb[bot] merged 1 commit into
mainfrom
fix/rekor-response-shape

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Closes #9854

The backend was half-migrated

submitToRekor is Rekor v2 on the way out — hashedRekordRequestV002, POST /api/v2/log/entries — and Rekor v1 on the way back in:

const entries = Object.values(raw as Record<string, unknown>);   // v1: { "<uuid>": entry }
typeof candidate["logIndex"] !== "number"                        // v1: number
typeof candidate["uuid"] !== "string"                            // v1: has a uuid

v2 returns the TransparencyLogEntry directly, encodes logIndex as a proto3 int64 (JSON "42", a string), and has no uuid — an entry is located by log index plus the checkpoint its inclusion proof was issued against.

All three checks fail on every real response, so this backend could never record a successful anchor, even when the log accepted the submission.

How it surfaced

On the live self-host instance from #9845. Once the shard URL was corrected, the published failure moved from fetch failed (DNS) to "Rekor response did not match the expected TransparencyLogEntry shape" — the submission now reaches a real log and is rejected only at our end.

Why review couldn't have caught it

const REKOR_RESPONSE = { "24296fb24b8ad77a": { logIndex: 42, uuid: "24296fb24b8ad77a", logId: { keyId: "c2iga0d1" } } };

The fixture was v1-shaped, and the assertion's title read "reading the entry under its dynamic uuid key". The test agreed with the bug. Both are corrected rather than deleted, so the contract change is visible in history — and the scheduler suite had the same v1 mock inline.

Judgement calls

  • An absent checkpoint parses to null rather than failing. The entry is in the log; only the offline re-check is unavailable. Throwing away a real successful submission over an optional field is the worse trade.
  • An unparseable logIndex still fails. Coercing would put NaN in a published backendRef, which is worse than no anchor.
  • backendRef reshaped (uuidcheckpoint). It is opaque per-backend JSON — the git backend stores {owner, repo, branch, path, sha} — so nothing parses the rekor variant's fields.
  • The v1 wrapper is now explicitly rejected, with a test, rather than half-parsed into a backendRef that points nowhere.

Validation

tsc --noEmit clean. 87 tests pass across the rekor, scheduler, persistence and public-route suites. ledger-anchor-rekor.ts at 100% lines / 100% branches.

Merged current main (including #9845, which touched the same file) and verified both changes coexist: the corrected default shard, the endpoint-naming error wrap, and this parser are all present.

submitToRekor was v2 on the request side -- hashedRekordRequestV002, POST
/api/v2/log/entries -- while parseRekorResponse expected v1: an object keyed by
entry uuid, a numeric logIndex, and a uuid field.

Rekor v2 returns the TransparencyLogEntry directly, encodes logIndex as a proto3
int64 (JSON "42", a string), and has no uuid at all. All three checks failed on
every real response, so this backend could never record a successful anchor even
when the log accepted the submission -- it wrote status: failed with "did not
match the expected TransparencyLogEntry shape".

Surfaced on a live self-host instance: once #9845 fixed the shard URL, the error
moved from fetch failed (DNS) to this parse failure, i.e. the submission now
reaches a real log and is rejected only at our end.

The fixture was itself v1-shaped and its assertion's title read "reading the
entry under its dynamic uuid key", so the test agreed with the bug and could not
fail. Replaced with a real-shaped entry; the scheduler's inline mock had the same
problem.

backendRef carries the inclusion proof's checkpoint where uuid used to be -- v2's
locator, since an entry is identified by log index plus the checkpoint the proof
was issued against. An absent checkpoint parses to null rather than failing: the
entry IS in the log, only the offline re-check is unavailable, and discarding a
real submission over an optional field is the worse trade. An unparseable
logIndex still fails, so NaN can never reach a published backendRef.

Closes #9854
@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-29 14:29:44 UTC

3 files · 1 AI reviewer · no blockers · readiness 95/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR correctly fixes a real bug: the Rekor response parser expected v1's uuid-keyed wrapper shape while the request side already targets v2's flat TransparencyLogEntry, meaning no anchor could ever succeed. The rewritten parseRekorResponse handles v2's string-or-number logIndex, drops the nonexistent uuid field in favor of a nested checkpoint (with both bare-string and {envelope} encodings tolerated), and the backendRef schema change is threaded through consistently to the persistence call site. Tests are substantially expanded and correctly assert both the new v2 fixture and rejection of the old v1 shape, closing the gap where the prior test's assertion literally validated the bug.

Nits — 6 non-blocking
  • src/review/ledger-anchor-rekor.ts:118 references issue openapi: the route auth-parity meta-test two files are exported FOR does not exist #9851 inline in a comment rather than a named constant/doc link — minor, purely stylistic.
  • parseCheckpoint's tolerance for a bare-string checkpoint (in addition to the { envelope } encoding) is speculative hardening for a shape not confirmed to occur in the real v2 API — reasonable but worth confirming against the actual Rekor v2 spec.
  • The scheduler test mock at test/unit/ledger-anchor-scheduler.test.ts uses inclusionProof.checkpoint.envelope but omits kindVersion/canonicalizedBody fields present in the more thorough rekor.test.ts fixture — harmless since those aren't read by parseRekorResponse, but slightly inconsistent fixture shapes across test files.
  • Consider extracting the REKOR_RESPONSE-style v2 fixture into a shared test helper so ledger-anchor-rekor.test.ts and ledger-anchor-scheduler.test.ts don't each hand-roll a slightly different v2 mock.
  • The backendRef schema change (uuid -> checkpoint) is a breaking change to any already-persisted rows; confirm downstream consumers of backendRef (e.g. UI/API surfacing anchor info) tolerate the old uuid-shaped rows still in the DB.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9854
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 345 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 345 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The diff rewrites parseRekorResponse to read the TransparencyLogEntry directly, accepts logIndex as string or number while rejecting non-finite values, requires logId.keyId, replaces uuid with a checkpoint parsed from inclusionProof that is null-safe, rejects the old v1 wrapper, and updates both the test fixture and the scheduler's inline mock with corresponding coverage for all the listed cases.

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 345 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 2 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@JSONbored JSONbored self-assigned this Jul 29, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.77%. Comparing base (31e0d4e) to head (0b370ee).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9855      +/-   ##
==========================================
- Coverage   91.65%   90.77%   -0.88%     
==========================================
  Files         916      916              
  Lines      112763   112772       +9     
  Branches    27087    27093       +6     
==========================================
- Hits       103348   102366     -982     
- Misses       8126     9315    +1189     
+ Partials     1289     1091     -198     
Flag Coverage Δ
backend 94.10% <100.00%> (-1.57%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/review/ledger-anchor-rekor.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 226d974 into main Jul 29, 2026
8 checks passed
@loopover-orb
loopover-orb Bot deleted the fix/rekor-response-shape branch July 29, 2026 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

orb(anchor): the Rekor backend parses a v1 response shape, so a v2 submission can never record success

1 participant