Divvent

For Divvent’s own team. It is generated from the operations document in our repository, which is the one to edit. If you use Divvent, the help centre is for you.

5. Trials, and seeding one from production

Every application has one permanent environment, prod. Testing happens in trials beside it — optional, named, as many as a customer opens, each a Worker, database, bucket and URL of its own, each lasting until it is closed (STAGE2.md §1, DESIGN.md §2). A trial runs on synthetic data unless it is seeded from production on approval, below.

Opening a trial, and deploying into it

The customer does it from the portal: Releases → Try 0.2.0 in a new trial, a form with a name, which opens the trial and queues the latest revision into it. An operator can open or close one on a customer's behalf from Console → Tenants → a tenant → Open trials. Opening writes a row and deploys nothing.

What deploys it is the runner, divvent deploys, which passes --instance <application>--<trial>: trial try-b of heatco is heatco--try-b-prod (the Worker and the D1) and heatco--try-b-prod-media (the bucket). The two hyphens are the mark of a trial, and no slug or trial name may hold two together: with one, application heatco-try-b's production and heatco's trial try-b were the same Worker (#53). A trial's first deploy is two: production's revision onto the empty database, then the change — so the trial runs the very migration production will, and the promote compiles to the same artifact. By hand, the same thing is

divvent deploy <modelDir> --env prod --tenant heatco --instance heatco--try-b

Promoting a trial puts the trial's exact artifact on production: the portal's Promote try-b to production queues a promote carrying the trial's deployedArtifact, and divvent deploy --artifact <hash> refuses if the runner's compile hashes to anything else. Deploying straight to production is the other road, and its undo is the pre-migration bookmark (§3).

Closing a trial is refused while a revision is still running: the row is the only thing that names its Worker, database and bucket, so closing it first would leave them with nothing to find them by. Tear it down (§6) and then close it — the refusal prints the exact divvent destroy line. Closing it also forgets its signing secret, so a trial opened again under the same name gets a new one.

Seeding a trial from production

divvent snapshot restore <modelDir> --to-instance heatco--try-b --approve

--from and --to both default to prod: the trial is seeded from production and runs beside it, so the environment is the same on both sides and --to-instance is what differs. Needs DIVVENT_OPERATOR set, so the record says who. It runs inside the tenant's own account — the data never transits the control plane and no operator reads it — and takes an undo bookmark on the destination before overwriting it.

Deploy the trial first. The restore copies data; it does not create a schema, and it refuses a destination with no database. It replaces the trial's database with production's, migration ledger included, so a trial whose revision needs a migration is deployed again afterwards — which runs that migration over the copy, and is the reason to seed at all. **_divvent_meta is not copied**: it says whose a database is and holds that deployment's media token, pins and bookmark, and production's in the trial made the trial production's — refused on its next deploy and its teardown.

Production's sessions come across and open nothing. The copy carries the session rows, but a trial signs with its own BETTER_AUTH_SECRET (§1.5), so a cookie production signed does not verify there. Anybody trying the trial signs in to it.

It will refuse, and each refusal is the feature:

The blocked sets are asserted against DESIGN.md §2's own sentence, so widening the code without revisiting the paragraph fails a test.

Reusing a database instead of copying into one

divvent deploy <modelDir> --env prod --tenant heatco \
  --instance heatco--try-b --reuse-database heatco-prod

Only where nothing needs migrating, and the deploy checks rather than trusting: if the named database is missing a step the revision needs, it refuses in preflight, before anything exists, and names the restore above. A trial that migrated the database it was pointed at would alter the schema of the data it was only meant to look at — successfully, because the migration is correct for the trial's revision and wrong for everything else bound to that database.

Two other refusals, each for its own reason:

The bucket is never reused. An object a trial writes belongs to the trial.

Nor is the ledger. A reusing trial writes nothing into the database's _divvent_meta but what is keyed by its own Worker's name — its realtime classes, and its media token id as media.tokenId.<trial worker>: the pins, the deploy time, the owed contracts, the snapshot bookmark and media.tokenId there are the owning deployment's, and a reusing trial used to take the last for its own, replace it and revoke it. Its teardown revokes its own token from that key, and needs to know which database it was bound to: from its deploy record on this machine, or destroy --reuse-database <database>. Without either, a trial Worker still holding a media credential is refused before anything is deleted, rather than deleted with its token left live. A database another company records as its own is refused, reused or not.

Every deploy now asks whether the recorded media token is live, not only whether the Worker carries the secret names, so a production whose token an earlier reusing trial revoked gets a new one on its next deploy.

It is not read-only, and cannot be made so. D1 has no read-only binding, so a trial sharing a database can still write rows into it. What the rule buys is that the schema is untouched and the sharing is deliberate — the deploy says so on every run that takes this path. A trial that must not write at all gets a database of its own and a restore.

---