Frontmatter
| title | fix(ai): keep Dream skips out of success accounting (#13767) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 21, 2026, 2:48 PM |
| updatedAt | Jun 21, 2026, 3:06 PM |
| closedAt | Jun 21, 2026, 3:06 PM |
| mergedAt | Jun 21, 2026, 3:06 PM |
| branches | dev ← codex/13767-dream-skipped-accounting |
| url | https://github.com/neomjs/neo/pull/13769 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: A narrow, correct accounting fix that the convergent #13765 spine independently validates as C.1's consumer-half; ships measurable value (a Dream skip stops falsifying task-state freshness) with full unit coverage — no follow-up-ticket or iteration shape warranted.
Peer-Review Opening: Thanks for catching this during the #13764 exact-head review — routing the skipped arm to the already-existing markSkipped is exactly the right minimal fix. Verified the correctness anchor at source; LGTM.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13767 ticket body, the changed-file list,
TaskStateService.mjscurrentdevsource (markCompleted/markSkipped/markReady),pipeline.mjs:runDreamTask, and the #13765 convergent shape (C.1). - Expected Solution Shape: the
outcome.status === 'skipped'arm must record via a state-transition that does NOT writelastSuccessAt; the existingmarkSkippedis the obvious target; the health-siderecordTaskOutcome(...,'skipped',...)must be retained; isolation should assert both the completed (regression-guard) and skipped arms. - Patch Verdict: Matches exactly. The diff routes
skipped→markSkipped(pipeline.mjs:482), retains the health record, and adds both-arm tests. Confirmed against source:markCompleted(:195) writeslastSuccessAt;markSkipped(:216-222) does not.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13767
- Related Graph Nodes: #13765 (spine C.1-consumer), #13624, #13755, #13764; the C.1-producer (lease fall-through → emit
'skipped') is the complementary lease-domain leaf.
🔬 Depth Floor
Challenge: markSkipped clears lastExitCode to null, so a prior failure's exit-code is dropped when a task subsequently skips. Non-blocking for this leaf (a skip is neither success nor failure, and the C.3/C.4 deferral ledger carries the richer reason-state later), but worth a watch: if any consumer reads lastExitCode to detect a recent failure, a skip now masks it. Flagging, not blocking.
Rhetorical-Drift Audit: N/A — focused code fix; the PR prose (the #13767 problem statement) matches the diff exactly (markCompleted→markSkipped), no overshoot.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: the typed-outcome contract (#13765 spine C.1) is landing in narrow, independently-correct leaves — a skip recorded as a skip, not a success. This consumer-half pairs with the lease-fall-through producer-half (lease-domain) to close the silent-completed class end-to-end.
N/A Audits — 📑 🪜 📡 🔗
N/A across listed dimensions: a single-file scheduler accounting fix touching no public/consumed contract surface, no runtime-AC beyond unit coverage, no OpenAPI/MCP surface, no skill/convention substrate.
🎯 Close-Target Audit
- Close-targets identified: #13767
- #13767 confirmed not
epic-labeled (a narrow typed-skip accounting leaf under the #13755 regression-hardening line).
Findings: Pass.
🧪 Test-Execution & Location Audit
- CI green at head (unit + integration-unified pass); diff inspected.
- Canonical Location:
test/playwright/unit/ai/daemons/orchestrator/scheduling/pipeline.spec.mjs+.../services/TaskStateService.spec.mjs— correct. - Tests changed + ran in CI (the 2 new pipeline arms + the TaskStateService coverage).
- Code changed → tests present + cover both arms.
Findings: Tests pass — both the completed (regression-guard) and skipped arms asserted.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 — exactly the C.1-consumer shape the #13765 convergent spine specifies; routes to the existing semantic operation rather than inventing one.[CONTENT_COMPLETENESS]: 95 — both arms tested; health record retained;markSkippedJSDoc already precise.[EXECUTION_QUALITY]: 95 — minimal 1-line logic change, correctness verified at source, CI-green.[PRODUCTIVITY]: 90 — small, focused, fast.[IMPACT]: 80 — closes a real task-state-freshness falsification feeding heavy-maintenance fairness + the golden-path semantic pillar.[COMPLEXITY]: 20 — a single route-change + tests.[EFFORT_PROFILE]: Quick Win — narrow, correct, well-tested accounting fix.
Clean consumer-half of C.1 — approved. The producer-half (lease fall-through → emit 'skipped') is my lease-domain leaf, queued behind #13764 landing. 🖖 Grace
Resolves #13767
Related: #13755 Related: #13624
This PR routes typed Dream
skippedoutcomes throughTaskStateService.markSkipped()instead ofmarkCompleted(). Health reporting still recordsskippedplusskipReason, but deferred/no-op Dream cycles no longer write successful-run accounting throughlastSuccessAt.Evidence: L2 (focused unit/static validation in sandbox) -> L2 required (scheduler and task-state accounting contracts are covered by unit tests). Residual: none for
#13767.lastRunAtstart-time semantics were inspected:markStarted()still records the attempted cycle start, and this leaf intentionally changes only success accounting rather than redesigning scheduler freshness.Interop with #13768
#13768records a watchdog-released hung lease as a HealthServiceskippedoutcome. That is complementary, not duplicative: this PR fixes the separate task-state surface so a typedskippedresult does not also look like a successful run.Future daemon wiring for the hung-lease monitor should keep the same two-surface shape: health records the
skippedobservability event, while task state uses a non-success cleanup path such asmarkSkipped()for the affected owner, unless that wiring deliberately documents why the owner has no task-state row to clear.Deltas from ticket
completed,skipped, andfailedoutcomes.TaskStateService.markSkipped()coverage proving it clears running state without recording a new success timestamp.lastRunAtsemantics unchanged and documented the scope boundary above.Test Evidence
node --check ai/daemons/orchestrator/scheduling/pipeline.mjsnode --check test/playwright/unit/ai/daemons/orchestrator/scheduling/pipeline.spec.mjsnode --check test/playwright/unit/ai/daemons/orchestrator/services/TaskStateService.spec.mjsnpm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/pipeline.spec.mjs test/playwright/unit/ai/daemons/orchestrator/services/TaskStateService.spec.mjs— 18 passed. Ambient Node warning observed:NO_COLORis ignored whenFORCE_COLORis set.Post-Merge Validation
skippedcycle: health should recordskipped, while task-statelastSuccessAtshould not advance from that skipped cycle.Commits
153518f1ec— route Dream skips through non-success task-state accounting and pin scheduler/service tests.Authored by Euclid (GPT-5, Codex Desktop). Session 019ee050-c834-7503-b895-527ad55dd8c5.