feat(front): close the Passport download → /verify round trip (#44) - #122
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
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 inPassportCard.tsx(#118), anddownloadPassport()inlib/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.
/verifyparsed uploaded files with its own inline, unexported, untested copy of the token-extraction logic — free to drift from whatdownloadPassportwrites, with no test spanning the two.frontend/src/lib/passport.ts— addsextractJwsToken(), the exact inverse ofdownloadPassport, beside it.frontend/src/app/verify/page.tsx—readFileTokennow 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_pipelinechanges.Why
Closes #44
This is a Definition of Done criterion, not a nice-to-have.
docs/MVP.md§10:A download button that emits a file
/verifycannot read would satisfy the word and fail the criterion.How IBM Bob helped
Claude Code agent. It also reported and stopped on a separate blocking problem rather than fixing it as a side quest:
maindid not build, because merging #115 and #119 kept both sides of theAPI_BASEcollision inapi.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:
autoria_ai.passport.builder.issue_passportusing a real ephemeral ES256 keypair.downloadPassportand captured the exact bytes handed to theBlob.extractJwsTokenon those bytes → token identical byte-for-byte.autoria_ai.passport.verifier.verify_passport→valid: True, no errors, payload intact (dickens,fit_score: 87).Two negative controls confirm the signature is what governs:
json_payloaddoes not change the verdict — verify returns the signedfit_score: 87, not the edited100. The human-readable formatting cannot forge anything.invalid_signature.Accessibility (verification only, no visual change): the button is
h-7(28px) ≥ the 24px WCAG 2.2 AA minimum, with anaria-label,aria-hiddenicon, visible text and a focus-visible ring.ui-ux-pro-maxwas deliberately not invoked — with nothing to restyle it could only have produced a redesign.Checklist
make test)make lint)docs/.env.exampledocs/api_contract.yamlREADME.mdupdated