Uh oh!
There was an error while loading. Please reload this page.
LLP 0276 + 0277: hidden rows stay off the sync gate; an answer-less config is not a reconfigure - #874
Conversation
On every enrolled machine the sync gate led with the two hidden raw-proxy rows (raw-anthropic / raw-openai) wearing the fleet label, because @hypaware/ai-gateway sits in the central layer so they classify locked - while the picker had deliberately never offered them. The lane's locked descriptors now go through the same visiblePickerDescriptors filter the pick lane uses, at the screen, never at the locked set (which would re-compose the org gateway into the local layer, LLP 0129). The no-candidates short-circuit splits accordingly: with no visible org row to name, it says nothing syncs instead of naming the fleet as owner of an empty list. Extends LLP 0202 and supersedes its "sync/opt-out menu is unchanged" consequence line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hyp remote add before the first hyp init writes a config holding only query.remotes. The pick phase classified any readable config file as a reconfigure, so that run seeded from an empty read-back instead of from detection: every box arrived unchecked, no defaults gate rendered, and the export default quietly flipped to keep-local. The classification now keys on whether the config records a pick answer, discriminated by the plugins key: the composer always writes a plugins array, the side-channel writers never do. An answer-less config seeds like no config at all - detection pre-checks, gates render, export takes the first-run local-parquet default - while its own keys still carry through the composition fold. plugins: [] stays a reconfigure: an emptied install must not be re-consented from detection. Extends LLP 0183. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the no-candidates line stops claiming nothing syncs Two defects in the LLP 0266 half of this PR, both on the sync gate. The locked list went through `visiblePickerDescriptors`; the candidate list did not. A carried hidden row (LLP 0202 #carry-through) reaches `picked.descriptors` whenever that row is not locked - a team join whose org config has not converged, or a machine whose central layer does not declare `@hypaware/ai-gateway` - and the gate then rendered it as an editable checkbox for a row the picker deliberately never offered, where unchecking it writes a `local-only` entry for a source the user never saw. Both row lists now take the same filter, which is what makes LLP 0266's "absent from every wizard screen" true rather than half true. The new no-candidates line then said "nothing syncs to your server" on exactly the machine class LLP 0266 targets: an enrolled machine whose locked set is entirely the hidden `raw-*` pair filtered out of the display. Those rows are still locked, still composed by the org's central layer, and under LLP 0188 #locked they always sync and can never be opted out - so the sentence traded LLP 0202's over-disclosure for an affirmatively false claim about what leaves the machine. The branch now splits three ways on `lockedHidden`, a count the lane gets so it can tell the truth about withheld rows without being able to name them. LLP 0266 §sync-gate, §no-candidates, and §consequences updated to match.
philcunliffe
commented
Aug 19, 2026
neutral review round: PR #874 @ |
| state | line |
|---|---|
| a visible org row | "Everything you picked is managed by your fleet and always syncs." + the rows |
no visible org row, lockedHidden > 0 | "You picked nothing to record, but capture your fleet manages directly still syncs to your server." |
| no locked row at all | "You picked nothing to record, so nothing syncs to your server." |
A count, not a list: the lane must be able to tell the truth about the withheld rows without being able to name them. All three paths stay noQuestion, so LLP 0191 §back-edges and the step counter are unaffected.
Finding 2 (medium, FIXED): the display filter was applied to locked but not to candidates
src/core/cli/wizard/index.js:539 (reviewed head)
LLP 0266's stated invariant widens LLP 0202 to "absent from every wizard screen", but only half of the screen was filtered. candidates: picked.descriptors is sources.map(id => descriptors.get(id)), and sources carries a hidden id whenever that id is not locked, via the LLP 0202 carry-through:
- a machine configured
hyp init --source raw-anthropic(config collects nothing visible, socarried = ['raw-anthropic']) that then runshyp initon the team pathway, where the join's org-config converge has not landed yet andlockedSetis empty; - an enrolled machine whose central layer does not declare
@hypaware/ai-gateway.
The sync gate then listed "Anthropic API" as an editable candidate and rendered it as a checkbox, and unchecking it writes a local-only policy entry for a source the picker deliberately never offered.
Fix:candidates: visiblePickerDescriptors(picked.descriptors). Filtered at the screen, not in pick.js, which keeps LLP 0266's own "filter at the screen, never at the locked set" rule and leaves configure.js's needs-setup read of picked.descriptors untouched. The policy store's editor semantics are correct under this: an existing opt-out entry for a now-unshown row is kept, never silently dropped, which is what runWizardSyncScope already documents.
No issues in the LLP 0267 half
configRecordsPickAnswer is a sound discriminator: composePickerConfig always emits a plugins array (including []), and mutateLocalConfig (the only remote add / remove writer) never touches the key. carryForwardExistingConfig handles existing.plugins === undefined safely, the reconfigure span attribute and the export read-back are consistently keyed to configured !== undefined, and expressRowsSafe reuses resolvePickSeeding so the express gate cannot drift from the pick lane. The returning-gate mismatch (configExists && configValid) is already declared a residual in LLP 0267 §consequences.
What was pushed
11bd103e on wizard-sync-gate-and-answerless-config:
src/core/cli/wizard/index.js: both row lists throughvisiblePickerDescriptors;lockedHiddenpassed to the lane.src/core/cli/wizard/sync_scope.js: three-way no-candidates split.src/core/cli/wizard/types.d.ts:lockedHidden?: number, and the doc comments oncandidates/lockednow say both arrive display-filtered.test/core/cli/wizard/sync_scope.test.js: hidden-only locked set does not claim nothing syncs and still never names the withheld rows.test/core/cli/wizard/index.test.js: a hidden picked row is not a sync candidate;lockedHiddenis threaded.llp/0266-hidden-rows-stay-off-the-sync-gate.decision.md: §sync-gate, §no-candidates, and §consequences updated to match. Amended in place rather than superseded because the doc is introduced by this same unmerged PR, so there is no landed decision to preserve.
0266 is also claimed by fix/issue-836 (PR #850) and update/icebird-squirreling-native-batches (PR #866); 0267 is also claimed by fix/issue-837 (PR #849). Both of those PRs are older, so this branch yields the numbers. 0276 and 0277 are 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>
The LLP 0276 filter takes hidden rows off both sync-lane lists, but only the locked list reported how many it removed. A carried hidden row (LLP 0202 #carry-through) that is not locked reaches picked.descriptors, is composed into the local layer, and syncs unless an opt-out entry says otherwise - and when it is the only pick, the filter empties `candidates` with `locked` empty and `lockedHidden` 0, so the lane took the strongest of its no-question sentences and told the user "nothing syncs to your server" while capture was in fact leaving the machine. That is the affirmatively false claim LLP 0276 #no-candidates ruled out for the locked case and did not carry to the candidate case. `runInitWizard` now passes `candidatesHidden` alongside `lockedHidden`, one count per filtered list, and the no-question branch gains a fourth line: with no locked row but a hidden pick standing, it states that capture already set up on this machine still syncs, naming neither the row nor the fleet (which does not own it). LLP 0276 #no-candidates extended with the case and the sentence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
philcunliffe
commented
Aug 19, 2026
neutral review round: |
philcunliffe
commented
Aug 19, 2026
neutral triage: PR #874 @ |
Uh oh!
There was an error while loading. Please reload this page.
…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>
…ing gate that keys on a recorded answer (#925) * LLP 0281: the sync gate's fleet sentence stops at the fleet's rows, and 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> * Review fix: the returning gate's answer claim reads each config layer, 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> * Review fix: a central layer answers the pick question only when it carries 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> --------- Co-authored-by: neutral <neutral@hyparam.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: test <test@test.com>
Two onboarding-wizard fixes, one commit and one LLP decision each.
LLP 0276: a hidden picker row stays off the sync gate too
On every enrolled machine the sync gate led with the two hidden raw-proxy rows (
raw-anthropic/raw-openai) labelled· managed by your fleet- rows the picker had deliberately never offered (LLP 0202). They classify locked because@hypaware/ai-gatewaysits in the central layer, so the only fleet-labelled rows were the two nobody had seen, and the label read as a property of the client rows beneath them.runInitWizardnow passes the locked descriptors throughvisiblePickerDescriptors, the same display filter the pick lane uses. Filtered at the screen, never at the locked set: dropping hidden ids fromlockedSourcesupstream would re-compose the org's gateway into the local layer (the LLP 0129 collision).runWizardSyncScopesplits: with no visible org row to name, it prints "You picked nothing to record, so nothing syncs to your server" instead of attributing an empty list to the fleet. Both paths staynoQuestion, so the step counter is unaffected.LLP 0277: an answer-less config does not make a reconfigure
hyp remote addbefore the firsthyp init(the documented team onboarding order) writes a config holding onlyquery.remotes. The pick phase classified any readable config file as a reconfigure, so the first real onboarding run seeded from an empty read-back instead of from detection: every box arrived unchecked but still labelled· detected, no defaults/express gate rendered, and the export read-back flipped the first-runlocal-parquetdefault tokeep-local.pluginskey (configRecordsPickAnswer): the pick composer always writes apluginsarray, the side-channel writers never touch it.plugins: []remains a reconfigure: an emptied install cannot be told from one, and re-seeding it from detection would re-consent on the user's behalf, which is what LLP 0183 forbids.reconfiguretelemetry attribute onwizard.pick.startfollows the answer-keyed classification.hyp initafterhyp remote addstill fronts the "already set up" summary; left to a follow-up.Testing
node --test test/core/cli/wizard/*.test.js: 132 pass, 0 fail (new coverage for the filtered sync gate, both no-candidates lines, answer-less seeding, key carry-forward, and theplugins: []boundary).npm test: 28 failures in query/parquet/report suites, identical count on a clean master (pre-existing, unrelated).🤖 Generated with Claude Code