FE-747: Declarative output arcs β topology-level routing for branching transitions#154
FE-747: Declarative output arcs β topology-level routing for branching transitions#154kostandinang wants to merge 4 commits into
Conversation
New TRACK F frontier between petri-epic-verification-merge (done) and petri-graph-compilation (horizon, blocked on FE-700). Moves conditional output routing from wireHandlers fire closures into typed Guard predicates declared on HandlerDescriptor so a topology-only consumer can enumerate reachable output places per transition without invoking actions, reports, or the test runner β structural prerequisite for any static analysis (simulation, reachability, deadlock detection) and FE-700-independent. Retires the "Declarative output arcs" sub-bullet under petri-graph-compilation since it's now its own frontier; keeps "Token state enrichment" there. Co-Authored-By: Claude <noreply@anthropic.com>
Move conditional output routing from wireHandlers fire closures into typed Guard predicates declared on HandlerDescriptor. ActionDescriptor gains a required guard:Guard (replacing routeField); RunTestsDescriptor adds passGuard; AssessSemanticDescriptor adds satisfiedGuard. wireHandlers consumes them via a pure evalGuard(guard, report) helper. Adds enumerateCandidateOutputs(transition) so static consumers can derive the reachable output-place set per transition from topology alone, without instantiating actions, reports, or the test runner. Halt paths (budget exhaustion, verify-epic failure) and token transforms stay in fire closures and remain follow-on slices. New invariant I125-K. Engine contract suite unchanged (84 orchestrator tests pass); npm run verify green. Co-Authored-By: Claude <noreply@anthropic.com>
Disambiguates the typed routing predicate from TransitionContract.guard, the pre-existing human-readable note string on the same record. Pure rename across net-blueprint.ts (type + interpreter), net-compiler.ts (consumer), and topology.test.ts. Descriptor field names (passGuard, satisfiedGuard, ActionDescriptor.guard) keep their domain identifiers. No behavior change; 84 orchestrator tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
The existing per-kind tests computed expected output sets from the same descriptor fields the enumerator consumes, so they'd pass silently if both the topology emitter and the enumerator dropped a branch in lockstep. Add three goldens that pin literal expected place names for slice-1:evaluate, slice-1:run-tests, and slice-1:assess-semantic against the simplePlan fixture. Lockstep drift now surfaces immediately. Retires memory/REFACTOR.md (FE-747 refactor pass complete). Co-Authored-By: Claude <noreply@anthropic.com>
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
PR SummaryLow Risk Overview I125-K in Reviewed by Cursor Bugbot for commit f9de408. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
|
||
| export type RouteGuard = { kind: 'always' } | { kind: 'reportFieldTruthy'; field: string }; | ||
|
|
||
| export function evalRouteGuard(guard: RouteGuard, report: ReportLine | undefined): boolean { |
There was a problem hiding this comment.
evalRouteGuard currently has no explicit fallthrough handling, so an unknown/malformed guard.kind at runtime would return undefined (treated as false) and could silently misroute. Since guards are pure data (and may eventually be serialized/deserialized), consider making unsupported kinds fail fast to protect routing correctness.
Severity: medium
π€ Was this useful? React with π or π, or π if it prevented an incident/outage.

TL;DR. Moves conditional output routing from
wireHandlersfire closures into typedRouteGuardpredicates onHandlerDescriptor. Topology-only consumers can now enumerate reachable output places per transition without instantiating actions, reports, or the test runner. FE-700-independent Phase-3 prep that also satisfies Phase 4 simulation's routing-side structural prerequisite.What changed
Declarative routing on
HandlerDescriptorActionDescriptor.routeField?: stringβguard: RouteGuard(required;{kind: 'always'}for unconditional).RunTestsDescriptoraddspassGuard: RouteGuard.AssessSemanticDescriptoraddssatisfiedGuard: RouteGuard.always,reportFieldTruthy. Extension shape documented in code.Pure interpreter and topology consumer
evalRouteGuard(guard, report): booleanβ pure helper consumed bywireHandlers.enumerateCandidateOutputs(transition): Set<string>β returns the topology-derived output-place set per transition without runtime instantiation.No inline if-ladders on report payloads in fire closures.
Invariant I125-K in
memory/SPEC.mdprotects topology-side analyzability.Why now
First Phase-3 prep step independent of FE-700 (intent-graph-semantics). Without declarative guards, formal analyses (reachability, deadlock detection, simulation) can only see input-side structure β which would make
petri-simulation-oracle(Phase 4) impossible regardless of when Phase 3 graph compilation lands.Verification
topology.test.tsβ 12 tests coveringevalRouteGuardpurity,enumerateCandidateOutputsper descriptor kind, and 3 literal-fixture goldens that catch lockstep drift between emitter and enumerator.npm run verifygreen β 122 test files, 1421+ tests pass.Out of scope
Halt paths (
run-testsretry exhaustion,assess-semanticrework exhaustion,verify-epicfailure) still mutatectx.haltedfrom fire closures. Token transforms (reportIdattach, retry/rework propagation, budget decrement) stay in closures.verify-epicstill readsreport.payload.passedinline. All flagged as follow-on slices in I125-K.Traceability
Requirements 46, 47, 48; D155-K (refinement of FE-738 HandlerDescriptor design); invariant I125-K. Frontier
petri-declarative-routinginmemory/PLAN.md. Under umbrella H-6476.