feat(application): an app can declare the FLOWS and AGENTS it is made of - #205
Merged
Merged
Conversation
Exporting an OpenBuild app leaves behind everything that makes it do anything, and the reason is upstream of the exporter: the `application` schema has nowhere to say what those things are. It declared fifteen properties, two of which bind the app to other objects — `dataRegisters` and `connectors`. Neither is a flow and neither is an agent. Measured on the dev instance: `hydra-console` binds one register with three schemas, while the instance holds 86 `Flow` entities and 14 agent definitions, none of them reachable from the application. Adding bundling code first would have had nothing to read. This adds `flows` and `agents` as optional slug-addressed bindings, shaped exactly like `dataRegisters` so the builder pickers, the export payload and the reviewer all recognise them. Slug rather than id because ids are per-instance: an exported app carrying one resolves to a different object, or to nothing, wherever it lands — which is the failure this whole line of work exists to prevent. 🔑 THERE IS EXACTLY ONE FLOW SYSTEM (ADR-065). The binding is `flows`, it points at the OpenRegister `Flow` ENTITY, and there is no such thing as a "hermiq flow". A flow that calls an agent is an ordinary OpenRegister flow whose nodes are agentic node types hermiq contributes to the engine's registry. A second binding for hermiq would rebuild exactly the ambiguity ADR-065 was written to end — it exists because "flow" meant five things across the fleet and that overlap "already produced one dead feature and one silent data-loss bug".⚠️ The property description names the ENTITY on purpose. An `agentflow` OBJECT store in the hermiq register mirrors some definitions and drifts: a definition written to the object left the engine executing the previous graph, run log showing the old node set, no error anywhere. Consumers must resolve the entity, and now the schema says so rather than leaving each one to get it right alone. kind: config — one register.d fragment, no PHP, no route, no listener. Nothing reads these bindings yet; the follower spec (openbuild-exports-flows-and-agents, kind: code) adds the bundler, the install/update-time seeding and the e2e coverage. Split per ADR-032, which rejects `mixed` after two such specs burned a full builder budget without producing a PR. Verified against a live instance rather than the file: the schema reads back 17 properties with both bindings present, `Application.required` unchanged at ["slug","name"], `dataRegisters` and `connectors` untouched. Constraints were each observed refusing AND a paired control accepting — an unknown key beside a valid one is refused by `additionalProperties`, an uppercase slug by the pattern, a numeric id by the type, while no-bindings and a dangling slug are both accepted. The first unknown-key test omitted `flow` and was refused for a missing required property, proving nothing; it is recorded and re-run. One acceptance criterion could not be verified as written and says so in tasks.md: re-saving any of the 30 existing applications is refused by unrelated OpenBuild rules (appType immutability, hybrid slug read-only), so "existing objects stay valid" rests on both properties being optional plus an observed save with no bindings, not on a direct observation of those 30. Instance left as found: 30 applications, no probe leftovers, no back-filling.
…irely Implementing the follower's bundler falsified two things this spec asserted, and both would have shipped into a merged schema before anyone noticed. 1. THE `Flow` ENTITY HAS NO SLUG. It carries uuid, name, app, enabled, trigger, nodes, edges — and `FlowMapper` offers `findByUuid()` with no slug lookup beside it. The binding said slug purely by analogy with `dataRegisters`, and the analogy does not hold. Now UUID-addressed. The reasoning that produced "slug" was really an argument against the numeric `id` — an auto-increment column, per-instance, resolving to a different flow or to nothing after an export — and that argument is exactly why a UUID works. Conditional on one thing, now an explicit requirement on the follower: the importing side must seed the flow with the SAME UUID rather than minting a new one, or every binding in the imported app points at nothing while every file looks correct. 2. THERE MUST BE NO `agents` BINDING. The `agent` schema already carries `applicationSlug`, and `AgentsController` already resolves an application's agents through it. An `agents` array on the application would be a second edge for the same relationship, pointing the other way — two facts that can disagree with nothing to arbitrate between them. The exporter queries agents by `applicationSlug`; the schema gains nothing. Same instinct ADR-065 applies to flows, one domain down. Re-verified against the live instance after the correction, not against the file: the schema reads back 16 properties (was 15), `flows` present with the UUID pattern, `agents` ABSENT — checked as absent rather than merely never added. Constraints observed accepting AND refusing: real sequencer UUID 6b14a1fd-… ACCEPTED, round-tripped the SLUG this spec first specified REFUSED — pattern numeric id 5020 REFUSED — type valid UUID + an unknown key REFUSED — additionalProperties no `flows` key at all ACCEPTED dangling but well-formed UUID ACCEPTED The slug case stays as a permanent regression guard: it is what this spec originally asked for, and a schema accepting it would resolve to nothing at export time. Seed Data now carries real UUIDs read off the instance, so the follower's fixtures resolve rather than merely parse — including the sequencer, whose 76-node graph contains `hermiq.workload-step` and therefore exercises "an agentic flow binds like any other" instead of only the happy path. Follower artefacts updated to inherit both corrections. Instance left as found: 30 applications, no probe leftovers.
Contributor
Quality Report — ConductionNL/openbuild @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 638/638 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-15 07:14 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/openbuild @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| test-l10n | ✅ | ||||
| check-gitignore | ✅ | ||||
| check-nc-floor | ✅ | ||||
| format | ✅ | ||||
| composer | ✅ | ✅ 106/106 | |||
| npm | ✅ | ✅ 638/638 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-15 07:50 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
pushed a commit
that referenced
this pull request
Aug 15, 2026
# Conflicts: # openspec/changes/openbuild-exports-flows-and-agents/tasks.md
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.
feat(application): an app can declare the FLOWS and AGENTS it is made of
Exporting an OpenBuild app leaves behind everything that makes it do
anything, and the reason is upstream of the exporter: the
applicationschema has nowhere to say what those things are.
It declared fifteen properties, two of which bind the app to other objects —
dataRegistersandconnectors. Neither is a flow and neither is an agent.Measured on the dev instance:
hydra-consolebinds one register with threeschemas, while the instance holds 86
Flowentities and 14 agentdefinitions, none of them reachable from the application. Adding bundling
code first would have had nothing to read.
This adds
flowsandagentsas optional slug-addressed bindings, shapedexactly like
dataRegistersso the builder pickers, the export payload andthe reviewer all recognise them. Slug rather than id because ids are
per-instance: an exported app carrying one resolves to a different object,
or to nothing, wherever it lands — which is the failure this whole line of
work exists to prevent.
🔑 THERE IS EXACTLY ONE FLOW SYSTEM (ADR-065). The binding is
flows, itpoints at the OpenRegister
FlowENTITY, and there is no such thing as a"hermiq flow". A flow that calls an agent is an ordinary OpenRegister flow
whose nodes are agentic node types hermiq contributes to the engine's
registry. A second binding for hermiq would rebuild exactly the ambiguity
ADR-065 was written to end — it exists because "flow" meant five things
across the fleet and that overlap "already produced one dead feature and one
silent data-loss bug".
agentflowOBJECT store in the hermiq register mirrors some definitions and drifts: a
definition written to the object left the engine executing the previous
graph, run log showing the old node set, no error anywhere. Consumers must
resolve the entity, and now the schema says so rather than leaving each one
to get it right alone.
kind: config — one register.d fragment, no PHP, no route, no listener.
Nothing reads these bindings yet; the follower spec
(openbuild-exports-flows-and-agents, kind: code) adds the bundler, the
install/update-time seeding and the e2e coverage. Split per ADR-032, which
rejects
mixedafter two such specs burned a full builder budget withoutproducing a PR.
Verified against a live instance rather than the file: the schema reads back
17 properties with both bindings present,
Application.requiredunchanged at["slug","name"],
dataRegistersandconnectorsuntouched. Constraints wereeach observed refusing AND a paired control accepting — an unknown key beside
a valid one is refused by
additionalProperties, an uppercase slug by thepattern, a numeric id by the type, while no-bindings and a dangling slug are
both accepted. The first unknown-key test omitted
flowand was refused fora missing required property, proving nothing; it is recorded and re-run.
One acceptance criterion could not be verified as written and says so in
tasks.md: re-saving any of the 30 existing applications is refused by
unrelated OpenBuild rules (appType immutability, hybrid slug read-only), so
"existing objects stay valid" rests on both properties being optional plus an
observed save with no bindings, not on a direct observation of those 30.
Instance left as found: 30 applications, no probe leftovers, no back-filling.