Uh oh!
There was an error while loading. Please reload this page.
fix(devx): re-pin the publish smoke to the declared first-run contract - #14255
Merged
Conversation
The packed-tarball smoke asserted that the first POST /auth/sign-up/email after `objectstack dev --fresh` returns 200 — an expectation authored before #11739/#11767 made `invite_only` the default audience posture. It is not the first account that reaches that probe. `--fresh` seeds a dev admin in-process at boot through the real signUpEmail pipeline, and THAT creation is the zero-user one the bootstrap bypass admits. The smoke's own sign-up is the SECOND self-serve account, which invite_only refuses with SELF_REGISTRATION_CLOSED + 403 — correctly. The release candidate had therefore never once passed this gate with the posture default aboard. Per the maintainer-approved ruling (option A, director batch #23): - assert the seeded admin can SIGN IN, which is the packed install's proof that the bootstrap bypass really fired; - assert the uninvited second self-serve sign-up is REFUSED, pinning both the 403 and the envelope code so an accidental widening of the unauthenticated surface reds the RC instead of shipping; - obtain the non-admin probe user through the operator path (the invitation carve-out) before the existing session/CRUD probes. Behaviour only — no vendor-internal symbol is pinned, and no auth or audience runtime code is touched. The posture is READ from the artifact's own public /auth/config rather than hard-coded, because the two smoke modes install different products: `registry` smokes the last PUBLISHED release, and plugin-auth 17.2.0 (2026-08-23) predates #11767 (2026-08-25). In `pack` mode the advertised posture must BE invite_only; in both modes enforcement must match what the artifact advertises. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
baozhoutao
marked this pull request as ready for review
September 1, 2026 16:42
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#14000
Re-pins the packed-tarball smoke to the first-run contract the platform actually declares, per the maintainer-approved ruling (option A, director batch #23, comment 5494456135). No auth or audience runtime code is touched — option B is excluded by the ruling, and the product behaviour under test is correct as it stands.
What was wrong
scripts/publish-smoke.shasserted that the firstPOST /auth/sign-up/emailafterobjectstack dev --freshreturns 200. That expectation was authored before #11739/#11767 madeinvite_onlythe default audience posture, and it is not the first account that reaches the probe:objectstack dev --freshseeds a dev admin in-process at boot (maybeSeedDevAdmin, plugin-auth) through the realsignUpEmailpipeline, sharing theisHumanUserRowpredicate with the audience gate's bootstrap bypass. That creation is the zero-user one the bypass admits. The smoke's own sign-up is therefore the second self-serve account, whichinvite_onlyrefuses withSELF_REGISTRATION_CLOSED+ 403 — correctly. The release candidate had never once passed this gate with the posture default aboard (~7 days red, measured in the issue thread).The probe sequence
GET /auth/get-session(anonymous)GET /auth/config(anonymous)packmodefeatures.audiencePosturemust beinvite_onlyPOST /auth/sign-in/email(seeded dev admin)POST /auth/sign-up/email(uninvited second account)code == SELF_REGISTRATION_CLOSEDPOST /auth/organization/invite-member(as the admin)status == "pending"— the operator pathPOST /auth/sign-up/email(the invitee)POST /auth/sign-in/email(probe user)GET /auth/get-session(signed in)Ruling points 1, 2 and 3 are probes 3, 4 and 5–6. Behaviour is pinned throughout — an HTTP status plus a
codethis repo owns and publishes. No vendor-internal symbol is asserted anywhere, which is the wiring shape that made #11767's breakage possible.Two decisions worth reviewing
The operator path is the invitation carve-out, not
admin/create-user. The ruling names either.POST /auth/admin/create-userneeds better-auth'sadminplugin, and a scaffolded project does not get it by default —admin: pluginConfig.admin ?? scimEffective(auth-manager.ts), and the blank template declares noauthconfig at all — so that route would answer 501NOT_IMPLEMENTEDon the very artifact under test. The organization plugin is on by default (organization: pluginConfig.organization ?? true), and ADR-0081'sensureDefaultOrganizationexists precisely soinvite-memberhas an active org on a single-org install. So the carve-out is the operator path a fresh install actually ships.The posture is read from the artifact, not hard-coded — because the two smoke modes install different products.
packsmokes this repo's tree;registrysmokes the last published release, whose contract this tree does not define.@objectstack/plugin-authlatest is 17.2.0, published 2026-08-23; #11767 landed 2026-08-25. Hard-coding the RC's posture would have turned the weekly registry canary red — and auto-filed a "fresh install is broken" issue — over a release behaving exactly as its own contract declares.So the script reads
features.audiencePostureoff the public/auth/configand asserts that enforcement matches what the artifact advertises. Inpackmode the advertised posture must additionally beinvite_only, which is where the ruling's closed-by-default pin bites: widening the default reds the RC before any behaviour probe can report the widening as a cheerful 200. The legacy open-registration branch is reachable only byregistrymode against a pre-#11739 release and retires itself the first time a published release carries the posture.Ruling point 4 — option C's direct bootstrap probe: declined, and why
Folding in a
--no-seed-adminvariant was conditioned on being cheap. It is not, and it is also redundant:--no-seed-adminis a real flag, but exercising it means a secondobjectstack dev --freshboot — the slowest step in the job — plus restructuring machinery that is single-boot throughout: oneSERVER_PID, oneDEV_TMPDIR(whose per-run uniqueness is what lets the script prove the server it probes is its own), oneSERVER_LOGthat the closing error-scan reads, and a cleanup trap keyed on all three.signUpEmail; if the bypass stops firing (the feat(spec,auth)!: one declared audience posture for self-registration — invite_only|email_domain|open, default invite_only #11767 defect), the seed never lands and probe 3 fails — naming the real defect directly rather than leaving it to be inferred from a sign-up refusal downstream. C's marginal information is only that the bypass admits a self-serve first account rather than the seed's first account.Landing window — when this fix reaches the tested tree
main, butpack-smoketests the release branch.publish-smoke.yml'spack-smokejob runs onworkflow_runafter each Release run and checks outchangeset-release/mainwhen an open release PR exists, so merging this does not turn the RC green by itself. The fix reaches the tested tree at the next release-branch re-cut (the next Release run that re-cutschangeset-release/mainfrom a main containing this commit).⛔ Judge the result by the commit status on the release-branch head, never by the check-run attached to a main SHA — the card's own trap, measured biting three seats, and it will bite this PR's reviewers the same way if they read it the other way.
Verification
See the report comment on #14000 for the full measurement record, including what was measured locally and what is left to CI.
skip-changeset: this PR publishes nothing from any package — the diff is one CI driver script.Generated by Claude Code