From af006e0e596bbdc32711addcc978c40d34700ae3 Mon Sep 17 00:00:00 2001 From: Mario Aldayuz Date: Sun, 5 Jul 2026 10:59:30 -0400 Subject: [PATCH] Gate the whole brood/watch activation on brood prerequisites (no auto-brood without Portkey) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nectar's design contract (brood-prereqs.ts / PRD-018k) is "brooding is dormant out of the box: auto-brood on boot runs only when Deep Lake creds resolve AND Portkey is explicitly enabled." But the gate was incomplete: cli.ts only omitted the DESCRIBE deps when Portkey was absent, while the structural brood (walk / mint / embed) still ran for any active project. So a bound project with no inference configured kicked off a full- codebase brood on boot, pegging CPU to no purpose (live-observed on the-apiary). Thread a `broodReady` predicate into LiveActiveProjects: when the brood prerequisites are not satisfied, resolve() reports zero active projects, so the supervisor stands up NO brood/watch context — the whole auto-brood stays dormant, not just the describe deps. cli.ts wires `broodReady: () => broodPrereqs.ready`. Default-safe (omitted => always ready), so the pre-existing suite is unchanged; new NEC-023 test covers dormant-without- Portkey and activation once satisfied. Co-Authored-By: Claude Opus 4.8 --- src/cli.ts | 6 ++++ src/hive-graph/live-active-projects.ts | Bin 7937 -> 9169 bytes test/live-active-projects.test.ts | 37 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/cli.ts b/src/cli.ts index f75b866..7741535 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1335,6 +1335,12 @@ async function runDaemon(): Promise { log: (line) => process.stderr.write(`${JSON.stringify({ ts: new Date().toISOString(), ...line })}\n`), }), controlOptions: liveControlOptions, + // PRD-018k / NEC-023: gate the WHOLE brood/watch activation on the brood prerequisites, not + // just the describe deps. Brooding is dormant out of the box: without Deep Lake creds AND + // Portkey enabled, resolve() reports zero active projects, so the supervisor auto-broods + // nothing (previously the structural brood — walk/mint/embed — still ran for any bound + // project even with no inference configured, pegging CPU to no purpose). + broodReady: () => broodPrereqs.ready, onError: (err) => process.stderr.write( `nectar daemon: active-project resolve failed (non-fatal): ${err instanceof Error ? err.message : String(err)}\n`, diff --git a/src/hive-graph/live-active-projects.ts b/src/hive-graph/live-active-projects.ts index 37660349305d582865cda900b0953b6de2145e5b..f0f7fa6b629732d3862c9950921cf11e629b9261 100644 GIT binary patch delta 1177 zcmZ{k&2G~`6oofzU{$sVRi~RODNS5pgDSO92x3#xs;Xc;v9IH4oEbB79Ve|&;2Db8 zBHjS0Pr@tkB;2v{Q=}qARxC~KIp>~l^7ZidyI(&y=0LQw^8mmOPS1Lsy{9imh|qug zrnC3#`97+QI3ovnVBoAxP&jh*snlLGc_>HlinUKQC1~}C3LL8uA$F9IVXboCOn2mM zJ|P_SdpNa@M>K=>bI`QNb*wp`fs7jDl(f5e9i+4w$_eyN&fXvO&tXf3O&ce6T3a*n zrdRX1bjBkUMcT#Ht6ZBAj`f6&VwUHrvlkSz?=FmdhNHsH9eZ4ovrzRZiPs#1w?V4$ z?#qJQM0@MtS((J6EYLq`HWF2e;|#GiOjE|6FW+j9Rc3R7D%p0L#mO&c9*K2hWq23H zGA_!w=SCtdI3XW9JqTY4-^EAGSqL36VyKwvxt?7aOH`FF3Tag;A4OwrIHK`D)(58y zQt_(H3)%e15L=FlHuZE4sB8?)WlMmHK`t!Qd}23N>!Q5Zdf*xSK_?mNF}oX zgn=DvW=eMdSyDnwPHbq;I)*QW4>7Db5R-^|nI#$P9FnIrai+{L60zD{B+sB=jet-u z>@4WL+()a8gG0$7>s03E$HwLTc@*6VYi5n4{tv3nJ|);~LOq_WU<`!}EN8(EL}86} z-HoELH5iwkA*HF=ip!RlkClQpTgy*l0RUu^2wngH diff --git a/test/live-active-projects.test.ts b/test/live-active-projects.test.ts index c370adb..b16a283 100644 --- a/test/live-active-projects.test.ts +++ b/test/live-active-projects.test.ts @@ -86,6 +86,43 @@ test("W1-N: a projects.json binding written AFTER boot is picked up on the next } }); +test("NEC-023: brood prerequisites unmet (no Portkey) => dormant even with credentials + a binding", () => { + const cacheDir = tempDir("nectar-live-cache-"); + const stateDir = tempDir("nectar-live-state-"); + let broodReady = false; + const live = new LiveActiveProjects({ + loadCredentials: () => CREDS, + createStore: () => fakeStore, + buildContext: ({ project }): RunningContext => ({ + projectId: project.projectId, + path: project.path, + watcherState: () => "running", + start: async () => {}, + stop: async () => {}, + }), + controlOptions: { cacheDir, broodingState: { dir: stateDir } }, + // The Portkey/creds prereq gate. False => the daemon must NOT auto-brood. + broodReady: () => broodReady, + }); + + try { + // Credentials resolve AND a project is bound, but Portkey is not enabled + // (broodReady=false) => brooding is dormant out of the box: zero active projects, + // so the supervisor stands up no brood/watch context (the auto-brood-without-Portkey fix). + writeProjectsCache(cacheDir, CREDS.orgId, CREDS.workspaceId, [{ path: "/work/repo-a", projectId: "proj-a" }]); + assert.equal(live.resolve().active.length, 0, "no Portkey => dormant even with a binding present"); + + // Once the prereqs are satisfied (Portkey configured), the SAME binding activates. + broodReady = true; + const after = live.resolve(); + assert.equal(after.active.length, 1, "prereqs satisfied => the binding activates"); + assert.equal(after.active[0].projectId, "proj-a"); + } finally { + rmSync(cacheDir, { recursive: true, force: true }); + rmSync(stateDir, { recursive: true, force: true }); + } +}); + test("W1-N: with credentials absent the resolver is dormant and fail-soft (empty resolution, factory is a no-op)", async () => { const cacheDir = tempDir("nectar-live-cache-"); const stateDir = tempDir("nectar-live-state-");