Finish Persims consolidation
This commit is contained in:
1 parent
c755004684
commit
447eeaeba4
1 file changed
+144
@@ -0,0 +1,144 @@
|
||||
# Dedicated Persimmons identity and vaults
|
||||
|
||||
Persimmons keeps its existing product host and extension crypto. Signup provisions
|
||||
a non-admin user in **git.persimmons.app**, a dedicated Gitea on the Fly app
|
||||
**persimmons-gitea** with a single 1 GiB **persimmons_gitea_data** volume in iad.
|
||||
It does not use Teahub Gitea for product identities or repositories.
|
||||
|
||||
The independent `persimmons-launch.yml` Actions operator holds FLY_INFRA and
|
||||
CLOUD_DNS. It deploys the dedicated Gitea, establishes DNS/HTTPS, checks
|
||||
`/api/healthz` including database/cache, and runs a fixed bootstrap program via
|
||||
Fly Machines exec. The bootstrap creates the provisioning admin before any product
|
||||
signup, with a random unrecoverable login password and an API token scoped to
|
||||
`write:admin,read:user,read:repository`. A root-only 0700 directory/0600 file on
|
||||
the dedicated Gitea volume makes bootstrap resumable. The operator captures that
|
||||
credential response and stages it with `fly secrets import` stdin exclusively on
|
||||
**persimmons-app** as PERSIMMONS_GITEA_TOKEN. No provider credential is sent to
|
||||
product or Gitea runtime; no token enters argv, receipts, logs, source, or clients.
|
||||
Public Gitea registration and Actions are disabled; repositories are private.
|
||||
|
||||
The application has no generic admin proxy. It uses only fixed user provisioning,
|
||||
identity lookup and create-repository endpoints. Password login uses Basic auth
|
||||
over pinned HTTPS to Gitea `/api/v1/user` with redirects refused. Passwords and
|
||||
Gitea user tokens are not retained. Opaque 12-hour HttpOnly/Secure sessions contain
|
||||
only a digest, immutable Gitea ID, login, owner `pg-<id>`, and expiry in SQLite.
|
||||
Admin identities cannot sign in to the product. Username/email conflict is 409.
|
||||
Generated email aliases are private placeholders; no email delivery/recovery is
|
||||
implemented. Gitea account renames require a fresh product login for named vaults.
|
||||
Existing product sessions otherwise remain valid until logout or their expiry.
|
||||
|
||||
The first database open upgrades the disposable local schema to version 2. Under
|
||||
one transaction it drops only old users/sessions/throttle tables and the local
|
||||
`/data/human-vault.sqlite` human_items table. It never resets a version-2 store,
|
||||
and rejects unknown future schema versions. This is the explicitly authorized
|
||||
fresh-account reset. Existing browser keys use a different owner namespace and
|
||||
are not imported. Client-side wrapped keys and encrypted credential capture/fill
|
||||
remain unchanged, as do accepted server-visible origin/username metadata.
|
||||
|
||||
`GET /api/vaults` lists only recorded creations belonging to the authenticated
|
||||
user. `POST /api/vaults` accepts exactly `{name}` from the product origin with the
|
||||
Persimmons request header; caller-supplied owner, repo ID, or privacy is rejected.
|
||||
Whitespace becomes hyphens; valid names otherwise retain spelling. The service
|
||||
checks the live Gitea user ID before creating on behalf of that user, requests
|
||||
`private=true,auto_init=false`, and verifies both the creation response and a fresh
|
||||
GET for the same immutable repo ID, owner ID/login, privacy, and emptiness before
|
||||
recording it. Duplicate/existing repositories are conflicts, never imported.
|
||||
Lists recheck identity, repo ID, ownership and privacy; transferred, replaced or
|
||||
public repositories fail closed. An interrupted create can leave an empty private
|
||||
repo without a local record: retries conflict instead of silently adopting it.
|
||||
|
||||
Named vaults appear below Master vault. Empty repositories are valid. Juice,
|
||||
Actions/scoping, and credential rotation remain future work; retained legacy code
|
||||
and documentation are preserved. New named repositories can later host scoped
|
||||
credentials without changing Master-vault ownership or encryption.
|
||||
|
||||
Validation: `python persimmons/build.py`, standalone host/Gitea/UX tests, extension
|
||||
Node tests, operator HTTP acceptance, then `tests/persimmons-host-browser.cjs`
|
||||
against live UI/API. The live browser harness creates a fresh non-admin user,
|
||||
creates `work-test` through the modal, verifies it independently on Gitea, rejects
|
||||
a duplicate, links/initializes Master vault in the real extension, captures/fills
|
||||
without submitting, deletes the synthetic ciphertext, and verifies logout.
|
||||
Synthetic test identities and empty private repositories remain as evidence;
|
||||
passwords, sessions, and plaintext never appear in receipts.
|
||||
|
||||
Provider references: [Gitea CLI bootstrap and scoped tokens](https://docs.gitea.com/administration/command-line/),
|
||||
[Gitea create user API](https://docs.gitea.com/api/1.23/operations/admin-create-user/),
|
||||
[Gitea create repository on behalf of a user](https://docs.gitea.com/api/1.23/operations/admin-create-repo/),
|
||||
and [Fly Machines API](https://docs.fly.io/machines/api/machines-resource/).
|
||||
|
||||
## Accepted live deployment — 2026-10-01
|
||||
|
||||
**Done.** Deployed source **`2f8fcc8208205b30eaefc8d49ac1991c63ad47a1`**.
|
||||
[Persimmons launch run 1003](https://teahub.xyz/matcha/teahub/actions/runs/1003),
|
||||
job **1005**, completed successfully through the existing independent Actions
|
||||
operator. Provision/deploy/HTTP acceptance took **142.519 seconds**, finishing at
|
||||
04:28:40 UTC. Every operator deploy was preceded by an explicit fetch with
|
||||
HEAD = origin/main = source above, ahead 0 / behind 0 / dirty false. Initial base
|
||||
was `6580c3380554bb1ca38269a1883ec86b7330bd99`. No delegation occurred.
|
||||
|
||||
| Surface | Fly app / machine | Persistent volume | Image digest |
|
||||
| --- | --- | --- | --- |
|
||||
| `https://persimmons.app` | `persimmons-app` / `48e729dc5796e8`, release 28 | `vol_rkgqyxm5k2gxm064` (`persimmons_data`, existing 1 GiB) | `sha256:b414c01a4f70101fd39d35a81475f67494b69df4a38ef2d7234068d1dcc2ec96` |
|
||||
| `https://git.persimmons.app` | `persimmons-gitea` / `0800251ae09308` | `vol_r68lo8o2kwy38md4` (`persimmons_gitea_data`, new 1 GiB) | `sha256:e395e81c7ceb898179fe14d277b4650dc31c74dd94784c05de14446d212847a4` |
|
||||
|
||||
Both machines are in iad, one shared CPU and 512 MiB each. Dedicated Gitea image:
|
||||
`registry.fly.io/persimmons-gitea:deployment-01M3TVDC6PTEF627XDF3VFW1WJ`;
|
||||
product image:
|
||||
`registry.fly.io/persimmons-app:deployment-01M3TVGTF7VZ6HXS8BM85CZ3AW`.
|
||||
Image labels record the exact source above.
|
||||
|
||||
Cloudflare DNS-only records for `git.persimmons.app`: A **66.241.124.151**, AAAA
|
||||
**2a09:8280:1::1a2:6e84:0**, TTL 60. Valid HTTPS was independently exercised.
|
||||
Existing apex records remain A **66.241.124.82**, AAAA
|
||||
**2a09:8280:1::1a0:f8e1:0**. Gitea `/api/healthz` returned 200 and `pass` for both
|
||||
`database:ping` and `cache:ping` in Actions and independently at 04:29:46 UTC.
|
||||
Its public `/user/sign_up` returns a 200 notice saying registration is disabled;
|
||||
it does not offer user-registration inputs. Bootstrap completed automatically;
|
||||
no manual administrator step was required.
|
||||
|
||||
Live API acceptance created **probe-e84a6b84644740fda0d1**, immutable Gitea ID **2**,
|
||||
`is_admin=false`; its **probe-e84a6b84644740fda0d1/work-test**, repo ID **1**, was
|
||||
independently read using that user's own credentials and verified private/empty.
|
||||
Duplicate creation returned 409. Registration/login were 200, encrypted item
|
||||
creation 201, persisted read after logout/login 200, deletion/logout 200. The
|
||||
synthetic ciphertext and active session were removed.
|
||||
|
||||
A fresh Chromium **153.0.8010.12** profile then exercised the actual live page,
|
||||
without source/UI interception. Browser signup created
|
||||
**browser-04780a06f4a43a72**, Gitea ID **3**, `is_admin=false`. The **+ New vault**
|
||||
modal created **browser-04780a06f4a43a72/work-test**, repo ID **2**. Its name/owner
|
||||
and “Private · Empty vault” card appeared below Master vault; an independent
|
||||
Gitea API read confirmed owner ID 3, private=true, empty=true. Repeating the modal
|
||||
showed the duplicate-repository conflict.
|
||||
|
||||
The same browser linked and initialized Master vault using the real pinned
|
||||
extension **0.4.8** (`kiolgjoohhgdohcafgelhikpgkbmckbg`), captured a synthetic login
|
||||
at the real `https://wp.phly.cloud/wp-login.php`, locked/unlocked, explicitly filled
|
||||
both fields with **zero POST submissions**, deleted the synthetic item, and
|
||||
confirmed logout clears the extension connection. Homepage and popup lock state
|
||||
agreed. The harness pregrants the exact Persimmons extension host permission;
|
||||
it does not claim to click Chromium's native permission prompt. The disposable
|
||||
profile was removed. No existing human account or provider credential was used.
|
||||
|
||||
Local checks: **25 Python tests and 18 Node tests passed**. Actions repeated the
|
||||
standalone host/Gitea/UX contract suite (**20 Python tests**) and **18 Node tests**
|
||||
before provisioning. Coverage includes Gitea signup/login/session, duplicate user,
|
||||
Master-vault continuity, private/empty/owner checks, duplicate/no-import behavior,
|
||||
wrong-owner and replaced-repo rejection, request scoping, admin sign-in rejection,
|
||||
credential non-disclosure, one-time bounded reset, package/crypto/capture/fill,
|
||||
and the unchanged no-reveal MCP boundary. A pre-existing Node assertion expecting
|
||||
removed Juice popup controls was updated to the already-accepted human-only popup;
|
||||
legacy implementation and rotate/scoping documentation remain intact.
|
||||
|
||||
Both product URLs returned current health/source and nonempty UI/assets. The live
|
||||
app JS and extension ZIP match built candidate bytes exactly:
|
||||
|
||||
- app.js SHA-256: `706c12437ce1e53723aa54e39c861632477923cf41e32a71c5bdf0b3319c6972`
|
||||
- extension.zip SHA-256: `2ac21e6ac547073d8574ab99fd919645456085bcadf7a970a5ce06033d97097a`
|
||||
|
||||
The reset ran only against Persimmons' disposable local identity/session/human
|
||||
store when the new health check opened schema version 2. Teahub infrastructure,
|
||||
Gitea data, and unrelated apps were not changed. The two synthetic non-admin users
|
||||
and their empty private repositories remain as acceptance evidence. No Juice
|
||||
implementation was added. This receipt-only follow-up does not require another
|
||||
runtime deployment; the runtime/image source SHA is the one recorded above.
|
||||
Reference in new issue
Block a user