Skip to content

feat(flow): approval and event steps land on the one engine, with the steps-to-graph migration - #1822

Merged
rubenvdlinde merged 3 commits into
developmentfrom
feat/one-engine-flowcut
Sep 2, 2026
Merged

feat(flow): approval and event steps land on the one engine, with the steps-to-graph migration#1822
rubenvdlinde merged 3 commits into
developmentfrom
feat/one-engine-flowcut

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What this does

Closes out integriq-flow-nodes and starts retire-integriq-flow-schema in dependency order, converging integriq's app-local flow machinery onto the OpenRegister engine.

The two missing nodes (retire task 1)

  • openconnector.approval-request: persists a pending approval_request (the HITL system of record, mirrored onto shared tasks per feat(approvals): mirror every HITL approval into the shared OpenRegister task service #1812) and suspends the run the way openregister.await-signal does, with a heartbeat. Approved resumes with the decision on every item. Rejected routes by default, or fails the run under failOnReject. Expired fails closed, always. The heartbeat re-reads the record, so a lost signal costs one heartbeat, never the flow.
  • openconnector.event-emit: one CloudEvent per item through the existing EventService pipeline. Confirmed first that openregister ships no emitter (its send nodes address people, not systems), so the change grew by this node exactly as its gate predicted.
  • ApprovalsController resolves engine-run approvals and delivers the decision through the new EngineSignalService, a class_exists-guarded FlowRunSignalService::signalAs() wrapper (the BrokeredCallService idiom). The record resolves first; delivery is best-effort because the node's heartbeat treats the record as the authority.
  • approval_request gains engineRunUuid/signalNodeId/question in the register fragment, because OpenRegister silently drops undeclared properties.

The steps-to-graph migration (retire task 2)

  • FlowStepsToGraphTranslator: pure steps[] to nodes/edges. Step order becomes the node id verbatim, so branch targets survive without renumbering. Branch JsonLogic conditions ride the edges, matching the engine's edge-condition routing. Duplicate orders and inexpressible features are refused with one reason each, never approximated.
  • FlowGraphMigrationService + repair step + occ integriq:flow:steps-to-graph: additive (steps stays as the rollback shape, the two engines dual-run through the window), idempotent (an object carrying nodes is skipped), dry run by default, --rollback --apply restores the steps-only shape and refuses a flow whose graph is its only shape.
  • The flow schema declares nodes/edges so the written graph is not silently dropped on save.

Bookkeeping

  • docs/features/flow-nodes.md plus Dutch strings for both nodes.
  • Both changes' tasks.md updated to what is actually true. Flow-nodes task 4 stays open with a note: the demo seed was never wired (lib/sources.seed.json is the PDOK file and has no importer), so the live end-to-end run has not happened.
  • Measured before migrating: the dev instance holds one flow schema row and zero live flow objects.

Deliberately NOT in this PR

Retire tasks 3 to 5: deleting FlowRunnerService, moving FlowsController off the integriq register, RuleToFlowGenerator graph emission, removing the flow schema, the prune, and the gate-106 baseline regeneration. Those only start once migrated flows have run in anger on the engine; the change is staged for multi-PR delivery.

Verification

  • composer lint, phpcs, phpmd, psalm, phpstan, check:routes, check:no-legacy-types: all green, run individually.
  • PHPUnit: 2744 tests green, including 20 new ones (node semantics, translator property tests, migration idempotence and rollback refusal, controller signal paths, ApprovalService engine addressing).
  • Hydra gates --scope-to-diff against origin/development: green except gate-53 locally, which is the composer-locked v1.10.0 schema not knowing the flow page type this repo already ships; the gates@main schema CI actually runs allows it (verified against the raw file).
  • npm run lint and npm run format: green.

🤖 Generated with Claude Code

… the steps-to-graph migration

integriq-flow-nodes is implemented in the tree; this closes its open ends and
starts retire-integriq-flow-schema in dependency order.

Task 1 (the missing nodes):
- openconnector.approval-request: persists a pending approval_request (the
  HITL system of record, mirrored onto shared tasks) and suspends the run the
  way openregister.await-signal does, with a heartbeat. Approved resumes with
  the decision on every item; rejected routes (or fails under failOnReject);
  expired fails closed. The heartbeat re-reads the record, so a lost signal
  costs one heartbeat, never the flow.
- openconnector.event-emit: one CloudEvent per item through the existing
  EventService pipeline. Confirmed first that openregister ships no emitter.
- ApprovalsController approves/rejects engine-run requests and delivers the
  decision through EngineSignalService, a guarded FlowRunSignalService::
  signalAs wrapper (BrokeredCallService idiom). The record resolves first;
  delivery is best-effort by design.
- approval_request gains engineRunUuid/signalNodeId/question in the register
  fragment, because OpenRegister silently drops undeclared properties.

Task 2 (the steps-to-graph migration):
- FlowStepsToGraphTranslator: pure steps[] -> nodes/edges. Step order becomes
  the node id verbatim so branch targets survive; branch conditions ride the
  edges; duplicate orders and inexpressible features are refused with reasons,
  never approximated.
- FlowGraphMigrationService + repair step + occ integriq:flow:steps-to-graph:
  additive (steps stays as the rollback shape), idempotent (nodes present =
  skipped), dry run by default, --rollback restores the steps-only shape.
- The flow schema declares nodes/edges so the written graph is not dropped.

Docs (docs/features/flow-nodes.md), Dutch strings for both nodes, and the
tasks.md state of both changes updated to match what is actually true,
including why flow-nodes Task 4 stays open (the demo seed was never wired;
lib/sources.seed.json is the PDOK file and has no importer).
…andle is not a relation

The three verbatim flow-engine stubs carried OpenRegister's own @SPEC paths,
which resolve to nothing in this repo (gate-46); stripped, like every earlier
stub. engineRunUuid dropped its format:uuid so it does not read as a
relation-shaped property: an engine run lives in OpenRegister's flow_runs
table, not as an object any schema here could $ref (gate-54).
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/integriq @ a2ceaeb

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
build
check-specs
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 141/141
npm ✅ 537/537
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 14:41 UTC

Download the full PDF report from the workflow artifacts.

…the new orchestration code

Three CI findings, three remedies:

- check:l10n-js: l10n/nl.js is generated from nl.json by npm run l10n:build,
  and the hand-inserted keys sat in a different order than the generator
  writes. Regenerated.
- gate-101 (demo-data-coverage): touching the flow schema means the flow
  schema owes three valid demo objects, and the mock register had none. Three
  added by hand: a linear pipeline, a gated publish with an approval step,
  and a branched flow carrying the migrated nodes/edges graph beside its
  steps. Deliberately NOT the generator's full rewrite, which would have
  replaced the whole curated mock register with 165 generated objects.
- coverage guard: 768 new statements at 74 percent read as a drop. New unit
  tests for the occ command (all four paths), the repair step (counts,
  refusal logging, the never-throws contract), EngineSignalService's guarded
  branch, and both nodes' config forms.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/integriq @ f0ae545

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
build
check-specs
test-l10n
format
check-schema-l10n
check-l10n-js
composer ✅ 141/141
npm ✅ 537/537
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development
Hydra gates

Quality workflow — 2026-09-02 18:05 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit d6cdbec into development Sep 2, 2026
51 checks passed
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