Skip to content

Dev - #320

Merged
vsilent merged 34 commits into
mainfrom
dev
Nov 3, 2026
Merged

Dev#320
vsilent merged 34 commits into
mainfrom
dev

Conversation

@vsilent

@vsilent vsilent commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

robotizeit and others added 30 commits October 19, 2026 16:10
A bake started from POST /api/admin/bake now lands on the `unstable`
channel, which no buyer gets. POST /api/admin/bake/{id}/promote (called by
the harness after a green smoke clone) moves it to `latest`; the previous
`latest` becomes `stable`. Clone and validate resolve `latest`, then
`stable`, then the newest ungated snapshot, so stacks baked before this
change (and by the `bake` binary) keep working.

- migration: baked_snapshot_channels (pointer per stack/provider/channel),
  baked_snapshots.gated, Casbin rule for the promote route
- db::bake_registry: point, state, promote (one transaction, pointers
  locked; only the current `unstable` can be promoted; a repeat is a no-op)
- `gated` keeps a bake whose smoke failed hidden after the next bake
  replaces the `unstable` pointer

Tests: tests/bake_channels.rs (written failing first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /api/admin/bake accepted any deployment whose server is in the
platform's Hetzner project. Quick Deploy buyers' servers live there too,
and sanitizing wipes the box (SSH host keys, .env, data volumes) before
snapshotting it, so a mistyped deployment_id could wreck a buyer's server
and put its data into a published snapshot.

- the deployment must belong to the caller (403 otherwise); a service
  token (admin_service) owns no box and is refused. The bake harness calls
  with the CLI token of the admin who deployed the box, so it is unaffected
- a deployment with a Managed-server usage row (Quick Deploy) is refused
  (409), even when the caller made it
- the health probe no longer follows redirects: the box chooses where they
  point, and the backend would call that address for it

Tests: tests/admin_bake.rs (another user's box, a Quick Deploy server,
admin_service; the old code accepted the first with 202) and a unit test
for the redirect, both written failing first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The smoke clone goes through POST /api/v1/deploy/clone, which serves only
latest/stable. A fresh bake sits on unstable, so the smoke could never find
it and nothing would ever be promoted.

clone takes an optional `channel: "unstable"`, for group_admin only (403
otherwise, 400 for any other channel). It resolves exactly the bake the
unstable pointer holds, with no fallback to a released snapshot. Buyers who
omit it are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every spawned test app creates its own database (~11 MB with the
migrations) and nothing dropped them. A full CI run filled the runner's
disk until Postgres panicked ("could not write to file pg_wal/...: No
space left on device") and the last few hundred tests failed with
"Failed to start test app", on every branch.

configure_database (integration and BDD harnesses) now registers each
database it creates, and an atexit handler drops them all with
DROP DATABASE ... WITH (FORCE) when the process exits. Most apps live in
a static shared by a test file, so a Rust Drop would never run; process
exit is the end of each test under nextest and of each binary under
cargo test. The drop runs on a fresh thread: at exit the main thread's
thread-locals, which tokio needs, are gone.

Regression test: tests/test_databases_are_dropped.rs runs a child test
process that spawns an app, then checks its database is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests: a test process drops the databases it created
BUGS: stacker install writes no .env (.env.example defaults missing)
…ovided keys flagged as regenerated (#14)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bake only the caller's own build box; no redirects in the health probe
BUGS: bake finalize order (#13), provided-key conflict message (#14)
Bake channels: a bake reaches buyers only after its smoke clone passes
Every auth method was instrumented with the whole ServiceRequest as a
span field. Its Debug output lists every header and the query string, so
each request wrote bearer, cookie, agent and MCP query tokens into the
JSON log at info level; anyone reading the logs could replay them. The
spans now record no arguments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TKZy6f5shPzM9WLdswH6f
(cherry picked from commit 61517cee0e698fc3245ed3d2f64df9c97e1fb291)
Keep credentials out of the auth spans
`channel: "unstable"` was allowed only for role `group_admin`. `root`
inherits `group_admin` in Casbin, so the same admin who can POST
/api/admin/bake got 403 on the smoke clone of that bake (seen on
production), and the bake could never be promoted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ress

fetch_user returned a shared `test_user_id` (role group_user) for any
bearer token when the request to the auth server failed and auth_url was
loopback or contained "localhost" - which Settings::default() is. Any
string in `Authorization: Bearer` then acted as that one account. The
branch was a test convenience compiled into production; the test harness
runs its own /me mock and never needed it.

Now it is the same error as for any other address, and the middleware
refuses the request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
get_configuration fell back to Settings::default() whenever the
configuration failed to deserialize, silently: auth at localhost, the
database at 127.0.0.1. A typo in a production configuration.yaml would
start a service on those defaults.

A configuration that is there but does not parse is now an error. With
no configuration file at all the defaults still apply: CI and the test
harness run that way (configuration.yaml.dist is not read - `with_name`
does not recognise the `.dist` extension).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quick Deploy clone: a root admin may smoke-clone an unpromoted bake
OAuth: an unreachable auth server refuses the token, whatever its address
Configuration: a file that does not parse stops the service
The marketplace vendor profile accepted a free-form avatar_url and served
it verbatim on the public author page, so a vendor could put an arbitrary
external image URL on a page other users visit. avatar_url is now always
derived from the account email via Gravatar (d=identicon), the client
value is ignored, and a migration NULLs non-Gravatar values already
stored (the next profile save fills in the derived URL). website_url,
rendered into an <a href>, now rejects non-http(s) schemes.
@vsilent
vsilent merged commit 5017575 into main Nov 3, 2026
22 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants