LearnNewsExamplesServices
Frontmatter
id11513
titleLane A of #11503 — add `backup` to DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES + cross-poll deferral tests
stateClosed
labels
enhancementaitestingarchitecture
assigneesneo-opus-4-7
createdAtMay 17, 2026, 3:19 AM
updatedAtMay 17, 2026, 8:22 AM
githubUrlhttps://github.com/neomjs/neo/issues/11513
authorneo-opus-4-7
commentsCount0
parentIssue11503
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 17, 2026, 8:22 AM

Lane A of #11503 — add backup to DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES + cross-poll deferral tests

Closedenhancementaitestingarchitecture
neo-opus-4-7
neo-opus-4-7 commented on May 17, 2026, 3:19 AM

Resolves Lane A of #11503 umbrella. Sibling to closed Lanes B (#11505 / PR #11506) and C (#11507 / PR #11509).

FAIR-band: in-band [12/30] — prio-0 continuation per operator elevation 2026-05-16T22:55Z; completes the heavy-maintenance mutex AT THE SCHEDULER LAYER (Lane C closed the manual-CLI surface; Lane A closes the daemon-scheduler set + cross-poll proof).

Context

V-B-A on ai/daemons/Orchestrator.mjs:43-49 confirms backup is NOT currently in DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES:

export const DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES = Object.freeze([
    'summary',
    'kbSync',
    PRIMARY_DEV_SYNC_TASK_NAME,
    DREAM_TASK_NAME,
    GOLDEN_PATH_TASK_NAME
]);

backup IS defined in TaskDefinitions.mjs:81-87 (executes buildScripts/ai/backup.mjs which exports KB Chroma, Memory Core Chroma, and SQLite graph state) but is missing from the orchestrator-side heavy-maintenance set. Per #11503's umbrella Problem-statement section "Backup is not in the heavy set", this is the AC1 gap.

V-B-A on test/playwright/unit/ai/daemons/Orchestrator.spec.mjs lines 134-230 confirms the existing heavy-maintenance test coverage is summary-vs-kbSync ONLY (both same-poll backpressure at line 134 and cross-poll deferral at line 184). The other heavy classes (dream, golden-path, primary-dev-sync, and now backup) have NO cross-poll deferral coverage. Per #11503's AC2 ("focused orchestrator tests prove daemon-lifetime deferral across polls for every heavy task class, including dream and golden-path, not just same-poll summary-vs-KB"), this is a coverage gap.

The backup-isolation test at line 244 is unrelated — it covers backup scheduling failures, NOT backup participating in heavy-maintenance backpressure.

The Problem

Today's empirical anchor (2026-05-16T19:27Z): orchestrator kbSync wedge cascade. While Lane C closes the manual-CLI bypass surface, the daemon-side backup task can still overlap with summary, kbSync, primary-dev-sync, dream, or golden-path because it's not in the heavy set. A scheduled backup landing during a long Dream cycle (LLM-heavy graph writes) would produce concurrent Chroma + SQLite contention exactly like the wedge class Lane B + C are designed to prevent.

The cross-poll coverage gap is the second half: even if backup is added to the set today, a future refactor could silently regress one of the 6 heavy classes' deferral behavior because there is no per-class test pinning the cross-poll contract. The umbrella explicitly calls this out.

Architectural Reality

  • ai/daemons/Orchestrator.mjs:43-49DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES frozen array (missing 'backup')
  • ai/daemons/Orchestrator.mjs:270-274heavyMaintenanceTaskNames_ reactive config defaults to the constant
  • ai/daemons/Orchestrator.mjs:336 — orchestrator constructor cloning the default into instance state
  • ai/daemons/TaskDefinitions.mjs:81-87backup task definition (already wired into orchestrator scheduling; just not classified as heavy)
  • test/playwright/unit/ai/daemons/Orchestrator.spec.mjs:134 — same-poll backpressure test (summary→kbSync)
  • test/playwright/unit/ai/daemons/Orchestrator.spec.mjs:184 — cross-poll deferral test (summary→kbSync); does NOT cover dream, golden-path, primary-dev-sync, backup
  • test/playwright/unit/ai/daemons/Orchestrator.spec.mjs:80 — state-key sanity test already includes 'backup' in expected keys (confirming backup is wired into TaskStateService); this is the empirical proof the task exists at the scheduler layer, it's just missing the heavy-classification flag

The Fix

Code change (1 line):

@@ ai/daemons/Orchestrator.mjs @@
 export const DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES = Object.freeze([
     'summary',
     'kbSync',
+    'backup',
     PRIMARY_DEV_SYNC_TASK_NAME,
     DREAM_TASK_NAME,
     GOLDEN_PATH_TASK_NAME
 ]);

Test additions (cross-poll deferral coverage per AC2):

Add focused per-heavy-class cross-poll deferral tests in Orchestrator.spec.mjs covering each of the 6 heavy classes:

  • backup deferred when another heavy task is running (NEW class)
  • dream deferred when another heavy task is running (gap fill)
  • golden-path deferred when another heavy task is running (gap fill)
  • primary-dev-sync deferred when another heavy task is running (gap fill)
  • Confirm symmetric: each heavy class can ALSO act as the blocker

The existing summary↔kbSync coverage (lines 134 + 184) stays as-is; new tests are additive.

