Uh oh!
There was an error while loading. Please reload this page.
Returning gate keeps managed truthful when the merged config is invalid (#605) - #607
Conversation
…id (#605) evaluateReturningGate returned `{ action: 'first-run', managed: false }` whenever the config was missing or failed validation, before it looked at `report.layered.hasCentral`. A machine with a central layer whose merged config stops validating (a server-side config change, client/server schema drift) was therefore reported as an unmanaged first run: the orchestrator never computed the locked set, and the picker rendered the org's rows editable. Picking one composed it into the local layer, which is exactly the central/local collision LLP 0129 #join-before-picker exists to avoid. Derive `managed` from the central layer before the early return, and compute the locked set on the first-run path when the gate reports a managed machine. Which gate or screen is shown is unchanged; the broader UX question from the issue (should an invalid config on a managed machine show the returning gate with a diagnostic instead?) stays open. Co-Authored-By: Claude <noreply@anthropic.com>
The first-run path on a managed machine now calls it too. The docstring enumerated only the join phase and the scoped re-entry. Co-Authored-By: Claude <noreply@anthropic.com>
philcunliffe
commented
Aug 4, 2026
Verdict: the fix is correct and well-targeted. One doc-honesty finding fixed, two informational notes for the maintainer.Reviewed at head Ground truth I re-derived (not taken from the PR body)Pre-fix FAIL confirmed. With Post-fix, full suite + typecheck at my head ( The bug is reachable and the fix is real. The lock actually lands. Conventions: no semicolons, no em dashes, no Findings1. Low (fixed): |
| Commit | File | Finding |
|---|---|---|
fd15e15 | src/core/cli/wizard/join.js | #1, docstring now names the third caller |
Pushed to fix/issue-605. No change to the PR body, no merge.
review) Round-2 doc-honesty follow-ups to the same class of finding round 1 fixed in join.js, in the two places it did not reach: - `computeLockedSafe` (src/core/cli/wizard/index.js) was documented as "the scoped re-entry's locked-set computation"; this PR gives it a second caller, the managed first-run path. - `ReturningGateResult.managed` (src/core/cli/wizard/types.d.ts) was documented as "true when the merged config carries a central layer". Decoupling `managed` from the merged config's validity is the whole point of this change, so the contract now says a central layer on disk, independently of whether the merge exists or validates. - The `{configExists: false, hasCentral: true}` fork test now says in place that the pairing is defensive guard coverage, not a state `collectHypAwareStatus` emits, so nobody later reads it as evidence. Doc/comment only; no behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
philcunliffe
commented
Aug 4, 2026
Round 2 verdict: the fix is correct and ready. Round 1's medium is not a blocker (evidence below). Three doc-honesty findings fixed in |
| Commit | File | Finding |
|---|---|---|
aebb3f2 | src/core/cli/wizard/index.js | computeLockedSafe doc names its second caller |
aebb3f2 | src/core/cli/wizard/types.d.ts | ReturningGateResult.managed contract decoupled from the merged config |
aebb3f2 | test/core/cli/wizard/fork.test.js | the unreachable-pairing test says so in place |
Doc and comment only, no behavior change. Pushed to fix/issue-605
(fd15e15..aebb3f2). No change to the PR body, no merge.
Residual findings
No residual findings against the code in this PR. Both round-2 findings are
fixed and verified in the committed tree, and round 1's informational item is
resolved in place.
The one open item is not a finding against #607 and should not block it:
LLP 0129 does not decide what the returning gate does on an enrolled machine
whose merged config no longer validates, so such a machine is still offered the
"Join a team" fork. This is byte-identical on master (see section 1a), the PR
improves rather than worsens the state, and #605 explicitly reserves the
decision for the maintainer. It wants a new LLP @refing 0129, tracked
separately, not a silent code change here.
philcunliffe
commented
Aug 4, 2026
Triage complete at head |
Uh oh!
There was an error while loading. Please reload this page.
…g LLP docs Conflict resolutions: - fork.js: keep master's hoisted managed derivation (truthful on the invalid-config first-run path, #607) plus the branch's single Reconfigure menu (LLP 0182); drop the retired scoped re-entry. - pick.js: master's reconfigure-from-disk seeding (LLP 0183) becomes a tier of the branch's seed concept: back-navigation selection > on-disk config > detection. Gate and menu read the merged seed; export choice and retention read back from the existing config. - walkthrough.js: keep master's regenerated-from-picks overwrite prompt. - index.js: fold master's first-run managed lock into the branch's unconditional managed lock (the else-if was unreachable). - Master's new reconfigure tests answer the branch's defaults gate with 'customize'; the managed-first-run test stays connected ('stay'). LLP renumbering (master's numbers are settled): - 0181-enrolled-default-sync-with-client-optout -> 0188 - 0183-cli-severity-colour -> 0189 - 0185-wizard-defaults-gate -> 0190 - 0186-wizard-back-navigation -> 0191 References updated repo-wide; master's own 0181/0183/0185/0186 refs untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause
evaluateReturningGate(src/core/cli/wizard/fork.js) returned{ action: 'first-run', managed: false }whenever!report.configExists || !report.configValid, before thehasCentralcheck ran. A machine with acentral layer whose merged config fails validation (a server-side config
change, client/server schema drift) was classified as an unmanaged first run:
gate.managedwas false, socomputeCentralLockedSourcesnever ran and thelocked set stayed empty.
A user who picked one composed it into the local layer: exactly the
central/local collision LLP 0129
#join-before-pickerexists to avoid.The fix (the minimum from the issue)
Two lines of behavior, no UX change:
src/core/cli/wizard/fork.js: derivemanagedfromreport.layered.hasCentralbefore the first-run early return, so thegate reports it truthfully on that path. A central layer on disk is a
property of the machine (LLP 0031), not of whether the merged config
currently validates.
src/core/cli/wizard/index.js: onaction === 'first-run'withgate.managed, compute the locked set (the same guardedcomputeLockedSafethe scoped re-entry uses) and threadmanagedinto thepick phase. The pathway stays unset, so the fork still runs exactly as
before.
Which gate or screen is shown is unchanged, and no "config invalid" diagnostic
was added. Both changed sites carry an
@ref LLP 0129#join-before-picker [implements]annotation.The design question remains open for the maintainer
The issue asks what the gate should do with an invalid config on a managed
machine, and this PR deliberately does not answer it. Still open: whether an
invalid config on a managed machine should show the returning gate with a
"config invalid" diagnostic instead of falling to the first-run path. That is
a UX call for the maintainer. This change is a pure correctness fix that stops
the central/local collision (the actual harm) without deciding it, and it does
not preclude the diagnostic option later.
Ground truth
Regression tests added: two in
test/core/cli/wizard/fork.test.js(a managedmachine with an invalid config, and with no config at all, still reports
managed: true) and one intest/core/cli/wizard/index.test.jsthat seeds areal central layer on disk and asserts the orchestrator locks
claudefrom iton the first-run path.
Pre-fix FAIL (tests on unmodified source)
Post-fix PASS (same two files)
Full suite + typecheck
Scope
Touches only
src/core/cli/wizard/fork.jsand the locked-set call site insrc/core/cli/wizard/index.js(plus their two test files). No overlap with theparallel work on
src/core/cli/wizard/pick.js/composePickerConfig(#603)or the finale/attach lane in
src/core/cli/walkthrough.js(#604).Fixes#605