Skip to content

Document Sign-Off MVP + roadmap MRP mapping - #356

Open
QSchlegel wants to merge 2 commits into
preprodfrom
claude/document-signoff-mvp
Open

Document Sign-Off MVP + roadmap MRP mapping#356
QSchlegel wants to merge 2 commits into
preprodfrom
claude/document-signoff-mvp

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

Two independent changes, one per commit — review them separately.

1. docs(roadmap) — MRP mapping and the April–July window

The 2026-08-03 renumbering (984aa46) moved Month 1 to April, but MRP task cards created before that still carry the following month's bullet text — the card headed "MRP Month 2" lists the June workstreams. This writes the mapping down so it stops costing time.

2. feat(documents) — Document Sign-Off MVP (PRD-001)

A wallet-native, off-chain approval layer: bind approval to an exact content hash, inherit the wallet's signers and threshold, export a proof anyone can verify without an account. No chain dependency.

Two rules carry the feature, and both are enforced server-side, not in the UI:

  1. Version-hash binding. submitSignerAction rebuilds the canonical signed payload from the server's own records and requires a byte-identical match before the signature is checked. A signature collected for one version cannot be replayed onto another, and a tampered comment invalidates the submission. Inline content is re-hashed server-side and rejected on mismatch.
  2. Threshold from a frozen snapshot. DocumentSignerSnapshot captures the wallet's signers, threshold and policy hash when the round starts. Approval counting reads the snapshot, never the live wallet — changing wallet membership cannot rewrite a decision already made.

Data modelDocument, DocumentVersion, DocumentReview, DocumentSignerSnapshot, DocumentEvent, migration 20260805090000_add_document_signoff. The migration enables RLS with deny-all PostgREST policies on all five tables, matching the contract in 20260706100000_enable_rls_followup_tables (#332).

RoutercreateDocument, uploadVersion, startReview, submitSignerAction, exportProof, verifyProof. CIP-8 verification via Mesh's checkSignature against the signer's address. verifyProof is public on purpose: a counterparty holding the JSON and the file must be able to check it without an account, and it reads nothing from the database.

Four routes under /wallets/[wallet]/documents — list, create, detail, version review. Files are hashed in the browser via WebCrypto; only the digest is sent, so the bytes never leave the signer's machine.

src/lib/documents/ is dependency-free apart from node crypto, with the signature check injected into the verifier — the same code runs in an offline verifier with no Mesh install.

Heads-up for the deploy

This adds a migration. Railway's prestart runs prisma migrate deploy, so merging to preprod will apply 20260805090000_add_document_signoff to the preprod database on the next deploy. Five new tables, no changes to existing ones.

Verification

  • npx tsc --noEmit — clean (the pre-existing txScriptRecovery.test.ts errors on preprod are untouched)
  • npx jest src/__tests__/documentSignoff.test.ts — 28 passing: canonicalization, version-hash binding, threshold evaluation, and proof verification including tampering, duplicate signers, out-of-snapshot signers and non-canonical payloads
  • npm run build — passes; all four document routes register as server-rendered
  • /roadmap checked in a local production build (next dev cannot serve SSR in a worktree — Turbopack resolves the whisky WASM to /ROOT/node_modules/…)

Not included

PRD-001 is still status: Draft. Proof export is JSON only — the PDF summary and the public verify page are the M5 half of the MVP.

🤖 Generated with Claude Code

QSchlegel and others added 2 commits August 7, 2026 09:20
…m/hours line

The month headings were renumbered on 2026-08-03 (984aa46) so that Month 1 =
April, but MRP task cards created before that date still carry bullet text for
the following month — the card headed "MRP Month 2" lists the June workstreams.
That mismatch has now cost enough time to be worth writing down.

- Add an MRP task mapping table at the top: MRP Month N = roadmap Month N =
  calendar month, for all twelve months, with the on-chain task hashes we have
  and a note explaining why the cards disagree.
- Route each MRP month to the actual merged PRs behind it (April 10, May 3,
  June 51, July 16), each count linking to the exact GitHub search so any row
  can be reproduced rather than taken on trust.
- Widen "Delivered to date" from May–July to April–July. April's output is
  infrastructure, so it folds into the existing sections: the preprod
  environment and real-chain smoke CI (#218, #217) under Testing & CI, and a
  new transaction-and-signing-integrity through-line under Platform (#217 VKey
  witness filtering -> #227 invalid-CBOR guard -> #257 Mesh pin + witness
  verification guard).
- Recover two May items the 2026-07-26 audit had missed: the Import Wallet
  wizard (#259) and the #257 signing fix.
- Fix the timeline left stale by the renumbering: April 2026 – March 2027, in
  ROADMAP.md, the /roadmap page and the SEO description.
- Remove the "Quirin + Andre · ~25 h/wk" line from ROADMAP.md and the public
  roadmap page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gn-off

Implements PRD-001: a wallet-native, off-chain approval layer where a team
binds approval to an exact content hash, inherits the wallet's signer set and
threshold, and exports a proof anyone can verify without an account.

Two rules carry the feature, and both are enforced server-side rather than in
the UI:

1. Version-hash binding. submitSignerAction rebuilds the canonical signed
   payload from the server's own records and requires a byte-identical match
   *before* the signature is checked, so a signature collected for one version
   can never be replayed onto another, and a tampered comment invalidates the
   submission. Inline content is re-hashed server-side and rejected on mismatch.

2. Threshold inheritance from a frozen snapshot. DocumentSignerSnapshot captures
   the wallet's signers, threshold and policy hash when the round starts;
   approval counting reads the snapshot, never the live wallet, so changing
   wallet membership cannot rewrite a decision already made.

Data model (5 models + migration 20260805090000): Document, DocumentVersion,
DocumentReview, DocumentSignerSnapshot, DocumentEvent. The migration enables RLS
with deny-all PostgREST policies on all five tables, matching the contract in
20260706100000_enable_rls_followup_tables (#332).

Router (src/server/api/routers/documents.ts): createDocument, uploadVersion,
startReview, submitSignerAction, exportProof, verifyProof, plus the reads the
pages need. CIP-8 verification uses Mesh's checkSignature against the signer's
address. verifyProof is public on purpose — a counterparty holding the JSON and
the file must be able to check it without an account, and it touches no DB.

Four routes under /wallets/[wallet]/documents: list, create, detail, and the
version review page. Files are hashed in the browser (SHA-256 via WebCrypto);
only the digest is sent, so the bytes never leave the signer's machine.

src/lib/documents/ is dependency-free apart from node crypto, with the signature
check injected into the verifier — the same code can run in an offline verifier
with no Mesh install.

Uploading a new version supersedes the previous one and starts a fresh round at
zero approvals: approval is bound to the hash, not the title.

Tests: 28 covering canonicalization, version-hash binding, threshold evaluation
and proof verification including tampering, duplicate signers, out-of-snapshot
signers and non-canonical payloads.

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

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
multisig Ready Ready Preview Aug 7, 2026 7:26am

Request Review

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.

1 participant