fix(dag): cross-instance adoption guard, subscription defect survival, cancel-skip race - #131
Merged
Conversation
… race - Adopt workflows only in the owning project's instance (recoverWorkflow, WorkflowStarted, wake redelivery) so multi-directory servers no longer spawn children under a foreign directory context - Replace handler-level Effect.ignore with a catchCause boundary so orDie defects from transient DB errors cannot kill event subscriptions for the process lifetime - Abort still-live fibers (abortChild + interrupt) when NodeSkipped lands on a running node, closing the cancel-race child session leak - Seed global dag.jsonc once at instance init via opt-in autoSeed; scheduling rounds stay pure reads and non-EEXIST seed failures are logged - Align the node PAUSED transitions with production paths and weld projector from-guards to the declared tables via exported projections + drift test
…iscovery fails bun's ancestor-directory workspace walk dies with CouldntReadCurrentDirectory in sandboxed checkouts, failing hooks on an environment defect. The shared runner keeps 'bun typecheck' as the primary path and falls back to running each package's own typecheck script only on that specific error; real type errors still fail on either path.
…fallback tsgo -b packages (app/desktop) can fail with TS5033 'operation not permitted' writing .tsbuildinfo under sandboxed hook contexts. Tolerate a package failure only when TS5033 is the sole TS diagnostic; real type errors still fail.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes four verified defects in the DAG module (plan: DAG 缺陷修复计划), plus a husky typecheck fallback for sandboxed checkouts.
P0 — Cross-instance workflow adoption (incident root cause)
DagLoop is per-directory InstanceState, but its event subscriptions and recovery scans had no project filter: with multiple directories mounted on one server, ANY instance could adopt ANY workflow, and the first spawn wave could run children under a foreign directory context (observed live: an explore node spawned into a different project and asked the user where the source tree was).
recoverWorkflow(covers startup scan + WorkflowReplanned entry-miss),WorkflowStartedhandler, and startup wake redelivery now adopt only workflows whoseprojectIdmatches the instance.P1 — Subscription death by defect
Effect.ignoreonly absorbs the error channel;orDiedefects (e.g. transient SQLITE_BUSY insidestore.getWorkflow) punched through and killed the forkedrunForEachfiber — that event type stayed permanently unhandled for the process lifetime. All 9 subscription handlers plus the idle wake trigger now end in acatchCauseboundary (guarded).P1 — Cancel-skip race leaks child sessions
Workflow-level cancel publishes NodeSkipped for running nodes; when that handler won the cross-stream race against WorkflowCancelled it deleted the fiber uninterrupted, so the child session kept running until prompt completion/timeout. The NodeSkipped handler now aborts live fibers (abortChild + interrupt), mirroring NodeCancelled.
P2 — Scheduling hot path wrote user config
DagConfig.loadauto-seeded the globaldag.jsoncon every scheduling round with all write errors swallowed. Seeding is now opt-in (autoSeed), happens once at instance init, and non-EEXIST failures are logged.P2 — State machine drift guard
The declared transition tables and the projector's SQL from-guards had already drifted (PAUSED→SKIPPED happens in production but was undeclared; the projector accepted pending→completed which nothing produces). The node table now matches production paths, projector guards are exported as
NodeStatusProjection/WorkflowStatusProjection(NodeCompleted tightened torunning), and a new drift test welds the two encodings together (completed→running reopen is the single documented exemption).chore(husky)
bun typecheckdies with CouldntReadCurrentDirectory in sandboxed checkouts whose ancestors are unreadable;tsgo -bbuildinfo writes can be denied (TS5033). Hooks now fall back to per-package typecheck on exactly those environment defects; real type errors still fail.Tests
packages/opencode/test/dag/dag-loop-guards.test.ts: foreign-project non-adoption (WorkflowStarted + recovery), own-project control, defect survival, skip-interrupts-live-child — 5 passpackages/core/test/dag-projector-drift.test.ts: projector guards ⊆ declared tables — 3 passpackages/opencodetest/dag: 249 pass;packages/core: 1129 pass (single failure is the pre-existing environment-bound Watcher .git/HEAD test, unrelated)