INTEGRATION-CONTRACT.md
VAULT-ADOPTION Integration Contract
Source of truth for integrating /Users/june/Downloads/peach-vault-adoption-brief.md.
Where this contract and an individual draft disagree, this contract wins.
Scope
Draft only. No production source was modified in this pass. Integrate in
browser first, then coordinate Android. peach-sync has no adoption work.
Current browser drift from the brief:
- Brief base says v4.0.3; current dev is v4.0.13.
PAIR_IDENTITYalready supports optionalvault_idand Prolicense.- Sync pairing is already usable by non-Pro devices; at least one device needs Pro and license transfer already rides normal pairing.
Files in src/drafts/VAULT-ADOPTION/
| Draft file | Destination / role |
|---|---|
README.md |
Draft index; keep or trash after integration. |
vault-createVaultFromRootKey.ts |
Code hunks for src/background/vault.ts. |
vault-createVaultFromRootKey.test.ts |
Destination src/background/vault-createVaultFromRootKey.test.ts; T-A01..T-A05. |
messages-CREATE_VAULT_FROM_ROOT_KEY.ts |
Code hunks for src/shared/types.ts and src/background/messages.ts. |
messages-CREATE_VAULT_FROM_ROOT_KEY.test.ts |
Destination src/background/messages.create-vault-from-root-key.test.ts; T-A06..T-A09. |
chirp-adopt.ts |
Destination src/shared/chirp-adopt.ts. |
chirp-adopt.test.ts |
Destination src/shared/chirp-adopt.test.ts; T-A10..T-A15. |
AdoptionConsentModal.tsx |
Destination src/popup/components/AdoptionConsentModal.tsx plus CSS hunk to src/popup/popup.css. |
AdoptionConsentModal.test.tsx |
Destination src/popup/components/AdoptionConsentModal.test.tsx; T-A16..T-A20. |
onboarding-adopt-path.tsx |
Destination src/popup/views/AdoptVaultView.tsx plus hunks for Onboarding.tsx, Lock.tsx, App.tsx. |
onboarding-adopt-path.test.tsx |
Destination src/popup/views/AdoptVaultView.test.tsx plus one contracts test; T-A21..T-A25. |
chirp-pair-adopt-source.ts |
Hunk for src/popup/chirp-pair.ts; requires a supporting responder-open-frame export from src/shared/chirp-handshake.ts. |
chirp-pair-adopt-source.test.ts |
Destination src/popup/chirp-pair.adopt-source.test.ts; T-A26..T-A29. |
INTEGRATION-CONTRACT.md |
This file. |
Integration Order
Follow phases 1 -> 2 -> 3 -> 4 -> 5 -> 6. Save each phase separately.
-
Phase 1: vault root-key adoption
- Add
wrapExistingRootKeyForRecoveryandcreateVaultFromRootKeytosrc/background/vault.ts. - Import
computeVaultId. - Run
npx tsc --noEmit, relevant vault tests, then save.
- Add
-
Phase 2: message handler
- Add
CREATE_VAULT_FROM_ROOT_KEYtoMessageType. - Add handler beside
CREATE_VAULT. - Handler is vault-less allowed and returns
{ ok: true, vaultId }. - Run message tests and save.
- Add
-
Phase 3: adoption sub-protocol
- Add
src/shared/chirp-adopt.ts. - Add tests T-A10..T-A15.
- Important correction from the brief: receiver computes and checks
computeVaultId(root_key_hex)before creating the vault. The brief's original order would allow partial local creation before detecting a mismatch.
- Add
-
Phase 4: source consent UI
- Add
AdoptionConsentModal. - Add amber CSS to
popup.css. - Wire the modal through the source pairing UI event path.
- Re-auth uses
VERIFY_MAIN_PASSWORDor the existingvault.verifyMainPasswordbackground path before allowinggrantAdoption.
- Add
-
Phase 5: receiver onboarding
- Add
"adopting"toApp.tsxview state. - Add
AdoptVaultView. - Add final onboarding action:
I already have Peach ->. - Receiver password is collected before chirp. Use
CREATE_VAULT_FROM_ROOT_KEYonly after SAS confirm andADOPT_GRANTvalidation.
- Add
-
Phase 6: source routing
- Refactor
chirp-handshake.tsso responder can inspect the first encrypted JSON frame asPAIR_IDENTITYorADOPT_REQUEST. - Normal pairing remains unchanged for
PAIR_IDENTITY. ADOPT_REQUESTrunsgrantAdoptiononly after SAS and consent pass.
- Refactor
-
Version/build save
- Bump browser version from current dev version by at least PATCH.
- Run
npm run buildaftertsc/vitest.
New Message Types
Add to src/shared/types.ts:
| "CREATE_VAULT_FROM_ROOT_KEY"
Payload:
{
password: string;
rootKeyHex: string;
}
Response:
{ ok: true; vaultId: string } | { ok: false; error: string }
Suggested error strings:
invalid_payloadADOPT_INVALID_PASSWORDADOPT_INVALID_ROOT_KEY: expected 64 lowercase hex charsADOPT_VAULT_ID_MISMATCH
New Frame Types
Add in src/shared/chirp-adopt.ts:
ADOPT_REQUEST
ADOPT_GRANT
ADOPT_IDENTITY
Do not overload PAIR_IDENTITY. Adoption is a sub-protocol that runs on the
same Noise/SAS channel after chirp, with its own two-phase persistence rules.
New View State
Add to App.tsx:
| "adopting"
"adopting" is vault-less allowed. If adoption succeeds:
- Refresh
HAS_VAULT. - Mark onboarding complete.
- Open the normal unlocked app shell.
- Trigger an immediate sync attempt if a paired source was persisted.
Estimated Test Count
29 specs:
- T-A01..T-A05: vault creation from adopted root key.
- T-A06..T-A09: message handler.
- T-A10..T-A15: adoption sub-protocol.
- T-A16..T-A20: source consent modal.
- T-A21..T-A25: receiver onboarding path.
- T-A26..T-A29: source routing.
Cross-Platform KAT Extensions
Add a fixed adoption KAT shared with Android:
- Fixed chirp ephemeral inputs.
- Fixed source root key: 32 bytes.
- Fixed source identity keys.
- Expected SAS.
- Expected
vault_id. - Expected frame sequence:
ADOPT_REQUEST -> ADOPT_GRANT -> ADOPT_IDENTITY.
The KAT should prove Android and browser serialize the same snake_case frame fields and derive the same vault id from the same root key bytes.
Android Work Required
Do not implement in this browser draft, but the Android integration pass needs:
- Pairing protocol layer: add
ADOPT_REQUEST,ADOPT_GRANT,ADOPT_IDENTITY. - Vault layer: create local vault from imported root key with local password.
- Source consent UI: warning modal with master-password re-auth.
- Receiver onboarding: "I already have Peach" path, local password first.
- License transfer: send/activate optional
licensein adoption frames, matching browser's normal pairing transfer behavior. - Tests mirroring T-A01..T-A29 plus the cross-platform KAT.
Likely files in peach-android (names may differ):
- pairing/chirp protocol module
- vault repository / crypto storage module
- onboarding Compose screens
- source consent Compose dialog
- paired-device repository
- license activation path
Security Checklist
- [x]
root_key_hexis validated as exactly 64 lowercase hex chars before use. - [x] Decoded root key bytes are zeroed after
createVaultFromRootKeyreturns. - [x] Drafts never log
root_key_hex. - [x]
ADOPT_GRANTis sent only after the regular SAS gate. - [x]
ADOPT_GRANTis sent only after source master-password re-auth succeeds. - [x] Source does not persist the receiver until
ADOPT_IDENTITYis received. - [x] Receiver aborts before creating a vault if
vault_iddoes not match the granted root key. - [x] Receiver stays vault-less on invalid root key, unexpected frame, SAS cancel, source consent reject, and vault-id mismatch.
- [x]
ADOPT_REQUESTfrom a peer without adoption support results in an unexpected-frame/error path, not a grant. - [x] Consent modal copy says "copies your entire vault including all passwords and Pro".
Known Integration Corrections
- The brief suggests
rootKeyHex.split('').fill('0'); that does not zero the original immutable string. Integrate byte-buffer zeroing and keep string scope narrow. createVaultFromRootKeymust setrootKey = new Uint8Array(rootKeyBytes), notrootKey = rootKeyBytes, because the input buffer is wiped infinally.- The current
runPairingRespondercannot handle adoption as-is because it decodes the first encrypted frame asPAIR_IDENTITY. Refactor around an open-frame responder primitive, then rebuild normal pairing on top.