Hold sink exports behind a bounded pick-pending marker on fresh enroll (LLP 0093)#287
Conversation
#281 follow-up) After the #283 reordering the picker ran at the right time but queried through the wrong registry: hyp remote login boots its kernel at dispatch time, before enrollment, and on a first-run box the local config names no plugins. The login process's query-registry snapshot therefore never registers ai_gateway_messages - that registration arrives with @hypaware/ai-gateway, which the org config-control pull enables only after enrollCentralSink installs the daemon. listCapturedDirectories then failed with "unknown dataset" (best-effort null), waitForCapturedDirectories read null as "enumeration cannot run, stop now" (no 30s poll), and the picker took its enumeration_failed durable-hint path on exactly the fresh enroll it exists for. The #283 tests stubbed waitForCaptured/listCandidates, so the gap was never exercised; a re-login on a populated box worked, which is why manual verification passed. Fix: freshenCaptureEnumeration (src/core/cli/remote_commands.js). On the fresh-enroll interactive fork, after waitForClientAttach returns a client, re-boot one fresh kernel (config profile, the same layered resolution any subsequent hyp command runs) and hand its registry's enumeration to the capture wait. One re-boot is sufficient by construction: attach markers are written by the reconciler that runs only after a confirmed config apply, so attach means the pulled central layer is on disk - and the attach handler requires the gateway capability, so that layer enables the plugin. The snapshot-already-has-it case (re-login on a populated box) skips the boot entirely; a failed re-boot or a still-missing dataset falls back to the prior behavior. Best-effort throughout: the refinement never breaks the login it refines (LLP 0072). The handle is disposed (sources.stopAll, mirroring dispatch) after the picker so no boot-started source keeps the login process alive. local_only.js exports CAPTURE_DATASET so the registry check and the enumeration SQL cannot drift. LLP 0069/0080 carry forward-ref notes per the immutable-record convention. Tests: seam-level coverage of the refresh wiring (fresh enumeration is polled, disposed after the picker; null refresh falls back), unit coverage of freshenCaptureEnumeration (snapshot no-op, success, still-missing, boot-throw), and - the #283 lesson - one deliberately unstubbed test that runs the REAL bootKernel against an on-disk central layer naming @hypaware/ai-gateway and asserts enumeration RUNS (empty list, pollable) instead of failing to null. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l (LLP 0093) Closes the one-time forwarding window the issue #281 reordering left open: after PR #283 deferred the local-only picker past enrollCentralSink, the daemon that enroll installs could attach, backfill, and start exporting rows before the user finished the pick. Withholding is enforced at export-read time, and the server has no retraction path, so rows from a directory the user was about to withhold forwarded once, permanently - narrowing LLP 0069 R6's "not forwarded, even once" on exactly the fresh-enroll path the picker exists for. Decision (LLP 0093): the enrolling login writes a machine-local pick-pending marker (usage-policy/pick-pending.json, co-located with the local-only list) BEFORE enrollCentralSink, so no daemon tick can beat it onto disk; the kernel sink driver skips whole export ticks while the marker is fresh (held: 'pick_pending' in the tick report); the login clears the marker in one finally covering every exit from the fresh-enroll fork. Freshness is mtime with a 10-minute TTL and stale or unreadable markers read as absent, so a crashed or abandoned login can never stall exports indefinitely - the hold is a bounded refinement, never a kill switch (LLP 0072). Held rows stay in the cache and export on the first tick after the pick; watermarks make that a plain catch-up. Enforcement is driver-wide rather than per-sink: the driver cannot know which sinks leave the machine without a new registration concept, and briefly deferring a local sink is harmless where one missed forward hold is a permanent leak. Rejected alternatives (backfill-before-forward reordering, off-machine-only gating, unbounded hold) are recorded in the decision doc. Re-logins and --no-forward/query-only logins never write the marker. Tests: marker module (fresh/clear/stale-unlink/injected-now/fail-open), driver hold (fresh marker holds the whole tick, cleared or expired marker exports), and login lifecycle (marker lands before enroll, persists through the pick, cleared on return, on enroll error, and on non-TTY; never written on re-login). Stacked on the issue #281 follow-up fix (fresh-enroll registry refresh). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
neutral review — adopted PR (full-heal, stacked on #286), head Verdict: clean. No blockers, majors, or minors. Reviewed the incremental delta on top of Egress / withholding invariant (no leak found): Bounded (no kill switch needed): TTL 10 min via file mtime ( Ordering / lifecycle / idempotency: Marker written BEFORE Test quality (proves withhold-while-pending + release-after-pick): @ref: |
# Conflicts: # llp/0069-local-only-dir-selection.spec.md # llp/0080-local-only-dir-selection.design.md # src/core/cli/remote_commands.js # test/core/remote-login-command.test.js
|
neutral review (round 2) — adopted PR, head Verdict: clean. Re-review after conflict resolution. Mergeable ∧ green ∧ reviewed. Why re-reviewed: #286 squash-merged to master and its branch was deleted; GitHub retargeted this PR's base to master, which left it Re-verification of the resolution (nothing lost, nothing masked):
Green: full suite Same clean bill as the round-1 review of |
Stacked on #286 (fresh-enroll registry refresh) - the diff here is only the export hold. When #286 merges, GitHub retargets this to master.
Problem
The issue #281 reordering (PR #283) left a one-time forwarding window it explicitly deferred (the "Revisited-by #281" notes in LLP 0069/0080): on a fresh auto-daemon enroll, the daemon attaches, backfills, and can start exporting backfilled rows before the user finishes the local-only pick. Withholding is enforced at export-read time and the server has no retraction path, so rows from a directory the user was about to withhold forward once, permanently. That narrowed LLP 0069 R6's "recorded here, never forwarded - not even once" on exactly the path the picker exists for.
Decision (new LLP 0093)
The enrolling login writes a machine-local pick-pending marker before
enrollCentralSink, and the kernel sink driver skips whole export ticks while that marker is fresh.usage-policy/pick-pending.jsonunder HYP_HOME state, co-located with the local-only list it guards. Content is debug metadata; freshness is mtime, so a torn write can neither wedge nor extend the hold.enrollCentralSinkso no daemon tick can beat it onto disk; cleared in onefinallycovering every exit (pick landed, cancelled, non-TTY skip, enroll error). Write and clear are best-effort - neither can fail the login (LLP 0072).Rejected alternatives (recorded in LLP 0093): backfill-before-forward reordering (does not close the window - the pick needs a human, and forwarding would resume the moment backfill finished if the user walked away), off-machine-only gating, unbounded hold.
Tests
held: 'pick_pending', noexportBatchcall); a cleared or expired marker exports normally.npm test: 2140 pass.build:typesclean. Smokes:core_boot_noop,daemon_foreground_start_stop,local_only_export_withholdall ok.🤖 Generated with Claude Code