# Persimmons follow-up candidate — 2026-09-30 Version **0.3.4** fixes popup-memory-only connection state with a non-secret owner hint and live session validation on every reopen. Vault keys still die with the popup. A read-only, exact-origin, same-owner handshake lets the signed-in page say Extension connected without exposing unlocked state or secrets. Actions/Juice is a compact collapsed divider with technical details under disclosures. A same-folder Python update helper plus Reload removes repeated manual ZIP handling. Orange icons, hero/footer copy, crypto, server pinning, origin allowlist, dedicated host and MCP boundaries are preserved. Candidate acceptance: **42 Python tests**, **11 Node tests across four files**, plus real Chromium 1243 against the live Persimmons API and the **real** `https://wp.phly.cloud/wp-login.php` (not an intercepted target fixture). Candidate homepage assets were served locally into the Persimmons origin for pre-publication review. Connect → initialize/unlock → save → destroy popup → refresh both pages → reopen automatically Connected/Locked → wrong-passphrase rejection → unlock/list → fill both fields with **zero POST submissions** → delete all pass. Homepage handshake, expired/logout session rejection, changed-owner rejection and revoked host permission also pass. No Tea permission was granted. Synthetic accounts remain, but successful tests remove their ciphertext and sign out; no provider credentials or sessions are included in receipts. The normal Chromium permission prompt was not clicked by automation: the harness pregrants the exact permission; the user's accepted prompt is the human evidence. Package SHA-256: `d98ce727ebdedd9ceaec226960103baa58f8a5b65a0b8e31155b5fdea7334996`. Initial and pre-publication fetch: HEAD/origin-main both `26fec5fadd444ca2ccfe70c7eeb9ee6688bb7de4`, ahead 0 / behind 0; initial clean, pre-commit dirty only with this task. No delegation. Build-generated host vault module remains byte-for-byte copied from the accepted source. Deployment remains a separate required receipt: dispatch **Launch independent Persimmons**, `.gitea/workflows/persimmons-launch.yml`, `matcha/teahub`, ref **main**, no inputs, after canonical publication. The local reviewed `tea_workflow_dispatch` allowlist does not include this workflow; do not extend it through its own running control plane or bypass it with provider secrets. Check the live `/healthz` source SHA and asset/package bytes after the run, then repeat the browser check without `PERSIMMONS_LOCAL_UI=1`. User retest: use **Update extension** on the homepage, save the helper once, run `python3 update-extension.py "/path/to/existing/persimmons-extension"`, then `chrome://extensions` → Persimmons → **Reload** (do not uninstall). Sign in at Persimmons; open the WordPress login URL, Connect once, unlock, save a disposable credential, close/refresh/reopen, unlock, select/Fill without submit, and Delete. Refresh Persimmons to see Extension connected. Future updates reuse the same helper command/folder; 0.3.4 also offers popup **Reload installed files**. Remaining: Chrome Web Store private/unlisted distribution needs explicit approval, review and a verified matching store identity. No Store upload/publication was attempted. True self-download of unpacked extension source is unavailable; Reload alone rereads files. Helper browser acceptance is Linux-only. Detailed update mechanics, platform notes and official references are in [persimmons-extension/README.md](../persimmons-extension/README.md#034-connection-controlled-acceptance-and-updates). --- # Persimmons taste-test receipt — 2026-09-30 Accepted: is live. Homepage, health, extension download, exact-origin preflight and real Chromium encrypted-vault flow pass. ## Deployment and acceptance - Runtime source: `54f0ff75f7b6a89d69dc4a211b44467ffc35be03`. [Final launch Action 617](https://teahub.xyz/matcha/teahub/actions/runs/617), job 619, succeeded; complete deployment/check cycle: 52.814 seconds. - Before deploy: explicit fetch; worktree HEAD = upstream = runtime source; ahead 0, behind 0, dirty false. Initial canonical source was `a80a64f`. AGENTS and factory checkpoint read; no delegation. - Fly app `persimmons-app`, `iad`; one shared CPU / 512 MiB; machine `48e729dc5796e8`, release 2, health passing. Encrypted 1 GiB volume `vol_rkgqyxm5k2gxm064` persists `/data`. - Image `registry.fly.io/persimmons-app:deployment-01M3QY0KQ70X44T1EA7CYETDE0`; digest `sha256:c99e5711c0dffee68816bd9059e81ce4c9bee6873e6f9b03198d4abae96a234f`. - Cloudflare apex A `66.241.124.82`, AAAA `2a09:8280:1::1a0:f8e1:0`; TTL 60, DNS-only. Fly-managed Let's Encrypt RSA/ECDSA certificates are verified and active. No additional hostname or Cloudflare Worker is needed. - `/`, `/healthz`, app JS, sign-in JS and extension ZIP: 200/nonempty. Health reports the runtime source above. Final extension 0.3.2 matches the tested package byte-for-byte: SHA-256 `41dd24474ad35fca6d37378b641c0ded300450bc39145315a2d1d256a87d0430`. - Preflight on `/api/human-vault/items` and `/api/human-vault/items/read`: 204, credentials true, exactly one ACAO: `chrome-extension://kiolgjoohhgdohcafgelhikpgkbmckbg`. Untrusted origins: 403, no ACAO. No wildcard callers. - Live disposable HTTP flow: register/login 200, extension Web Crypto ciphertext create 201, logout/login, persisted read 200, delete 200, logout 200. - Real Chromium 1243: homepage and independent signup pass; actual pinned popup has Persimmons permission and **no Tea permission**; session cookie transport, unlock, encrypted save, explicit fill, lock, unlock/delete and logout rejection all pass. Fill used a locally intercepted HTTPS form; it contacted neither WordPress nor Tea. Successful probes delete their ciphertext and sign out. - Ten Node tests and seven Python host/vault/MCP tests pass. Canonical MCP tests assert exactly `persimmons_list` and `persimmons_juice_list`, with no human-store access. Connected agent inventory exposes only the metadata tool, no reveal tool. ## Scope and preserved boundaries The host reuses the accepted `tea/web/🍊/index.html` design, with taste-test controls for independent sign-in and extension download. Repository-token management and Juice remain on the Tea fallback; this round does not migrate those stores or the full repository-management UI. No Tea/StreetPulse runtime dependency was added. No neighboring service was restarted, redeployed, or changed. Tea source/UI and human-vault routes remain available as fallback. Build copies `tea_mcp/human_vault.py` byte-for-byte; both copies have SHA-256 `8651aed1f8351477cdfd737a9f0d0b38b2e6e7f68edde964980e520a4c9f4f81`. Route prefix is `/api`; empty POST reads append `/read`. Real Chromium omitted Origin on permitted extension GET requests, which the accepted handler correctly rejected. The POST adapter carries Chromium's genuine Origin into the unchanged read handler; it never invents a caller header or weakens the HTTPS/session/owner checks. The initial HTTP-only launch ([614](https://teahub.xyz/matcha/teahub/actions/runs/614)) was corrected before final browser acceptance. Independent accounts use scrypt password verifiers, throttled same-origin auth POSTs, and revocable 12-hour opaque Secure/HttpOnly host-only sessions. Account passwords are separate from local vault passphrases. No account recovery, local key sync or old-vault migration is added. Wrapped keys are namespaced by origin and account. The separate extension Actions/Juice controls retain their explicit Tea permission/session flow. The new host has no MCP endpoint. Existing Actions `FLY_INFRA` / `CLOUD_DNS` secrets stayed inside the runner. [Preflight 610](https://teahub.xyz/matcha/teahub/actions/runs/610) proved zone reads; [611](https://teahub.xyz/matcha/teahub/actions/runs/611) created/deleted a unique TXT probe (both 200). No existing DNS records were replaced. Browser shared libraries and fonts were extracted under `/tmp`; no worker packages were installed. ## Exact human taste-test steps 1. Open `https://persimmons.app`. Follow **Sign in / create account**, choose a disposable username and a password of at least 12 characters, and create the account. The homepage should say **Signed in to Persimmons**. 2. Download the Chromium extension from the homepage, extract the ZIP, and use `chrome://extensions` → Developer mode → **Load unpacked** → extracted `persimmons-extension` folder. Verify ID `kiolgjoohhgdohcafgelhikpgkbmckbg`. Update/reload the existing same-ID extension if already installed; retain the same browser profile. Existing Tea keys remain separate. 3. In that browser profile, open an existing allowed HTTPS login form at `https://wp.phly.cloud` or `https://teahub.xyz`. This test does not change either service. Use only the existing disposable-credential test account. 4. Open the extension. Under **My vault · human accounts**, click **Connect / list this origin** and allow `persimmons.app`. Enter a distinct vault passphrase of at least 12 characters, then **Initialize / unlock My vault**. The passphrase and decrypted keys stay in the extension. 5. Enter the disposable account username/password and **Encrypt and save account**. Close and reopen the popup, reconnect, unlock, select the account, then click **Fill**. Check the two fields; the extension does not submit the form. 6. Lock the popup and verify Fill is unavailable. Unlock and delete the encrypted disposable item. Sign out on Persimmons; reconnect must fail safely. Sign in again to verify the same independent account owns the vault. 7. Agent inspection must expose only connection/Juice metadata through the existing accepted MCP inventory. Never paste passwords, vault passphrases, cookies or ciphertext into chat, logs, screenshots, or issue receipts. Keep the browser profile and vault passphrase: local keys have no recovery or sync. No existing credential or wrapped-key migration is attempted in this round. ## Repeat automated checks Run `python persimmons/build.py`, then `node --test persimmons-extension/tests/*.test.mjs` and `python -m unittest tests.test_persimmons_host tests.test_human_vault`. `tests/persimmons-host-browser.cjs` repeats the live disposable-account browser check from repository root. It requires Playwright and Chromium; optionally set `PERSIMMONS_PLAYWRIGHT_MODULE` to an installed Playwright module path and `PERSIMMONS_CHROMIUM` to the executable. Synthetic account verifiers remain; failed early browser probes' unused sessions expire within 12 hours. No real provider credentials were used. Provider mechanics: [Cloudflare DNS API](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/list/) and [Fly custom-domain validation](https://docs.fly.io/networking/custom-domain/). ## 0.3.5 golf pass — 2026-09-30 Generic popup copy now shows the current site and Connect, then Unlock, then saved accounts / Save for this site. Settings appears only after connection; Juice remains collapsed and appears only when unlocked on its supported Tea origin. Secondary token requests are lazy. Provider hints are absent elsewhere. The homepage removes unused provider/repository controls, hides Get when the extension responds, and shows Update only after a numeric version comparison against the build's version.json. CSV import is not shipped; the extension README specifies the bounded local, selective, deduplicating, ciphertext-only next step. Crypto, origin allowlists, owner/session validation and MCP access are unchanged. Validation: 38 Persimmons Python tests plus the human-vault checks pass; six Node test files cover crypto, current-origin behavior, no-reveal, connection ownership, popup visibility and homepage version states. Chromium acceptance is inconclusive: local UI navigation and a subsequent browser launch timed out on this worker. No successful real-browser result is claimed for this pass. Initial and pre-publication canonical base: bc1ad9ff8337d18da7ce52704eecbf0f6051d75c, ahead 0 / behind 0, no unrelated worktree edits. No delegation.