LLP 0279: the wizard's question lanes record with the config, and an accepted default keeps a standing answer - #901
LLP 0279: the wizard's question lanes record with the config, and an accepted default keeps a standing answer#901philcunliffe wants to merge 4 commits into
Conversation
…accepted default keeps a standing answer (#883) The sync lane wrote `client-sync.json` and the new-folder lane wrote `folder-ask.json` the moment each was answered, while the config write they belong to was deliberately deferred past them (LLP 0190 #commit-point). Decline the overwrite confirm that follows and the run printed "keeping existing config at ..." and exited 1 with two policy stores already rewritten. LLP 0190 carved out that exception in one sentence, on the grounds that a store entry for a source whose config never lands is inert and errs toward under-sync. It covers one direction of one lane: the sync store is an editor (a re-run can *remove* an opt-out, which over-syncs), its absence is LLP 0188 #migration's own marker so even the empty write stamps the machine, and the new-folder lane arrived later and inherited the shape without the argument. Both lanes now take `deferWrite` and hand their write back as `commit`, which the orchestrator runs immediately after `commitWizardPickedConfig` succeeds and not at all when it does not. The statements stay with their lanes (LLP 0201 #narrate binds each to its own block), so a refusal now names the answers it dropped alongside the config. The held writes are re-assigned per pass, never accumulated, so back navigation writes only the last answer. Second defect, same pair of lanes: the express gate's folder arm recorded DEFAULT_FOLDER_ASK_MODE rather than the standing mode, so a user with `hyp policy folders ask` who re-ran `hyp init` and accepted the gate lost the per-folder question. The sync lane's auto-accept arm already returns the opt-outs already standing. The folder arm now records `before` - the same value its asked screen puts on `default:` - and the gate's one line of consequence follows it ("new folders keep asking" on a machine set to `ask`). Testing: `node --test test/core/cli/wizard/*.test.js` 268 pass; five new tests, each failing on master first (the orchestrator-level abort test stamped `client-sync.json` and flipped `ask` to `sync`; the express test reset the standing answer). `npm test` 4490 pass / 0 fail; `npm run typecheck` clean; `hyp smoke walkthrough_picker_to_first_query` ok. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
0268 is already claimed by fix/issue-838 (PR #847), which is older, so this branch yields the number. 0279 is free across master and every open branch. Mechanical renumber only: no content change (LLP 0156). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(review of #901) Two follow-ups from review of the deferred-lane-write change: - `sync_scope.js` handed back a `commit` that could throw. It now runs after the config is on disk, with the new-folder lane's held write, the configure phase, and the finale still owed, so a throw abandoned the run half-done with a stack trace. The deferred commit now warns and leaves the standing store alone, the same contract the new-folder lane's deferred write already had. The inline write, which still runs before anything is committed, keeps throwing. - `index.js` cleared the held writes at the top of each pick pass but left `sourcesOptedOut` standing, so an abandoned pass's opt-outs were still reported in `wizard.finish` even though nothing was ever written. LLP 0279 records the sync lane's deferred-failure contract alongside the new-folder lane's. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
neutral review round:
|
…(review of #901) Three findings from the review round on #901, all in the newly deferred lane writes: - The refusal message named both lanes on an express pass, where neither lane asked anything: it narrated the standing state and handed back a no-op write, so there was no answer to have dropped. The message now covers only a pass that actually asked. - `sourcesOptedOut` still reported the answer rather than the write, so a deferred store write that failed left the finish log claiming an opt-out the export seam will never honour. The sync lane's `commit` now resolves to the scope in force, exactly as the new-folder lane's resolves to the mode in force, and the orchestrator reads it back. - A pass that never reached the lanes (a back through the fork onto a solo local run) carried the previous pass's `folderAsk` into the finish log. It clears with the held writes it belongs to. LLP 0279 updated in the same commit: the commit contract now says both writes report what they left in force, and the consequences cover both attributes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neutral review round on
|
neutral is stuck on this PR and needs a human decisionWhat neutral was doing: triage at the review-round cap on PR #901, head Blocking finding: a failed deferred sync-store write fails open on a privacy opt-out
Before this PR, the sync lane wrote The consequence when that write fails (read-only or permission-skewed state dir, ENOSPC mid-run, a directory occupying Decision neededHow should
Options 1-3 each want a small code change plus an LLP 0279 extension (Accepted docs change by extension, not edit). Non-blocking residuals (for the whole picture)
To unstick: reply with a comment on this PR (or push to the branch); neutral monitors this thread and will re-engage with your guidance on its next tick. |
Two defects in how the
hyp initwizard's question lanes persist state, both reported in #883, both asymmetries with a sibling lane that already gets it right.1.
deferWritedid not cover the lanes that write their own staterunInitWizardruns the pick lane withdeferWrite: trueso the config write lands after the question lanes (LLP 0190 #commit-point), on the stated grounds that "a cancel at the sync lane leaves the existing config untouched". The lanes themselves were not deferred:runWizardSyncScopewroteclient-sync.jsonandrunWizardFolderAskwrotefolder-ask.jsonthe moment each was answered. Decline the overwrite confirm that follows (natContinue? [Y/n], or a non-interactive commit without--force) and the run printed "keeping existing config at ..." and exited 1 with both policy stores already rewritten.LLP 0190 carved out that exception in a single sentence: "a store entry for a source whose config never lands is inert and errs toward under-sync". It covers one direction of one lane, and three things fall outside it:
local-onlyentry. That is the over-sync direction, and the "safe direction" argument does not reach it.readClientSyncEntriesreturnsnullfor a missing file and[]for an empty one, because absence is LLP 0188 #migration's marker. So even the "inert" write stamps the machine.Both lanes now take
deferWriteand hand their write back ascommit, which the orchestrator runs immediately aftercommitWizardPickedConfigsucceeds and not at all when it does not. The statements stay with their lanes: they are the never-silent floor (LLP 0188), and on the express path LLP 0201 #narrate binds each one to its own indented block. A refusal therefore now names what else it dropped ("the sync and new-folder answers from this run were not recorded either"), listing only the lanes that actually asked. The held writes are re-assigned per pass and cleared at the top of every pick pass, so back navigation (LLP 0191) writes the last answer and a pass that never reaches the lanes carries nothing forward.2. The express gate reset a standing
askpreferenceLLP 0201 #gate says accepting takes "every lane's stated default". The sync lane's auto-accept arm honors that (it returns the opt-outs already standing); the new-folder arm recorded
DEFAULT_FOLDER_ASK_MODE. A user who sethyp policy folders ask, re-ranhyp initon the same enrolled machine, and accepted the express gate had the per-folder session-start question turned off - by the screen offering to accept the defaults, on a lane whose own asked screen deliberately usesdefault: before.The arm now records
before, which changes nothing on a first run (wherebeforeis the shipped default). The gate's one line of consequence follows the answer, since it is the one line the fast path is guaranteed to read: "new folders keep asking" on a machine set toask, "new folders sync too" otherwise. The orchestrator reads the mode once, through the safe read, before the gate.Design record
New decision LLP 0279, with
Extended-by:forward-refs appended to LLP 0190 (whose #commit-point carve-out it withdraws) and LLP 0201.Testing
Five new tests, each confirmed failing on
masterbefore the fix:test/core/cli/wizard/index.test.js: a declined commit leavesclient-sync.jsonunstamped and a standingaskuntouched, and says so (failed on master: the store existed, the mode had flipped tosync); and a committed run persists both lanes right after the config lands.test/core/cli/wizard/folder_ask.test.js: the express path keeps a standingask;deferWritestates the answer and hands the write back; a deferred write that fails warns and reports the mode still in force.test/core/cli/wizard/sync_scope.test.js:deferWritestates the split and hands the store write back.test/core/cli/wizard/express.test.js: the accept row names the standing new-folder answer.npm test: 4490 pass, 0 fail.npm run typecheck: clean.hyp smoke walkthrough_picker_to_first_query: ok.Branch-disjoint from #874 (which touches the sync gate's locked-row filter and the pick-phase reconfigure classification); no overlapping hunks.
🤖 Generated with Claude Code
Fixes #883