Skip to content

fix: publish schedule mirrors for the version pin, and stop the chat probe answering 503 when unconfigured - #754

Merged
rubenvdlinde merged 3 commits into
developmentfrom
fix/schedule-mirror-publish-and-chat-health
Sep 2, 2026
Merged

fix: publish schedule mirrors for the version pin, and stop the chat probe answering 503 when unconfigured#754
rubenvdlinde merged 3 commits into
developmentfrom
fix/schedule-mirror-publish-and-chat-health

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Two defects from the live rig proof.

Schedule mirrors never published their flow (HIGH)

OpenRegister's FlowRunVersionPin refuses every scheduled dispatch of a flow with no published version. ScheduleFlowBridge::mirror created the engine flow and stopped there, so every mirrored schedule left the local dispatcher and gained a clock that never ticked. Reproduced twice on the rig; after one manual POST /api/flows/{id}/publish the whole dual-app chain worked (trigger tick, queued run, hermiq.schedule-dispatch, runNow(), run-audit row). A machine-managed mirror must not need that hand.

The bridge now publishes server-side through FlowVersionService, the same class the publish endpoint uses, resolved lazily from the container and guarded on the class existing. The chosen semantics:

  • Create: insert, publish, mark. A publish failure deletes the flow again and never writes the marker, so the schedule keeps its local clock.
  • Refresh: draft, update, publish. The engine runs the pinned published version, not the flow row, so a changed cadence lands as a new published version. Drafting first makes a crash between the update and the republish visible as an unpublished head.
  • Heal: an undrifted mirror without a published version is published on the next sync pass. This fixes the mirrors the rig already created.

New unit tests assert the flow's lifecycle after every path, and the version-service fake models the pin's contract: only a publish call can move a flow to published, so a bridge that skips publishing fails the suite (verified: 4 failures against the unfixed bridge). The publish and shape concerns moved into ScheduleFlowPublisher and ScheduleMirrorDefinition to keep the bridge under the PHPMD complexity ceiling. The openspec change (schedules-onto-engine-triggers) carries the semantics in design.md, tasks.md and two new spec scenarios.

/api/chat/health answered 503 when unconfigured (MEDIUM)

An instance without an LLM provider is healthy, not broken, and the designed 503 tripped every co-installed app's strict no-5xx e2e guard (it broke dossiq's KPI spec on the rig). The probe now answers 200 {status: unconfigured, configured: false, capabilities: []}; 5xx stays reserved for the app itself being broken, so a failing config read remains 503 config_error. The three e2e carve-outs that excused the old 503 are removed, because under the new contract a 503 from this route is a real fault. The sibling /api/health keeps its 503: a dead OpenRegister dependency is an outage, not a setting.

Follow-up for nextcloud-vue: CnAiCompanion renders on any 2xx, so an unconfigured instance now shows the launcher; the widget should branch on the capabilities list instead.

Verification

  • lint, phpcs, phpmd (per subdir), psalm, phpstan: clean
  • phpunit: 1,879 tests green (dev baseline 1,875)
  • hydra gates v1.11.0 --scope-to-diff vs origin/development: 42 of 42 applicable gates green
  • eslint on the changed specs clean; playwright test --list still collects

🤖 Generated with Claude Code

OpenRegister's FlowRunVersionPin refuses every scheduled dispatch of a flow
with no published version, so a mirror the bridge only inserted was a clock
that never ticked: the schedule left the local dispatcher and gained nothing
back. Reproduced twice on the dual-app rig; one manual publish made the whole
chain work, which is exactly the fix a machine-managed mirror must not need.

The bridge now publishes server-side through FlowVersionService, the class
the publish endpoint itself uses, resolved lazily and guarded on the class
existing. Create: insert, publish, mark, so a publish failure deletes the
flow and the schedule keeps its local clock. Refresh: draft, update, publish,
because the engine runs the pinned version, not the flow row, so a changed
cadence must land as a new published version. The undrifted branch publishes
any unpublished head, healing pre-publish mirrors on the next sync pass.

The publish and shape concerns move to ScheduleFlowPublisher and
ScheduleMirrorDefinition, keeping the bridge under the complexity ceiling.
The new stubs model the pin's contract, so a bridge that skips publishing
fails its tests instead of running unpublished flows.
GET /api/chat/health answered 503 {status:no_provider} on an instance
without an LLM provider. That is a configuration state, not an outage, and
the 5xx tripped every co-installed app's strict no-5xx e2e guard (it broke
dossiq's KPI spec on the rig). The probe now answers 200
{status:unconfigured, configured:false, capabilities:[]} so a consumer
decides on the body; 5xx is reserved for the app itself being broken, so a
failing config read stays 503 {status:config_error}.

The three e2e carve-outs that excused the designed 503 are gone: with this
contract a 503 from the probe means the app is broken and must fail the
sweep like any other 5xx. The sibling /api/health keeps its 503, which
reports a genuinely dead OpenRegister dependency, not a missing setting.

Known follow-up: nextcloud-vue's CnAiCompanion renders on any 2xx, so an
unconfigured instance now shows the launcher; the widget should branch on
the capabilities list.
… birth state

The real Flow entity is born draft, so the bridge publishes directly; the
standalone stub is born blank, so the bridge drafts first. The order
assertion encoded the stub's shape and failed on the server matrix. Pin what
matters on both: publish happens, and the marker comes after it.
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/hermiq @ 180c919

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
check-manifest
test-l10n
format
check-l10n-js
check-schema-l10n
composer ✅ 123/123
npm ✅ 730/730
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 19:05 UTC

Download the full PDF report from the workflow artifacts.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/hermiq @ c4c1a7a

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
check-manifest
test-l10n
format
check-l10n-js
check-schema-l10n
composer ✅ 123/123
npm ✅ 730/730
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 19:19 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit 96f14c9 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