feat(flows): author flows against OpenRegister's store, and stop calling them graphs - #149
Merged
Merged
Conversation
…g to write them resolveFk() nulls a dangling foreign key and says "row still migrated". That is right for an optional reference and impossible for Conversation.agentId, which the schema marks REQUIRED — a conversation that runs against no agent is not a conversation, so the write could only ever fail validation. It did, eight times per repair: "failed to write conversation <uuid>: Property 'agentId' should be type 'string' but is 'null'" — an error describing the symptom, emitted by a step that had already decided to carry on. These conversations reference agents that no longer exist. There is nothing to migrate them onto, so they are now skipped and reported as unmigratable, which is the true statement. The class docblock said the row is always migrated; it now records the exception rather than leaving the next reader to discover it from a stack trace.
…ing them graphs
Hermiq's flow editor read a duplicate `hermiq/agentflow` OBJECT mirror while
the engine ran the native `oc_openregister_flows` rows. Two copies, free to
drift, and they had: the Hydra sequencer's prose sat in the object's
`description` while the native row's `notes` was null, which is why the Notes
tab rendered empty for a flow that documents itself at length.
`flow-storage/spec.md` forbids the mirror outright — "A flow definition SHALL
NOT be stored as an OpenRegister object […] No other app SHALL own a flow
store, a flow controller, or a flow execution service." So the editor now goes
to `/apps/openregister/api/flows` for everything, and `src/api/graph.js` is
deleted: it posted to `/apps/hermiq/api/graph/run`, a route never registered in
`appinfo/routes.php`, so Run had been posting into a 404.
Four rendering defects, all one dialect mismatch. A flow is a Petri net: a node
is a PLACE and an edge is the TRANSITION carrying `type`/`config`. The builder
read `edges[].source`/`.target` and `nodes[].type` — keys a stored flow does
not have. On the live sequencer (17 nodes, 16 edges) that drew ZERO edges and
labelled every node with an em-dash, silently, because a dropped edge and an
absent key look the same. Now:
- edges draw from `from`/`to`, with array endpoints fanned out, so the three
splits render as 19 lines rather than 16;
- places are labelled `name || id`, never a dash;
- one card frame — the canvas wrapper owns it, the body adds none. Both
failure modes had shipped: a body frame over the wrapper's (a card in a
card), then no frame at all;
- the connection port carries the place's role — success where a run starts,
error where it ends — and is round again, 16x16. Nextcloud's global button
min-height had been stretching it to 16x34.
Also: the sidebar closes and reopens (its `close` event was unhandled and the
X did nothing), and the canvas zooms. `CnGraphCanvas` always emitted
`update:zoom`; nothing bound it, so the wheel was inert.
"Graph" is retired. It was never a second entity, and the second name is what
let the second store, the phantom endpoint and the blank canvas each look
reasonable. Routes, page ids, components, store, CSS and strings all say flow;
`/graphs` and `/graphs/:id` stay as legacy pages so shared links keep working.
Verified: lint, stylelint, check:specs, test:l10n and the webpack build pass;
`POST /api/flow/validate` accepts the round-tripped sequencer; e2e is 9/9 with
a positive control confirming the suite can fail.
…ductionNL/hermiq into feat/flow-engine-unification # Conflicts: # l10n/en.json
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
August 4, 2026 13:59
Contributor
Quality Report — ConductionNL/hermiq @
|
| 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 14:06 UTC
Download the full PDF report from the workflow artifacts.
… cannot survive `MigrateAgentDataTest::testDanglingForeignKeyIsNulledAndCounted` pinned a defect. It asserted that a conversation whose agent no longer exists is "still migrated" with `agentId` nulled — but `agentId` is REQUIRED on the Conversation schema, so that row can only ever fail validation. It did, eight times per repair, as "Property 'agentId' should be type 'string' but is 'null'": an error describing the symptom, emitted by a step that had already decided to carry on. f3497ef fixed the step to skip and report those rows instead. The test kept asserting the old behaviour, so the fix landed red. The spec never asked for the nulled write. It asks the step to "log the skipped record and continue processing remaining records" — which is exactly what skipping does. So the test now asserts that: - the unmigratable conversation is NOT written; - it is reported BY UUID, because a silent drop is not a skip; - and a second, resolvable conversation still migrates — the "continue processing" half, which is the requirement actually worth defending, since a repair step that abandons the run on one bad row leaves an instance half-migrated. `IOutput::warning` is captured for the first time; a test watching only `info` cannot tell a reported skip from a silent one.
Contributor
Quality Report — ConductionNL/hermiq @
|
| 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 14:30 UTC
Download the full PDF report from the workflow artifacts.
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
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 free
to 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.
What
Hermiq's flow editor now reads and writes OpenRegister's native flow store and nothing else, and the word "graph" is retired.
Why
The editor read a duplicate
hermiq/agentflowobject mirror while the engine ran the nativeoc_openregister_flowsrows. Two copies, free to drift — and they had: the Hydra sequencer's prose sat in the object'sdescriptionwhile the native row'snoteswas null, which is exactly why the Notes tab rendered empty for a flow that documents itself at length.flow-storage/spec.mdforbids the mirror outright:src/api/graph.jsis deleted. It posted to/apps/hermiq/api/graph/run— a route never registered inappinfo/routes.php, so Run had been posting into a 404.Four rendering defects, one dialect mismatch
A flow is a Petri net: a node is a place, an edge is the transition carrying
type/config. The builder readedges[].source/.targetandnodes[].type— keys a stored flow does not have. On the live sequencer (17 nodes, 16 edges) that drew zero edges and labelled every node with an em-dash. Silently, because a dropped edge and an absent key look identical.from/to, fanning array endpoints out — 3 splits ⇒ 19 lines from 16 edgesname || id, never a dashmin-heighthad stretched it to 16×34)Plus: the sidebar closes and reopens (its
closeevent was unhandled, so the X did nothing), and the canvas zooms —CnGraphCanvasalways emittedupdate:zoom, nothing bound it.The rename
"Graph" was never a second entity, and the second name is what let the second store, the phantom endpoint and the blank canvas each look reasonable. Routes, page ids, components, store, CSS and strings all say flow.
/graphsand/graphs/:idremain as legacy pages so links already pasted into Hydra issues and run logs keep working.Verification
lint,stylelint,check:specs,test:l10n, webpack build — all pass.POST /api/flow/validateaccepts the round-tripped sequencer. e2e 9/9, including a positive control: forcing the edge count to 18 fails reporting 19, so the suite is shown able to fail.Two of those greens were earned rather than assumed — the port-colour test was failing against correct code (it pinned
var()'s fallback literal, and--color-successresolves differently at the handle than at the root), and wrapping the sidebar toggle in the new zoom controls had dropped its class.Follow-ups (specced, not in this PR)
or-flow-action-nodes·or-flow-connectivity-and-last-run·or-flow-migrate-definitions·hermiq-flow-canvas-ports·cn-flow-store-and-canvas-rename(moves this PR'ssrc/api/flows.jsinto nc-vue as a sharedcreateFlowStore).🤖 Generated with Claude Code