ReviewOS

also looking at this

stacks/ts-cloud

fix(deploy): keep SQLite across deploys, and preflight an attached host's services

#174
Merged glennmichael123 wants to merge fix/sqlite-shared-path-and-attach-preflight into main
16 files +916 -12

Closes #172. Closes #173.

#172 — a SQLite database in a release directory is discarded on the next deploy

sharedPaths already exists for this and its documentation describes this exact case, but it defaults to ['.env']. So on a live box, current/database/stacks.sqlite is a regular 900 KB file and shared/ holds only .env — one deploy from being orphaned in a pruned release, with nothing warning about it.

The engine is the one thing ts-cloud can infer without being told: the deploy already resolves DB_CONNECTION/DB_DATABASE into the env it writes to the box.

  • DB_CONNECTION=sqlite + a release-relative DB_DATABASE → the file is added to the site's shared paths automatically, and the deploy log says so.
  • DB_CONNECTION=sqlite + no DB_DATABASE (an app defaulting its own path internally) → warn. Guessing the filename would share a path the app may never write while reporting the data as safe.
  • An absolute path → left alone. It already survives; a deploy replaces the release, not the filesystem around it.
  • Already declared, including the SharedPathSpec form two sites use to share one database → left alone. The operator's entry wins.

Both the server-app and PHP/Laravel builders get it, so it cannot drift between them.

Two ways the fix could itself have lost data, closed first

  • Existing state. ts_cloud_adopt_shared already copies the live release's real file into shared/ the first time a path becomes shared (-wal/-shm sidecars included). That covers the statushq box as it stands.
  • A site's first deploy. There is no live release to adopt from, so the empty placeholder would have replaced whatever the artifact shipped — an app shipping a seeded database would come up empty on the deploy that created it. buildLinkSharedPaths now seeds a still-empty shared file from the release's own copy. Narrow on purpose: regular non-empty file in the release, shared target still zero bytes (what a placeholder looks like, and what no real SQLite database ever is). It can only put content where there was none.

#173 — attach mode doesn't check the host, and hides the SSH stderr

Preflight the host. Attach mode provisions nothing by design, so managedServices in an attached config is a claim about the HOST, not a request. Before anything is built on the owner's services, the deploy probes the box and stops with the incompatibility named:

This project declares managedServices.postgres, but 'uptime-status' has no
postgres on 203.0.113.10. Attach mode does not provision services on the
owner's box, so nothing will install it. Point the app at an external service,
or ask the owner of 'uptime-status' to add it to their own config and
re-provision.

A service counts as present when its binary is on PATH or something is listening on its port — either alone, because a stopped service and an engine installed outside pantry are both "the owner has this". A probe that returns no answer never blocks the deploy: preflight, not gate.

Surface the remote stderr. formatSshFailure already captured and redacted it into perInstance, and then both drivers replaced it with a fixed sentence. Callers report result.error and nothing else, so the explanation was captured and then dropped — no flag, not even --verbose, could print it. summarizeRemoteFailures folds the failing instances' output into the error itself (tail-first, since that is where a script names its failure), for SSH and SSM alike.

Verification

bun test — 4068 pass, 0 fail. Both commits typecheck and pass on their own, checked in a separate worktree. Lint clean (the remaining pickier warnings are pre-existing, in files this branch does not touch).

New coverage: sqlite-shared-path.test.ts, managed-services-probe.test.ts, remote-failure.test.ts, plus the first-deploy seed guard in shared-paths.test.ts and four attach-preflight cases in compute-deploy.test.ts (refuses before uploading anything; proceeds when the host provides it; does not block on a silent probe; probes nothing for a project that owns its box).

Docs: docs/config.md gains a "State that must survive a deploy" section and an "It cannot install services on the owner's box" section under attach mode; SiteConfig.sharedPaths documents the SQLite inference.

16 changed files on the files tab, with 0 review threads.