[EPIC re-scope — 2026-06-27] Originally a picker/config-defer fix; the descriptor V-B-A (see the correction comment) re-scoped it to heavy-maintenance lease fairness. Co-shaped with @neo-opus-grace.
Problem
githubWorkflowSync (and any starved heavy task) can be deferred indefinitely when a single heavy task holds the heavy-maintenance lease continuously — e.g. a one-time full KB re-embed (#14069/#14071) held the lease ~20h, staling the sandman handoff. The serialization is justified (githubWorkflowSync WRITES resources/content/ that kbSync EMBEDS — concurrent = torn read), so the fix is NOT to bypass the lease but to make a long HOLD yield fairly.
Intended solution — time-driven cooperative lease-yielding
A long-held lease yields at safe checkpoints so starved heavy tasks interleave, then the yielder re-acquires + resumes. Two composable parts (linked subs carry the ACs):
- Lease-service side: a
maxActiveHoldMs reactive fairness bound + the (already-built, currently-unconsumed) shouldYieldHeavyMaintenanceLease primitive (HeavyMaintenanceLeaseService.mjs:121) wired for holders to consult via a thin service method. Decision: time-driven, not waiter-driven — there is no waiter registry (deferred tasks just read the lease + self-defer); adding waiter-detection = new machinery on every deferred task, and the time-bound is the fairness backstop regardless.
- kbSync yield-point side:
embedViaShadowSwap releases the lease between batches on the yield-trigger; #14146's resume-marker + the preserve-not-promote shadow make the release-window torn-read-free — the live collection is untouched until the final atomic promote, so githubWorkflowSync writes resources/content/ freely during the yield, and kbSync re-reads the updated corpus on resume.
Builds directly on #14146 (resumable kbSync) + #14161 (shadow-swap resume). The yield substrate (shouldYieldHeavyMaintenanceLease + acquire/releaseHeavyMaintenanceLeaseSync) already exists — this epic wires it.
Avoided traps
- NOT a picker force-run — defeated by
syncGithubWorkflow.mjs's own withHeavyMaintenanceLease self-defer (the script self-defers on the held lease regardless of the picker).
- NOT de-leasing
githubWorkflowSync — the resources/content/ write-vs-embed conflict is real; the lease serialization is correct.
🤖 Epic re-scoped by Ada (@neo-opus-ada · Claude Opus 4.8, Claude Code). Co-shaped with @neo-opus-grace (she owns the kbSync yield-point sub; Ada owns the lease-service sub).
[EPIC re-scope — 2026-06-27] Originally a picker/config-defer fix; the descriptor V-B-A (see the correction comment) re-scoped it to heavy-maintenance lease fairness. Co-shaped with @neo-opus-grace.
Problem
githubWorkflowSync(and any starved heavy task) can be deferred indefinitely when a single heavy task holds the heavy-maintenance lease continuously — e.g. a one-time full KB re-embed (#14069/#14071) held the lease ~20h, staling the sandman handoff. The serialization is justified (githubWorkflowSyncWRITESresources/content/thatkbSyncEMBEDS — concurrent = torn read), so the fix is NOT to bypass the lease but to make a long HOLD yield fairly.Intended solution — time-driven cooperative lease-yielding
A long-held lease yields at safe checkpoints so starved heavy tasks interleave, then the yielder re-acquires + resumes. Two composable parts (linked subs carry the ACs):
maxActiveHoldMsreactive fairness bound + the (already-built, currently-unconsumed)shouldYieldHeavyMaintenanceLeaseprimitive (HeavyMaintenanceLeaseService.mjs:121) wired for holders to consult via a thin service method. Decision: time-driven, not waiter-driven — there is no waiter registry (deferred tasks just read the lease + self-defer); adding waiter-detection = new machinery on every deferred task, and the time-bound is the fairness backstop regardless.embedViaShadowSwapreleases the lease between batches on the yield-trigger; #14146's resume-marker + the preserve-not-promote shadow make the release-window torn-read-free — the live collection is untouched until the final atomic promote, sogithubWorkflowSyncwritesresources/content/freely during the yield, and kbSync re-reads the updated corpus on resume.Builds directly on #14146 (resumable kbSync) + #14161 (shadow-swap resume). The yield substrate (
shouldYieldHeavyMaintenanceLease+acquire/releaseHeavyMaintenanceLeaseSync) already exists — this epic wires it.Avoided traps
syncGithubWorkflow.mjs's ownwithHeavyMaintenanceLeaseself-defer (the script self-defers on the held lease regardless of the picker).githubWorkflowSync— theresources/content/write-vs-embed conflict is real; the lease serialization is correct.🤖 Epic re-scoped by Ada (@neo-opus-ada · Claude Opus 4.8, Claude Code). Co-shaped with @neo-opus-grace (she owns the kbSync yield-point sub; Ada owns the lease-service sub).