Skip to content

fix(dag): harden runtime against stale events, orphaned fibers, and prompt injection - #100

Merged
LeXwDeX merged 1 commit into
devfrom
feat/dynamic-workflow
Jul 17, 2026
Merged

fix(dag): harden runtime against stale events, orphaned fibers, and prompt injection#100
LeXwDeX merged 1 commit into
devfrom
feat/dynamic-workflow

Conversation

@LeXwDeX

Copy link
Copy Markdown
Owner

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: NodeStarted now guards against terminal statuses (failed/completed/aborted/skipped), consistent with existing NodeCompleted/NodeFailed/NodeCancelled guards. The restart path is safe — NodeRestarted resets the row to pending first.

2. Orphaned-fiber hangs after crash recovery

  • loop.ts: hasRunningMatching replaces hasRunning() for the orchestrator_unresponsive safety net — only suppresses when a running node has an active fiber. Post-crash recovery starts with empty entry.fibers, so all-orphaned workflows are exposed to the net instead of hanging until their deadline.
  • recovery.ts: enforces per-node deadlines — null skips, expired fails with timeout, future leaves running.

3. Prompt injection via dynamic node outputs

  • templates/sanitize.ts: recursive sanitizeInput now covers resolvedMapping (input_mapping), closing the gap where only the static template input was sanitized.

4. Spawn-window terminalization race

  • spawn.ts: catches TerminalViolationError | InvalidTransitionError from nodeStarted, cancels the orphaned child session, returns a no-op fiber. No spurious NodeFailed is published.

5. Transition-table accuracy (code review follow-up)

Tests

  • dag-node-started-guard.test.ts (new) — failed/completed/restart scenarios
  • dag-recovery.test.ts — expired/future/null deadline
  • spawn-completion.test.ts — no spurious NodeFailed, child cancellation
  • dag-loop-integration.test.tshasRunningMatching empty/non-empty fiber maps
  • dag-templates.test.ts — nested objects/arrays, benign output preserved
  • dag-core.test.ts — all four terminal statuses return []

Verification

  • bun typecheck green on @opencode-ai/core and opencode
  • Pre-commit hook ran full-repo turbo typecheck (29 packages) — all pass
  • bun test test/dag-core.test.ts — 62 pass / 0 fail

…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
@LeXwDeX
LeXwDeX merged commit 9185e07 into devJul 17, 2026
4 checks passed
@LeXwDeX
LeXwDeX deleted the feat/dynamic-workflow branch July 17, 2026 02:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@LeXwDeX