fix(dag): harden runtime against stale events, orphaned fibers, and prompt injection - #100
Merged
Conversation
…rompt injection - projector: guard NodeStarted against terminal statuses (stale event resurrection) - loop/recovery: fail orphaned nodes on expired deadline; safety net uses active-fiber-aware hasRunningMatching so post-crash all-orphaned workflows no longer hang - spawn: catch terminalization race during spawn window, cancel orphaned child session, return no-op fiber (no spurious NodeFailed) - templates/sanitize: recursive sanitizeInput on resolvedMapping closes injection gap on dynamic input_mapping - types: FAILED now returns [] in transition table, aligning with isNodeTerminalStatus (iron law #2) — restart path uses RUNNING->PENDING, never FAILED - tests: cover NodeStarted guard, recovery deadlines, spawn terminalization, hasRunningMatching, nested sanitize
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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
Hardens the DAG runtime against three classes of concurrency hazards, plus a transition-table accuracy fix surfaced during code review.
Changes
1. Stale event resurrection — projector guard
projector.ts:NodeStartednow guards against terminal statuses (failed/completed/aborted/skipped), consistent with existingNodeCompleted/NodeFailed/NodeCancelledguards. The restart path is safe —NodeRestartedresets the row topendingfirst.2. Orphaned-fiber hangs after crash recovery
loop.ts:hasRunningMatchingreplaceshasRunning()for theorchestrator_unresponsivesafety net — only suppresses when a running node has an active fiber. Post-crash recovery starts with emptyentry.fibers, so all-orphaned workflows are exposed to the net instead of hanging until their deadline.recovery.ts: enforces per-node deadlines —nullskips, expired fails withtimeout, future leaves running.3. Prompt injection via dynamic node outputs
templates/sanitize.ts: recursivesanitizeInputnow coversresolvedMapping(input_mapping), closing the gap where only the static templateinputwas sanitized.4. Spawn-window terminalization race
spawn.ts: catchesTerminalViolationError | InvalidTransitionErrorfromnodeStarted, cancels the orphaned child session, returns a no-op fiber. No spuriousNodeFailedis published.5. Transition-table accuracy (code review follow-up)
types.ts:FAILEDnow returns[]ingetValidNextNodeStatuses, aligning withisNodeTerminalStatusand iron law build(deps): bump @hono/node-server from 1.19.11 to 1.19.13 in /packages/opencode in the npm_and_yarn group across 1 directory #2 (terminal irreversibility). The oldFAILED → {RUNNING, PENDING}entries were already dead code — all guarded call sites reject terminal states before consulting the table. Restart path usesRUNNING → PENDING → RUNNING, neverFAILED.Tests
dag-node-started-guard.test.ts(new) — failed/completed/restart scenariosdag-recovery.test.ts— expired/future/null deadlinespawn-completion.test.ts— no spurious NodeFailed, child cancellationdag-loop-integration.test.ts—hasRunningMatchingempty/non-empty fiber mapsdag-templates.test.ts— nested objects/arrays, benign output preserveddag-core.test.ts— all four terminal statuses return[]Verification
bun typecheckgreen on@opencode-ai/coreandopencodeturbo typecheck(29 packages) — all passbun test test/dag-core.test.ts— 62 pass / 0 fail