Test shape can be table-driven (single test factory iterating over heavy-task pairs) OR per-class tests mirroring the existing structure. Either is acceptable; prefer the shape that produces clearest failure messages.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES constant Orchestrator.mjs:43-49, #11503 umbrella AC1 Includes 'backup' alongside summary/kbSync/primary-dev-sync/dream/golden-path N/A (compile-time constant) JSDoc on constant + adjacent comment naming AC1/Lane A Spec assertion on constant contents + per-class cross-poll deferral tests
Heavy-maintenance backpressure (cross-poll) Orchestrator.poll() + activeHeavyTask + #11503 AC2 Each of the 6 heavy classes (summary, kbSync, backup, primary-dev-sync, dream, golden-path) defers when ANY other heavy task is running across polls Non-error skip + recordTaskOutcome with reasonCode: 'heavy-maintenance-backpressure' JSDoc on heavyMaintenanceTaskNames_ config Focused per-class test in Orchestrator.spec.mjs
Operator log surfaces writeLog calls in Orchestrator Existing Deferring [task name] INFO log shape extended to backup class Sparse logs, no WARN flood (no doc change — log shape already established by line 134/184 tests) Test assertion on log contents per class

Acceptance Criteria

  • AC1: 'backup' is added to DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES in ai/daemons/Orchestrator.mjs
  • AC2: Test asserts DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES contains exactly the 6 expected classes (regression-pin against accidental removal)
  • AC3: Cross-poll deferral test for backup blocked by an active summary / kbSync / dream / golden-path (at least one blocking pair to prove backup is in the deferral path)
  • AC4: Cross-poll deferral test for dream blocked by another heavy task (AC2-umbrella gap fill)
  • AC5: Cross-poll deferral test for golden-path blocked by another heavy task (AC2-umbrella gap fill)
  • AC6: Cross-poll deferral test for primary-dev-sync blocked by another heavy task (AC2-umbrella gap fill)
  • AC7: All new tests follow the existing createTestOrchestrator(...) factory + recordTaskOutcome + writeLog capture pattern from lines 134-230 (no new test substrate)
  • AC8: Existing Orchestrator.spec.mjs tests continue to pass (no regression in same-poll backpressure or backup-isolation paths)

Out of Scope

  • Lane D (PrimaryRepoSyncService.runKbSync() nested-cascade observability) — separate ticket, may be folded post-V-B-A trace if Lane C's syncKnowledgeBase.mjs wrapping covers it transitively
  • Lane E (observability / stale-lease health surfaces) — separate ticket
  • HeavyMaintenanceLeaseService consumer-guidance JSDoc (cycle-1+cycle-2 friction-to-gold from PR #11509) — separate small follow-up ticket per @neo-gpt's routing recommendation, NOT bundled into this PR
  • Refactoring DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES from frozen array into Set or other structure — out of scope; constant shape is stable enough
  • Adding new heavy-task classes beyond the 6 — none identified as candidates today; this ticket pins the current set, not extends it

Avoided Traps

  • Adding backup to the set without per-class cross-poll tests: rejected — the umbrella's AC2 explicitly calls out the gap. Without per-class tests, a future refactor can silently regress one class's deferral behavior because only summary↔kbSync is currently pinned. Empirical: PR #11509's cycle-2 review caught exactly this class of structural-correctness gap (no test pinning the invariant) — same lesson applies here at the scheduler-set level.
  • Table-driven test that asserts ALL pairs: not required by AC2; full N×N coverage adds combinatorial test time without proportional regression-prevention value. Per-class deferral tests + state-key sanity test (line 80) provide the necessary coverage with linear test count.
  • Renaming DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES to clarify "set vs default": rejected — default is the established prefix for the export pattern (see also DEFAULT_PRIMARY_DEV_SYNC_ROOTS_CONFIG etc.). Renaming would create downstream callsite churn for zero substrate value.
  • Bundling Lane D V-B-A trace into this PR: rejected per #11503 lane separation. Lane D's call-path investigation (does PrimaryRepoSyncService.runKbSync() import services directly or shell out to the now-wrapped syncKnowledgeBase.mjs?) is independent work and deserves its own commit + reasoning trail.

Related

  • Parent umbrella: #11503 (Enforce heavy-maintenance mutex across Agent OS tasks)
  • Sibling lane (closed): #11505 / PR #11506 (Lane B — lease primitive)
  • Sibling lane (closed): #11507 / PR #11509 (Lane C — manual CLI script adoption)
  • Sibling offshoot (closed): #11511 / PR #11512 (Golden Path light-maintenance classification)
  • Coordination anchor: @neo-gpt MESSAGE:3c42e809-d1e9-43ca-a10c-9bdc9a08eb7d (2026-05-17T01:14:56Z) acknowledged Lane A pickup as non-colliding with @neo-gpt's #11475 review lane
  • Empirical anchor: 2026-05-16T19:27Z wedge cascade (orchestrator kbSync stuck; Chroma contention) — the failure class this umbrella's three remaining lanes (A, D, E) progressively close

Handoff Retrieval Hints

  • Retrieval Hint: cross-poll heavy maintenance deferral backup orchestrator
  • Retrieval Hint: Commit SHA a5c638069 (Lane C merge into dev) — branch off origin/dev after this commit for clean separation
  • Retrieval Hint: Orchestrator.mjs DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES (exact symbol-anchor for the code change)
  • Retrieval Hint: Lane C ticket #11507 + PR #11509 are the closest sibling for shape/scope reference

Origin Session ID: f662d055-a35b-446a-83ff-5fc859604722

tobiu closed this issue on May 17, 2026, 8:22 AM
tobiu referenced in commit 4e0d0db - "feat(ai): add backup to heavy-maintenance set + cross-poll deferral tests — Lane A of #11503 (#11513) (#11514) on May 17, 2026, 8:22 AM
tobiu referenced in commit 55dadc7 - "feat(ai): annotate runKbSync cascade as kbSync lifecycle (#11520) (#11521) on May 17, 2026, 8:23 AM