Skip to content

refactor(flow)!: delete hermiq's second flow engine; contribute nodes only - #148

Merged
rubenvdlinde merged 4 commits into
developmentfrom
feat/flow-engine-unification
Aug 4, 2026
Merged

refactor(flow)!: delete hermiq's second flow engine; contribute nodes only#148
rubenvdlinde merged 4 commits into
developmentfrom
feat/flow-engine-unification

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Why

Hermiq carried a second flow engine alongside OpenRegister's. This deletes it, leaving hermiq to contribute nodes to the single OR flow engine (ADR-065). Net -2036 / +232.

Removed:

file lines
lib/Service/Graph/GraphExecutor.php 727
lib/Flow/HermiqFlowResolver.php 356
lib/Listener/GraphRunRequestedListener.php 188
lib/Controller/GraphController.php 146

Hermiq keeps HermiqAgentNode / HermiqWorkloadNode — the node contributions OR's engine dispatches.

Why it needs a verdict rather than a merge

This branch is what the hydra flows have been exercised against, but it has never had a CI run — no PR existed. Opening one so the deletion is measured rather than assumed.

Two things a reviewer should look at specifically:

  • tests/Stubs/Db/Flow.php and FlowMapper.php are new stubs. Stubs that declare an API the real dependency does not have have already produced a whole passing suite against a fiction in this fleet. These should be checked against OpenRegister's actual Flow/FlowMapper signatures.
  • lib/Repair/SeedHydraTriageFlow.php changed alongside the deletion; seeding runs on a fresh install, where there is no session.

Verification status

Not yet verified beyond the branch building locally. CI on this PR is the first real measurement.

… only

hermiq walked its own `agentflow` graphs through a 727-line `GraphExecutor`
while OpenRegister's engine walked the same documents through a node registry.
Two engines over one document type, and they had drifted apart in the way that
matters most:

`GraphSidebar` builds its palette from OpenRegister's node catalogue "and
nothing else", so every node dragged onto the canvas carries a NAMESPACED id —
`openregister.set-fields`, `hermiq.agent-step`. `GraphExecutor::runNode()`
switched on BARE ids — `condition`, `router`, `agent-step`, `object-write` —
and stripped no namespace. Every palette node therefore fell to `default:`, was
logged at info as "unknown node type; skipped", and the walk carried on. A graph
authored in the builder executed ZERO steps and reported success, on both paths
that reached it: the manual run endpoint AND `GraphRunRequestedListener`, which
was wired to object create/update/delete.

That is why the hydra pipeline still worked: it is schedule-driven, and the
schedule path went through OpenRegister's engine, whose registry THROWS on an
unknown type rather than skipping it. The same flow document ran correctly on
one trigger and silently did nothing on another.

Removed: `Service/Graph/GraphExecutor`, `Controller/GraphController`,
`Listener/GraphRunRequestedListener` and its three event registrations, the
`/api/graph/run` route, and the resolver stubs and tests that outlived the
resolver.

Kept, because node contribution IS the supported integration surface:
`HermiqAgentNode`, `HermiqWorkloadNode`, `HermiqFlowNodeListener`.

`SeedHydraTriageFlow` now seeds into OpenRegister's native flow store. It stays
disabled and ownerless: a repair step runs with no session, so there is no
identity to attribute the flow to, and seeding it enabled would mean picking an
owner for a graph that runs agents — a privilege decision nobody made. Enabling
it is a human act that supplies one.

Refs: openregister/openspec/changes/flow-engine-unification
One conflict, in Application.php: development added the Talk-bot lifecycle
listener next to the graph ingress this branch deletes.

Resolved by keeping development's AgentBotLifecycleListener registrations in
full and dropping only the GraphRunRequestedListener ones. The two happened to
sit in the same hunk because both hang off the object lifecycle; they are
unrelated features, and the Talk-bot work is not this branch's to revert.

The merge left ObjectCreatedEvent/UpdatedEvent/DeletedEvent used but unimported
— the graph hunk had carried those imports. Restored; without them Application
would have fataled on boot and taken every hermiq route with it.

