Uh oh!
There was an error while loading. Please reload this page.
Deferred #874 residuals: the sync gate's fleet sentence, and a returning gate that keys on a recorded answer - #925
Conversation
philcunliffe
commented
Aug 19, 2026
Review round: |
| test | at e21497b | at 6b211dc |
|---|---|---|
an enrolled machine that has not run init yet records no pick answer | fails | passes |
an empty plugins array survives beside a central layer that adds none | fails | passes |
Finding 3 (low, NOT fixed - reported): src/core/cli/wizard/sync_scope.js:127 asserts the hidden pick syncs without consulting the policy store
Capture already set up on this machine also syncs to your server. prints unconditionally on candidatesHidden > 0. optedOutBefore is computed only over candidateIds, i.e. the visible candidates, so a hidden picked source with a standing local-only entry is invisible to that check, and the screen claims capture ships when the export seam will withhold it.
Left as-is deliberately: the branch cannot do better, because candidatesHidden is a count by LLP 0276's own design ("the lane must be able to tell the truth about them without being able to name them") - there is no id to look up. The identical property already holds for the sentence LLP 0276 minted at sync_scope.js:107, which this branch reuses verbatim, so this is a pre-existing property of that sentence rather than something this PR introduces. Fixing it would mean handing the sync lane the hidden row ids that LLP 0276 withholds, which is a design change and belongs in its own LLP. Flagged here so it is on the record.
Checked and clean
- The
configRecordsPickAnswermove tosrc/core/config/schema.jsis complete: no stale importers anywhere in the tree,walkthrough.js->status.jsimport cycle genuinely avoided, JSDoc@ref LLP 0277#answer-lesscarried across intact. managedis still read before the new early return inevaluateReturningGate, matching the invalid-config branch.- The new
sync_scopebranch is ordered correctly after thelocked.length === 0branch, returnsnoQuestion: true, writes no policy store entry, and never names the withheld row (asserted). HypAwareStatusReport.configRecordsAnswerhas exactly one producer;hyp status's own rendering (src/core/commands/status.js:133,413) still keys onconfigExistsand is unchanged.test/core/init-configured-entry.test.jsend-to-end dispatch cases are unaffected (their configs carryplugins).- Style: no em dashes, no semicolons, no inline
import('...')types,@importspecifier is repo-root-anchored.
Not done: no merge, no ready-flip, PR body untouched.
philcunliffe
commented
Aug 19, 2026
Neutral review recordReviewed: Finding 1 - medium - FIXED
constcentralAnswersPick=[...centralPluginNames].some((name)=>name!==CENTRAL_ENROLLMENT_PLUGIN)The claim this computes is stated three times as "the central layer carries capture of its own" - LLP 0281 Verified empirically against Fix ( Positively verified: new test Finding 2 - low - LEFT (settled by precedent)
opts.stdout.write('Capture already set up on this machine also syncs to your server.\n')The branch fires on Left deliberately: this is the identical exposure carried by the pre-existing sibling branch at Checked and clean
New head: |
philcunliffe
commented
Aug 19, 2026
Triage at head Deferred to follow-up issue #935. |
…nd the returning gate keys on a recorded answer Two deferred residuals from PR #874 (issue #911), both the same shape: a screen keyed on a proxy for the fact it states. Finding 1: with a visible org row on the sync gate and a hidden carried pick standing beside it (`candidatesHidden > 0`), the lane printed "Everything you picked is managed by your fleet and always syncs". The carried row (LLP 0202 #carry-through) composes into the local layer and syncs, so the sentence handed the fleet an owner's claim over capture it does not own. The branch now narrows the fleet sentence to the rows the fleet owns and adds the machine's-own-capture line LLP 0276 already minted. Still a count, never a name; still `noQuestion`. Finding 2: the returning gate (LLP 0129 / 0182) keyed on `configExists && configValid`, so the answer-less config `hyp remote add` writes before the first `hyp init` still fronted the "already set up" summary - the residual LLP 0277 declared. `collectHypAwareStatus` now reports `configRecordsAnswer` off the effective config, and the gate takes the first-run path without it. Effective, not local: a machine carried by its central layer is set up, the fleet having answered for it. `configRecordsPickAnswer` moves from the CLI walkthrough to `config/schema.js` so the daemon-side reader does not close an import cycle. One definition, two readers. Finding 3 (PR #874's body citing LLP 0266/0267 after the renumber to 0276/0277) is metadata on a merged PR, corrected there; see LLP 0281 #not-done. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, not the merge
`configRecordsAnswer` was computed from `merged.effective`, which cannot
express the claim it makes.
The enrolling `hyp remote login` writes a central seed carrying
`plugins: [{ name: '@hypaware/central' }]` (`enrollCentralSink`), so on
the documented team order - `hyp remote add`, `hyp remote login`,
`hyp init` - the merged config had a `plugins` array before anyone was
asked anything, `configRecordsAnswer` came back true, and the returning
gate still fronted "HypAware is already configured". That is the exact
sequence LLP 0281 #returning-gate claims to fix; it did not fire for it.
The same read also lost a deliberate `plugins: []` record-nothing pick on
a joined machine: `mergeConfigLayers` sets the key only when the merged
list is non-empty, so onboarding re-opened on a deliberately emptied
install, which LLP 0277 #answer-less forbids.
Each layer is now read on its own terms: the local layer answers when it
records a pick answer (the same discriminator the pick lane reads), and
the central layer answers when it carries capture of its own. The bare
enrollment plugin is not an answer.
Two regression tests, verified failing before this change and passing
after. `npm test`: 4540 pass / 0 fail / 1 skipped. `npm run typecheck`
clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>…rries capture `configRecordsAnswer`'s central half read "any plugin that is not the enrollment seed", but the claim it stands for - stated in LLP 0281 #returning-gate, in the comment above it, and in the `HypAwareStatusReport` doc - is "the central layer carries capture of its own". A fleet whose central layer also pushes a sink or format plugin (`@hypaware/local-fs`, `@hypaware/format-parquet`, `@hypaware/gascity`) satisfied the old reading without anyone having answered anything, so the documented team order `hyp remote add` -> `hyp remote login` -> `hyp init` still fronted the returning summary over a machine that records nothing: the exact failure the decision closes. The test is now the positive one, against the picker catalog: does the central layer name a plugin that contributes a picker row? That is the same plugin-level test `computeCentralLockedSources` uses to decide which rows the org owns, so the locked set and this claim cannot disagree. With no catalog the question cannot be asked, so that path keeps the weaker plugin-name reading rather than re-opening onboarding's consent questions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
8768eea to
1e7b618CompareUh oh!
There was an error while loading. Please reload this page.
Master landed PR #925 (LLP 0281 #visible-org-row), which added the sibling no-candidates branch this PR's LLP 0289 #not-done anticipated. Both sides touch the same few lines of the sync lane, so the merge is where the two branches either agree on one fact or drift apart. Resolution: - `src/core/cli/wizard/sync_scope.js`: master's org-row branch gated on `candidatesHidden > 0`, the count this PR replaces with `candidatesHiddenIds`. It now reads the same `hiddenCandidateSyncs` boolean its no-locked sibling reads, which is exactly what LLP 0289 #not-done said it should do when #925 landed. A hidden pick the store already withholds does not ship, so the exhaustive fleet sentence is true again and the two branches turn on one question rather than disagreeing about it. Both branches of that fork now record `hidden_picks_syncing` on the span. - `llp/0276-...decision.md`: both sides appended an `Extended-by:` note to #no-candidates. Both are kept, master's (LLP 0281) first. - `test/core/cli/wizard/sync_scope.test.js`: both sides' tests are kept, master's two ported to `candidatesHiddenIds`, plus a new pin for the case the merge creates - an org row beside a hidden pick already opted out keeps the exhaustive sentence. That pin fails on the mechanical rename and passes on this resolution. - `llp/0281-...decision.md`: forward-ref to LLP 0289 #ask-the-store on #visible-org-row, since its branch now carries that `@ref`. - `llp/0289-...decision.md`: #not-done records that LLP 0281 has landed and the residual is closed. npm test: 5017 pass / 0 fail / 1 skipped. npm run typecheck: clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e it is Self-review of the master merge found one finding, fixed here. medium: the merge re-gated LLP 0281 #visible-org-row's branch on `hiddenCandidateSyncs`, a question about shipping, when what that Accepted decision settled was a question about ownership. It says so itself: "no clause here is affirmatively false about *what* leaves the machine - which is why round 2 left it", and the rule it adopted is "a sentence names only the rows whose owner it names." So on an enrolled machine with a visible org row, a hidden carried pick (LLP 0202 #carry-through), and a standing `hyp policy client raw-anthropic local-only`, the merged lane printed "Everything you picked is managed by your fleet and always syncs." over a row the fleet does not manage and that does not sync - the exact claim #925 had just removed from master, restored by this branch's merge and pinned by a new test. The branch now splits the two claims: - the fleet sentence narrows whenever a hidden pick exists, withheld or not, which is what LLP 0281 settled and what master does today - the second line, the one that promises an export, is the store's answer and prints only when a hidden pick is not already withheld, the same question the no-locked sibling asks So the two branches still agree about what leaves the machine, without this one taking back an owner's claim it had given up. The withheld row is still never named. LLP 0289 #not-done and #consequences record which half of that branch the store governs; the `Extended-by:` blockquote this PR appends to LLP 0281 #visible-org-row is corrected to match. Nothing LLP 0281 settled is edited. The reworked test fails on the pre-fix lane ("Everything you picked is managed by your fleet and always syncs." where none of it is the fleet's) and passes after. npm test: 5016 pass / 0 fail / 1 skipped (the one failure in this sandbox, `hyparquet-floor-pin`, reproduces unmodified at 9549f38 and is a shared-node_modules artifact). npm run typecheck: clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the deferred review findings triaged out of PR #874. Two behaviour fixes with regression tests, one metadata correction made on the merged PR itself. New decision doc LLP 0281, with
Extended-by:forward-refs appended to LLP 0276 §no-candidates and LLP 0277 §consequences (both Accepted, so neither is edited on the parts it settled).Finding 1: the sync gate's fleet sentence spoke for a row the fleet does not own
src/core/cli/wizard/sync_scope.jsWith a visible org row on the no-candidates branch the lane printed "Everything you picked is managed by your fleet and always syncs." over the org rows. When
candidatesHidden > 0a carried hidden row (LLP 0202 §carry-through) is also standing: it is insources, composes into the local layer, and absent an opt-out entry it syncs. The sentence quantified over it and attributed it to the fleet.LLP 0276 §no-candidates split the empty-locked cases four ways for exactly this reason and left the non-empty one; this applies the same rule to it. With a hidden pick standing beside the org rows:
The second line is the sentence LLP 0276 already minted for a machine's own capture, unchanged. Neither line names the withheld row -
candidatesHiddenstays a count. With no hidden pick standing, the exhaustive sentence is true and is kept verbatim, so the common enrolled run is untouched. The path staysnoQuestion, so LLP 0191 §back-edges and the step counter are unaffected.Finding 2: the returning gate keyed on file existence, not on a recorded answer
src/core/cli/wizard/fork.js,src/core/daemon/status.js,src/core/config/schema.jsLLP 0277 moved the pick phase onto "the config records a pick answer" and declared the returning gate a residual: it still read
configExists && configValid, sohyp initafter a barehyp remote add(the documented team onboarding order) fronted the "HypAware is already configured" summary over a machine that collects nothing.collectHypAwareStatusnow reportsconfigRecordsAnswer, computed from the effective (merged) config. Effective rather than local is deliberate: a machine carried entirely by its central layer is set up, the fleet having answered on its behalf, and demoting it to the first-run path would re-ask questions the org owns (LLP 0129 §join-before-picker).evaluateReturningGate's guard becomes!configExists || !configValid || !configRecordsAnswer.managedis still read before the early return, for the same reason the invalid-config branch pins it.configRecordsPickAnswermoves fromsrc/core/cli/walkthrough.jstosrc/core/config/schema.js. The daemon-side reader cannot import the CLI walkthrough without closing an import cycle (walkthrough.jsalready importsstatus.js), and two copies of the discriminator could disagree.Finding 3: PR #874 cites LLP 0266/0267
Repository metadata on a merged PR, not code: corrected on PR #874's title and body (0266 → 0276, 0267 → 0277,
neutral-triagemarker preserved). Recorded in LLP 0281 §not-done. Nothing in the tree to change.Testing
Every behavioural finding has a test verified to fail before the fix and pass after.
zero visible candidates with an org row and a hidden picked row: the fleet sentence covers only its own rowstest/core/cli/wizard/sync_scope.test.jszero visible candidates with an org row and no hidden pick: keeps the exhaustive fleet sentenceevaluateReturningGate: a valid config that records no pick answer is the first-run pathtest/core/cli/wizard/fork.test.jsevaluateReturningGate: an answer-less config on a managed machine is first-run and still managedevaluateReturningGate: a config that records a pick answer still fronts the returning gatetest/core/status-config-records-answer.test.js(remote-add-only, composed,plugins: [], no config, central-layer-answers)Failing-before was checked by reverting each production file in place (
sync_scope.js,fork.js,status.js) and re-running the suite.npm test: 4538 pass / 0 fail / 1 skipped.npm run typecheck: clean.Fixes#911