fix(core)!: PX2-R — registration becomes an unlock, not a gate - #623
Merged
KnockOutEZ merged 12 commits intoSep 8, 2026
Merged
Conversation
PX brief §0a.1-3 (CEO consulting pass 2026-09-03) amends PX2's hard gate to Studio-only. Core now dispatches all ten tools unregistered on every surface — MCP tools/call, the daemon's /v1 and firecrawl-compat routes, CLI one-shots, the REPL and serve start. The requireActivation seam and src/account/* stay put; only core call-site policy flips. In place of the refusal, an unregistered install is told once what an account adds: src/account/unlocks.ts owns the list, src/account/nudge.ts owns the persisted once-only counter, and the MCP result footer, the CLI/REPL seams and both init paths render it. REST counts runs but never renders — a typed envelope has no place for prose.
…efault PX brief §0a.2 and §0a.5. `--headless` (alias `--no-input`) asks nothing: stage one mails the code to the address the human owns and stops without creating anything; stage two takes `--code` and `--marketing-consent` as flags. The served telemetry disclosure is still shown on both stages — headless does not exempt an agent from relaying the wording the human is consenting to. The interactive consent prompt reverses to `[y/N]` with a false default: consent has to be an affirmative act. Product and security email stays transactional.
…nudge Every arm that asserted a refusal is now the inverse assertion at the same seam, keeping the property each one was written for: the compat family still carries the daemon sweep because it bypasses dispatchTool, `serve` still gets its own arm because its refusal was a process.exit, and the watch-scheduler arm still exists because PX2's gate sat above it deliberately. New arms pin the single nudge: quiet before N, once on N, never again, across a server restart, never on a registered install, and never counted from a failed call. Four mutations were run red and restored — flag never burned, threshold lowered to 1, failed calls counted, activation check dropped.
Every refusal arm keeps its expensive fixture and inverts its claim: the fresh install runs its first tool and all ten over MCP with no account, the --omit=optional install runs before it registers, and the back-dated-clock arm now asserts BOTH halves of the amendment — core keeps running on an entitlement that is out of its window and out of grace, while whoami shows the surviving requireActivation seam still calls it expired. New arms cover the single nudge with its unlock list and the pinned telemetry claim, and the headless two-stage registration, including that an omitted --marketing-consent persists as false.
…default Bare-Enter consent is asserted on the wire as false, not only in local state — the account row is what a marketing send reads. Headless stage one is pinned to create nothing: disclosure then request-code, no verify, no state file. Four mutations were run red and restored: consent default flipped back to true, headless flags defaulting to yes, stage one falling through to verify, and --headless accepting a missing --email.
…blic surface README, docs/cli.md and docs/privacy-security.md all still said the ten tools needed an activated install — false since the amendment made the hard gate Studio-only, and false in the two places a reader decides whether to install at all. They now lead with what an account ADDS. The telemetry sentence is pinned rather than paraphrased: doctor imports TELEMETRY_CLAIM instead of spelling a ninth variant, and the docs carry the same words the CLI and the first-run output show. "Nothing leaves your machine" is retired where it read as an absolute; the off-switch paragraph keeps its conditional claim in wording that cannot be quoted back as one. Also documents the headless register flow and the unticked marketing default, and corrects the telemetry comment in server.ts that still described a gate returning above it.
…r exists getting-started still had a step called "Activate this install" and quoted an init hint the code no longer prints; troubleshooting had four rows and a whole section explaining a refusal core cannot emit. Both now say the opposite, and troubleshooting keeps a pointer for anyone on an older build who really is seeing that line. llms.txt said telemetry was off by default. It is on for a registered install and has been since 0.3.0 — that one was simply wrong. The two remaining absolutes (the site's "nothing leaves" closer and the config TUI's help text) now carry the pinned sentence or a claim scoped to the off state. doctor's test spells the sentence out as a literal rather than importing the constant: the clause being tested is that six surfaces say the SAME words, which an import cannot fail to satisfy.
… meets
The unlock list was pinned at e2e level only on the CLI nudge; first-run setup
was covered by a unit test that calls activationNextStepLines directly and never
exercises the code path that prints it. Composing the lines and printing them are
two claims and only one of them is what somebody installing wigolo sees, so this
adds an arm that drives the installed binary's real setup and reads its stdout —
including the negative half, that setup never quotes the deleted refusal or the
old register next-step.
Types the two diff helpers as the SDK Client instead of a hand-written
{ callTool } shape. The SDK signature is generic over the request schema so the
structural type was never assignable, and it was costing nine entries on the
tests/ type-check debt ratchet (350 against a baseline of 341).
The arm looped ten runs and asserted exactly one nudge, on the theory that a generous margin past N is stronger than counting to N. It is not, because the nudge is an install-lifetime resource and the arms share one install: the ten-tool MCP arm crosses N first, spends the nudge into a result nobody is reading for a footer, and the CLI arm then observes zero. Measured on the fixture — the install sits at successful_runs 5 / nudged true before this arm starts, so it red on a product behaving exactly as specified. The arm now resets the counter and drives the whole shape: quiet for N-1, loud on N with the unlock list and the pinned telemetry claim, quiet for N more. That is both halves of "once, never repeated", and neither half depends on how many of the previous arm's ten tools happened to succeed. Adds the matching MCP arm, because the footer is where product law 9 puts the interface for a terminal user with no plugin: N successful calls over the protocol, the footer on exactly the Nth, absent on the next, and the tool's own JSON still parseable in the first content block. Also prints the fetch error text on the registered-tools diff seeding, which asserted a boolean and told us nothing about why it was true.
…them The registered ten-tool arm red twice on browser_engine_unavailable while seeding the diff baseline, and the cause is a prerequisite the fixture never had. installTarball is npm alone, so no engine is present; the fetch router pins a host to the tier that last served it, the fixture's short pages escalate once, and a later force_refresh therefore STARTS at the browser tier with no lower-tier content to fall back to. The failure also kicked off a background download that raced everything after it. beforeAll now warms the engine and asserts it, which is what an ordinary install does at setup and the only fix that stops an arm's result depending on how far a download got. Asserted rather than best-effort for the reason rc-gate-env.ts already gives: once the gate says it runs, a missing prerequisite throws instead of reporting green about something it never exercised.
…footer's list The REPL suite replaces node:fs wholesale and stubs the activation module, which under PX2 existed to keep the gate from refusing before readline attached. §0a.1 deleted that gate; what runs on this path now is the nudge, which READS AND WRITES the counter file — i.e. the mocked fs — so the module needs its two new exports stubbed or the whole file dies on a missing mock export. Caught by the full suite, which is what it is for. The MCP footer arm now asserts the unlock LIST and the telemetry claim, not just that a footer appeared: a footer that only invited the reader to register would be the wall announced late, which is the thing the amendment removed. Both new arms proven able to fail by emptying REGISTRATION_UNLOCKS and watching each go red on the missing line, then restoring it byte-identical. resetNudgeState creates its directory, because an arm run in isolation has not run whichever surface would otherwise have created it and died on ENOENT before asserting anything.
…-fix-core-px2-r-registration-gate
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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 free
to 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.
Closes
KnockOutEZ/wigolo-studio-run#336. Reconciles core to the CEO consulting-passamendment (PX brief §0a.1–5, 2026-09-03): PX2 shipped a hard registration gate on every
surface, and §0a.1 made that gate Studio-only. Core now runs unregistered, and registration
became an unlock.
Built per §0a, not per the issue's PX2-era text — §0a overrides both, and the issue itself
says so. The
requireActivationseam and all ofsrc/account/*stay: Studio and the unlockmachinery use them. Only core call-site policy flipped.
What changed
The gate is gone from every core surface. MCP
tools/call, the daemon's/v1andfirecrawl-compat routes (the
REST_UNGATED_EXACTallowlist went with it), CLI one-shots, theREPL, and
servestart. All ten tools run on a machine that has never registered.Registration is an unlock, and one file says what it unlocks.
src/account/unlocks.tsholds the four-line list, the "runs fully without an account" sentence and the §0a.4
telemetry claim, so the MCP footer, the per-session instructions notice and both
initpathscannot drift apart. No new tool and no description growth — the 400/400 budget is untouched;
src/instructions.tsswapped one line for one line.One nudge, ever.
src/account/nudge.tscounts successful runs in<dataDir>/account/nudge.jsonand burns a flag when it hands the nudge to a surface. Countingand claiming are separate calls so REST runs — which have no channel to print prose into —
still push the counter toward a nudge the user's next CLI or MCP call renders. A failed call
counts for nothing.
wigolo register --headlessis two-stage and asks nothing: stage one mails the code andcreates no account, stage two finishes with
--code. Marketing consent defaults to untickedon both paths — an absent flag is a no.
The telemetry claim is one sentence everywhere, per §0a.4, and the absolutes it replaces
are retired. README,
docs/cli.md,docs/getting-started.md,docs/privacy-security.md,docs/troubleshooting.md,llms.txt, the site's How-it-works closer, the config TUI's helpand
wigolo doctorall said something that is now false, or said the privacy story in theirown words.
llms.txtadditionally claimed telemetry was off by default, which was simplywrong. These are factual fixes to otherwise-frozen public docs.
Evidence
Merge gate, all three, run once at the end:
PX2 RC exit gate (
RUN_PX2_RC=1, real packed tarball + real Postgres + real accountsservice): 11 of 12 arms pass, including every clause this issue owns — first-run setup
names the unlocks, all ten tools run unregistered over MCP, the nudge is quiet for N-1 /
loud on N with the list and the pinned claim / quiet for N more, registration completes
interactively AND headlessly with consent unticked, and
WIGOLO_TELEMETRY=offsends zerorequests while on sends them.
The 12th arm — the registered ten-tool sweep — reds on
browser_engine_unavailableand wasred before this branch touched the file. It is filed as
KnockOutEZ/wigolo-studio-run#521with what was ruled out, not waved through: an asserted
wigolo warmup --browserinbeforeAlldoes not fix it, which points at a product disagreement between a warmupthat reports success and a fresh process that says the engine is absent. The RC suite is
opt-in and in neither
npm testnor CI.Two RC arms were wrong, and one of those was this issue's own. The nudge arm looped
past N asserting "exactly one nudge", but the nudge is an install-lifetime resource and the
ten-tool MCP arm spends it first — measured, the install sits at
successful_runs: 5, nudged: truebefore that arm starts, so it red on a product behaving exactly as specified.It now resets and drives the whole shape.
Both new arms proven able to fail.
REGISTRATION_UNLOCKSemptied → the first-run armreds on
first-run output omitted the unlock "sync — …", the footer arm reds onthe MCP footer omitted the unlock "sync — …"; restored byte-identical afterwards.The full suite caught one more, which is what it is for: the REPL history-mode file
mocks
node:fswholesale and stubs the activation module, which now needs the two nudgeexports or the whole file dies on a missing mock export.
Scope split
The server half of §0a.5 could not be fixed from this branch:
KnockOutEZ/wigolo-accountsdefaults
consent.marketingto true when a creation request omits the field, pinned attests/integration/auth-flow.test.ts:152citing the superseded pin 8. The client fix herenarrows it —
registernow always sends an explicit boolean — butloginomits the field andso would any other client. Filed as
KnockOutEZ/wigolo-studio-run#520(ready,lane:svc-auth, Q-lane so it cannot wedge PX2), with a line inknown-issues.md.Re-verified after merging the moved
studio-handoff-coretip into the branch: the base had advanced ontosrc/cli/doctor.ts, which this PR also edits. Clean merge, and all three gates re-run green on the merged tree.