Skip to content

feat: decode event routes, match topics and gate them per route and key (INC-976) - #594

Draft
arzafran wants to merge 3 commits into
mainfrom
franco/inc-976-event-routes
Draft

arzafran wants to merge 3 commits into
mainfrom
franco/inc-976-event-routes

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

First slice of event routes (INC-976): programa.json can now hold a routes block, and Programa reads it, checks it, and knows which events each route matches and when it should fire. Nothing fires yet. Hooking routes up to live agent events and running their actions (notify, run, review, send) comes in the next PRs.

Summary

Start with Sources/RouteGate.swift (the debounce/throttle logic), then Sources/ProgramaRoutes.swift (schema and validation). The rest is wiring.

  • Schema and validation (ProgramaRoutes.swift): each route has name, on, when, debounceMs, throttleMs, key, and exactly one action. Routes decode one at a time, so a bad route becomes an issue and the other routes, commands and recipes still load. Durations take milliseconds or strings like "5m".
  • Matching (RouteMatching.swift): MQTT-style topics with + and #. when follows pmndrs/pine's filter syntax, limited to equality, ne, in, exists and regex.
  • Gate (RouteGate.swift): one tagged-union state per (route, key) with a pure reducer that takes now as input. Debounce handles the last event of a burst, and throttle fires once and then drops matches for the window. The default key is {{topic}}, as in pine.
  • Config loading (ProgramaConfig.swift): routes load from the global ~/.config/programa/programa.json only, and they are part of the reload change check.
  • Socket contract: adds routes.list and the debug-only debug.routes.load. Both are stubs until the engine lands.
  • Contract change: commands is now optional, so a file with only routes loads. testDecodeMissingCommandsKeyThrows becomes testDecodeMissingCommandsKeyYieldsNoCommands on purpose.

Known follow-ups for the next slice:

  • A route-only edit bumps configRevision, which also refreshes the command palette.
  • Unknown keys and unknown when fields are not rejected yet, so a typo silently stops a route from firing.
  • Route issue messages are English only and get localized when they reach a notification.

Testing

  • ./scripts/reload.sh --tag inc-976-event-routes: build succeeded.
  • python3 scripts/check-structural-budgets.py: pass. scripts/check-v2-contract.sh: ok.
  • The new unit test classes (ProgramaRouteDecodingTests, ProgramaRouteStoreLoadingTests, RouteMatchingTests, RouteGateTests) have not run yet. This PR's CI is their first compile against the real module. They include the "once per 5 minutes" throttle check: matches at 0, 60 and 299 seconds fire once, and a match at 300 seconds fires again.
  • Not exercised: anything at runtime, since no event reaches the engine yet.

Demo Video

  • Video URL or attachment: n/a, no visible change.

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • Tested via CI and, for UI or behavior changes, a tagged build (./scripts/reload.sh --tag <name>)
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

@arzafran

Copy link
Copy Markdown
Member Author

Review of 5b26208 (Codex plus a manual check). CI is green. I confirmed each item below in the code.

1. send and prompt routes do nothing, but they show as fired. Sources/RouteActions.swift:107 ends the delivery right away, with no error. A user who configures one sees the fired count go up and nothing reaches the terminal. Until delivery is built, either reject these two actions when decoding, the way an unsupported action would be reported as a route issue, or record an error such as "send: not supported yet" so the counters show it.

2. Two quick events can open two review splits. review() (RouteActions.swift:148-159) checks hasReviewPanel on main, then opens the review off main after the repo probe. A second event that arrives before the first panel exists passes the same check. Review routes don't hold a delivery, so the gate doesn't stop it. Fix: recheck on the main actor right before the panel is created, or track a pending review per workspace.

3. The PR description is out of date. It still says "Nothing fires yet" and that runtime isn't exercised. The branch now has the event tap (29a05ed) and the notify/review/run actions (5b26208). The Testing section should cover those too.

Also: docs/ doesn't mention routes anywhere yet, and the checklist item for docs is unticked.

This branch has not been deployed

No deployments
Sign up for free to 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