Also corrected prose that still described the deleted engine as live: the seed
step's docblock, HermiqAgentNode's, and three schema descriptions naming
HermiqFlowResolver and CnFlowCanvas. Those read as instructions to the next
person and none of them were true any more.
Three failures, all introduced by this branch:

- phpstan: SeedHydraTriageFlow used `Application::APP_ID` with no import, so it
  resolved to OCA\Hermiq\Repair\Application — a class that does not exist. That
  was a runtime fatal in the seed path, not just an analysis complaint.
- psalm: deleting the flow-resolver stubs removed hermiq's only declarations of
  OpenRegister's flow contracts, while the rewritten seed step started using
  Db\Flow and Db\FlowMapper. Replaced with stubs for what is actually used.
- phpcs: whitespace left by the development merge.

The Flow stub keeps its accessors MAGIC — @method tags plus __call — because the
real class is an OCP Entity that serves them through Entity::__call. A stub whose
methods are more concrete than the original is worse than no stub: it passes
locally and then fails against the live class, which is exactly how the
ObjectEntity::getUuid() stub burned six CI legs.

FlowMapper declares only findAllFlows() and insert(). Every method in a stub is a
promise that hermiq may call it; the seed step is the only place hermiq touches
OR's flow store directly, so the promise stays that small.

Verified in a container with NC core: phpstan OK, psalm no errors, phpcs clean
across lib/ and both new stubs.
Resolves the two conflicts this branch carried. Both are files that
development changed AFTER this branch was cut, and in both the branch
side of the conflict was empty or a strict subset of development's:

- tests/Stubs/Db/Flow.php (add/add) — took development. Its stub
  round-trips through __call; the branch's returned null for every
  getter, which is the shape that makes "the seed sets the
  organisation" untestable (hermiq#140).
- lib/Repair/SeedHydraTriageFlow.php — took development. Its side adds
  the PHPMD coupling suppression and the hermiq#140 organisation guard
  that refuses to write an org-less orphan row. The branch side of both
  hunks was empty.

Nothing was reverted: the four deleted engine files (GraphExecutor,
HermiqFlowResolver, GraphRunRequestedListener, GraphController) are
absent from both sides and remain absent from the merge result.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/hermiq @ 8c93ca5

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
composer ✅ 117/117
npm ✅ 744/744
PHPUnit
Newman
Playwright

Quality workflow — 2026-08-04 10:44 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 3376801 into development Aug 4, 2026
36 checks passed
rubenvdlinde added a commit that referenced this pull request Aug 5, 2026
…failing CI (#157)

v1.0.1 is `f4d9756` (2026-08-03) and predates three gate fixes, so every
Hydra Gates run this repo has ever made executed a script in which 16
gates reported PASS when their helper never ran (ConductionNL/.github#147),
gate-33 had no axe report to read and never said so (#148), and gates 6
and 7 reported PASS on an empty scope (#149). The tick was identical
either way, which is why nothing in this repo's history shows it.

That pin is now also RED, and the mechanism is worth writing down.
quality.yml is referenced `@main` while this package is PINNED, so the
two can desync. #164 flipped `hydra-gates-require-full-coverage` to
default true in the shared workflow, and that flag requires a gate to
DECLARE itself not-applicable. v1.0.1 contains ZERO `_skip` calls; v1.3.0
has 36. v1.0.1 has no vocabulary to declare, so every absent prerequisite
became "DID NOT RUN" and failed the job — for gates the repo has no
subject matter for.

Measured on this branch, diff-scoped against origin/development exactly
as CI scopes it, in a private mount namespace with a private tmpfs (the
runner's ~50 /tmp/hydra-gate-*.log paths are shared state and two
concurrent runs corrupt each other's counts, .github#158 item 6):

  v1.0.1  exit 98  FAIL — "GATES THAT DID NOT RUN: 24 33"
  v1.3.0  exit 0   PASS — those gates named NOT APPLICABLE, with reasons

Independently confirmed end-to-end: doriath#160 changed this one line and
nothing else, and its Hydra Gates job went failure -> success.

v1.3.0 is `f7eaf2a` = .github@main at the time it was cut.

Refs ConductionNL/.github#159
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