Skip to content

feat(front): close the Passport download → /verify round trip (#44) - #122

Merged
sergi-torres merged 1 commit into
mainfrom
feat/44-download-passport
Jul 28, 2026
Merged

feat(front): close the Passport download → /verify round trip (#44)#122
sergi-torres merged 1 commit into
mainfrom
feat/44-download-passport

Conversation

@sergi-torres

@sergi-torres sergi-torres commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What

Closes the download → verify loop for the Authorship Passport.

The visible scope of #44 turned out to be already on main: the Download button and the collapsible formatted-JSON block live in PassportCard.tsx (#118), and downloadPassport() in lib/passport.ts (#42). Re-implementing them would have duplicated working code.

What was genuinely missing is the half the Definition of Done actually turns on: nothing proved the downloaded file could be verified back. /verify parsed uploaded files with its own inline, unexported, untested copy of the token-extraction logic — free to drift from what downloadPassport writes, with no test spanning the two.

  • frontend/src/lib/passport.ts — adds extractJwsToken(), the exact inverse of downloadPassport, beside it.
  • frontend/src/app/verify/page.tsxreadFileToken now delegates to it; the inline copy is gone. Behaviour-preserving.
  • frontend/src/lib/passport.test.ts — 4 round-trip tests over a genuinely signed passport.

Frontend suite: 49 → 53 passing. No test weakened or skipped. No UI, styling, i18n, backend or ai_pipeline changes.

Why

Closes #44

This is a Definition of Done criterion, not a nice-to-have. docs/MVP.md §10:

Authorship Passport issued, downloaded, and verifies with a valid signature

A download button that emits a file /verify cannot read would satisfy the word and fail the criterion.

How IBM Bob helped

  • Custom Mode used: none
  • Bob session export: n/a

Claude Code agent. It also reported and stopped on a separate blocking problem rather than fixing it as a side quest: main did not build, because merging #115 and #119 kept both sides of the API_BASE collision in api.ts. Filed and fixed separately as #121.

Screenshots / output

Verified by running the real code end to end across the language boundary, not by reasoning about it:

  1. Signed a real passport with autoria_ai.passport.builder.issue_passport using a real ephemeral ES256 keypair.
  2. Ran the real downloadPassport and captured the exact bytes handed to the Blob.
  3. Ran the real extractJwsToken on those bytes → token identical byte-for-byte.
  4. Fed that token to the real autoria_ai.passport.verifier.verify_passportvalid: True, no errors, payload intact (dickens, fit_score: 87).

Two negative controls confirm the signature is what governs:

  • Editing the pretty-printed json_payload does not change the verdict — verify returns the signed fit_score: 87, not the edited 100. The human-readable formatting cannot forge anything.
  • Editing the payload inside the JWS is correctly rejected as invalid_signature.

Accessibility (verification only, no visual change): the button is h-7 (28px) ≥ the 24px WCAG 2.2 AA minimum, with an aria-label, aria-hidden icon, visible text and a focus-visible ring. ui-ux-pro-max was deliberately not invoked — with nothing to restyle it could only have produced a redesign.

Checklist

  • Tests pass locally (make test)
  • Lint passes (make lint)
  • If schema changed (StyleProfile / Passport) → bumped version + updated docs/
  • If new env var → added to .env.example
  • If new/changed endpoint → updated docs/api_contract.yaml
  • Documentation in the relevant README.md updated
  • At least one teammate reviewed

Manual click-through still pending. The branch was cut before #121 landed, so the app could not be launched to click studio → Download → /verify upload in a browser. The code-level proof above is stronger and repeatable, but the click-through is worth doing now that main builds again.

Minor, unfixed: .prettierrc says trailingComma: "es5" while the committed code is "all". Formatting here used --trailing-comma=all and the pre-commit hook passed, but the config is inconsistent with the codebase.

The Download Passport button and the on-screen formatted JSON block already
landed with #42 and #118. What was missing is the other half of the MVP §10
Definition of Done — "issued, downloaded, and verifies with a valid
signature": nothing proved the downloaded file could be verified back.

/verify parsed uploaded files with its own inline copy of the token
extraction, unexported and untested, free to drift from what
downloadPassport writes. Lift it to lib/passport.extractJwsToken, next to
its inverse, and have /verify delegate to it. Behaviour is unchanged.

Lock the round trip with tests over a genuinely signed passport (real
ES256 key, real autoria_ai.passport.signer): downloadPassport → file bytes
→ extractJwsToken returns the token byte-for-byte, including with a
trailing newline and as a bare pasted token.

Verified end to end outside the suite: the token the frontend extracts
from the downloaded file was fed back to the real
autoria_ai.passport.verifier and returned valid: true. Editing the
pretty-printed json_payload does not affect the verdict (the signature
covers the compact JSON inside the JWS); editing the signed payload is
correctly rejected as invalid_signature.

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

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
author-ai Error Error Jul 28, 2026 5:47pm

@sergi-torres
sergi-torres merged commit c6d6533 into main Jul 28, 2026
7 of 8 checks passed
@sergi-torres
sergi-torres deleted the feat/44-download-passport branch July 28, 2026 18:03
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.

[frontend] "Download Passport" button with formatted JSON

1 participant