diff --git a/scripts/publish-smoke.sh b/scripts/publish-smoke.sh index f8e0cef579..4ef12a1e0c 100644 --- a/scripts/publish-smoke.sh +++ b/scripts/publish-smoke.sh @@ -26,14 +26,64 @@ # Both modes then run the scaffolded project's own `build` script and assert it # exits 0 — the step section 1b explains, and the one this gate used to lack. # -# Both modes then boot `objectstack dev --fresh` and assert: -# - GET /api/v1/auth/get-session → 200 (anonymous) -# - POST /api/v1/auth/sign-up/email → 200 -# - POST /api/v1/auth/sign-in/email → 200, session established +# Both modes then boot `objectstack dev --fresh` and assert the DECLARED +# first-run contract (see "the first run this asserts" below): +# - GET /api/v1/auth/get-session → 200 (anonymous) +# - GET /api/v1/auth/config → 200, and in `pack` mode the +# advertised audience posture +# IS the declared `invite_only` +# - POST /api/v1/auth/sign-in/email → 200 (the SEEDED dev admin) +# then, under a closed posture (always in `pack` mode — see the mode note at +# the `/auth/config` probe for why `registry` mode reads it rather than +# assuming it): +# - POST /api/v1/auth/sign-up/email → 403 SELF_REGISTRATION_CLOSED +# (an UNINVITED second account) +# - POST /api/v1/auth/organization/invite-member → 200 (the operator path) +# - POST /api/v1/auth/sign-up/email → 200 (the invitee, admitted +# by the invitation carve-out) +# - POST /api/v1/auth/sign-in/email → 200, session established # - REST CRUD on the scaffolded object (POST/GET/PATCH/DELETE /api/v1/data/…) # - zero error/fatal log lines (specifically the #3091 signature: # "Failed to register OIDC discovery routes") # +# ── the first run this asserts (#14000) ───────────────────────────────────── +# +# Until #14000 this script asserted that the first `POST /auth/sign-up/email` +# after `objectstack dev --fresh` returns 200 — an expectation authored before +# #11767/#11739 made `invite_only` the platform's DEFAULT audience posture. It +# is not the first account that reaches this probe: `objectstack dev --fresh` +# seeds a dev admin IN-PROCESS at boot (`maybeSeedDevAdmin`, plugin-auth) via +# the real `signUpEmail` pipeline, and THAT is the zero-user creation the +# bootstrap bypass admits. The smoke's own sign-up is therefore the SECOND +# self-serve account, which `invite_only` refuses — correctly — with +# `SELF_REGISTRATION_CLOSED` + 403. +# +# So the gate had been red for ~7 days on a product that was behaving exactly +# as declared, and the release candidate had NEVER once passed this smoke with +# the posture default aboard. Ruling (issue #14000, maintainer-approved, +# director batch #23) — re-pin the smoke TO the declared contract, and do not +# touch auth/audience runtime code: +# +# 1. the seeded admin can SIGN IN ⇒ the bootstrap bypass really +# fired inside the packed install +# (it is the only thing that could have created that account); +# 2. the second self-serve sign-up is REFUSED with the envelope `code` +# SELF_REGISTRATION_CLOSED and status 403 ⇒ the closed-by-default posture +# is now load-bearing in CI: an accidental widening of the unauthenticated +# surface turns the RC red instead of shipping; +# 3. the non-admin probe user arrives through the OPERATOR path (the +# invitation carve-out) before the session/CRUD probes run as before. +# +# ⛔ Assert BEHAVIOUR, never a vendor-internal symbol. Wiring a release gate to +# better-auth's own internals (dist line numbers, private helpers) is the shape +# that made #11767's breakage possible; every assertion here is an HTTP status +# plus a `code` this repo owns and publishes. +# +# ⛔ Do NOT "fix" a red here by relaxing an assertion back toward 200. The whole +# reason this gate exists (#3091) is that 15.1.0 shipped with every fresh +# project's auth endpoints broken because in-repo settings hid the user's real +# first run. A refusal that this script asserts is a refusal a user gets. +# # better-sqlite3 is an optionalDependency of @objectstack/driver-sql: if the # runner cannot build the native addon the install still succeeds and the # runtime falls back to the WASM sqlite driver (#2229) — the smoke must never @@ -717,6 +767,19 @@ COOKIES_USER="$SMOKE_ROOT/cookies-user.txt" COOKIES_ADMIN="$SMOKE_ROOT/cookies-admin.txt" BODY="$SMOKE_ROOT/body.json" +# The credentials `objectstack dev --fresh` seeds in-process, spelled once. +# Defaults live in plugin-auth (`maybeSeedDevAdmin`) and are echoed by the CLI +# startup banner (packages/cli/src/utils/format.ts). This script deliberately +# passes NO --admin-email/--admin-password: the values a user is handed on a +# fresh install are what this gate must exercise. +ADMIN_EMAIL="admin@objectos.ai" +ADMIN_PASSWORD="admin123" +# The non-admin probe user. Spelled once because the operator invitation and +# the sign-up that redeems it MUST address the same mailbox — the carve-out is +# a lookup for a pending `sys_invitation` row on exactly this address. +USER_EMAIL="smoke@example.com" +USER_PASSWORD="Sm0ke-Pass!42" + # probe