Skip to content

feat(hydra-gates): gate 94 — retired-git-host-metadata - #546

Merged
rubenvdlinde merged 2 commits into
mainfrom
feat/gate-94-retired-git-host-metadata
Aug 22, 2026
Merged

feat(hydra-gates): gate 94 — retired-git-host-metadata#546
rubenvdlinde merged 2 commits into
mainfrom
feat/gate-94-retired-git-host-metadata

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Why

ConductionNL moved onto Codeberg on 2026-05-29 and moved back off on 2026-07-23. The git layer completed — verified 2026-08-22, zero codeberg remotes fleet-wide. The shipped metadata did not.

23 apps still declare Codeberg URLs in appinfo/info.xml, which is published to the Nextcloud app store:

Field Consequence today
<bugs> a bug reporter lands on an issue tracker nobody reads
<screenshot> app-store listing images 404
<repository> / <website> contributors sent to a dead mirror

This is not cosmetic drift — it is the part of a host migration that is invisible from the git layer, which is exactly why it survived a month past the migration.

Why a gate, and why full-tree

Why a gate and not just a sweep: it needs both. A sweep fixes today's 23; the gate is what stops the 24th. The fleet has done two host migrations in three months — the specific host won't recur, but the shape will.

Why NOT diff-scoped — the reasoning gates 84 and 93 give, but sharper here. The drift this gate catches is already in the tree, put there by a migration that touched every repo at once. A diff-scoped version reports nothing on the ~99% of PRs that never open appinfo/info.xml — it would be blind to 100% of the debt it was written for.

Scope: shipped metadata, not prose

Reads only files whose contents reach a user or a package registry:

  • appinfo/info.xmlroot only (an NC app ships exactly one; a nested one belongs to a different app)
  • package.json / composer.json — any depth (workspaces, per-package design tokens are legitimate)

and within those, only the fields carrying a followable URL (bugs, website, repository, documentation, screenshot, homepage, support).

Deliberately out of scope: docs, changelogs, specs, learnings, archived openspec changes. Those record what was true when written; rewriting them makes the history untrue. A gate noisy enough to be switched off catches nothing.

custom_apps/ and friends are skipped. A dev environment drops checkouts of other apps inside a repo. Walking them made the first draft report openregister as having 69 findings — 63 of which belonged to seven other apps. A gate that blames a repo for a neighbour's metadata teaches people to ignore it.

Verification

Repo Verdict Why
procest FAIL (9) own info.xml + package.json
openregister FAIL (6) own only — was 69 before the custom_apps fix
decidesk FAIL (7) own only
nextcloud-vue PASS already clean
hydra SKIP(na) ships no info.xml / package.json / composer.json

Checker exit codes: 0 clean, 1 findings, 4 not applicable. bash -n clean; rc-to-verdict mapping exercised in isolation (PASS / FAIL / SKIP(na) all reproduced).

How this reaches the fleet

Per the delegator contract, each app's Code Quality calls ConductionNL/.github/.github/workflows/quality.yml@main and resolves the gate package at run time — so merging this reaches every app with no commit in any app. fleet-drift-sweep.yml then surfaces which apps fail it.

Note on the sweep: it currently exits non-zero on every run — but that is the sweep working. Its 2026-08-21 run dispatched to all 21 apps, collected verdicts, and failed on not green on development: pipelinq shillinq. Those are real app failures, not sweep breakage. Issue #523 ("the sweep has never run") is closed and its premise no longer holds.

ConductionNL moved onto Codeberg 2026-05-29 and moved back off 2026-07-23.
The git layer completed — verified 2026-08-22, zero codeberg remotes
fleet-wide. The shipped metadata did not.

23 apps still declare <bugs>, <website>, <repository> and <screenshot> URLs
pointing at codeberg.org/Conduction/<app> in appinfo/info.xml. That file is
published to the Nextcloud app store, so this is user-visible: <bugs> sends a
bug reporter to a tracker nobody reads, and <screenshot> 404s in the listing.

Gate 94 is full-tree, deliberately NOT diff-scoped — the same reasoning gates
84 and 93 give, but sharper here. The drift this gate exists to catch is
already in the tree, put there by a migration that touched every repo at once.
A diff-scoped version would report nothing on the ~99% of PRs that never open
appinfo/info.xml, i.e. it would be blind to 100% of the debt it was written
for.

Scope is shipped metadata only: appinfo/info.xml (root, since an NC app ships
exactly one) plus package.json / composer.json, and within those only the
fields carrying a URL a user follows. Docs, changelogs, specs, learnings and
archived openspec changes are out of scope — they record what was true when
written, and a gate noisy enough to be switched off catches nothing.

custom_apps/ and friends are skipped: a dev environment drops checkouts of
other apps inside a repo, and walking them made the gate report openregister
as having 69 findings, 63 of which belonged to seven other apps. A gate that
blames a repo for a neighbour's metadata teaches people to ignore it.

Verified: procest → FAIL (9), nextcloud-vue → PASS, hydra → SKIP(na).
@rubenvdlinde
rubenvdlinde merged commit 0ffcef8 into main Aug 22, 2026
36 of 37 checks passed
@rubenvdlinde
rubenvdlinde deleted the feat/gate-94-retired-git-host-metadata branch August 22, 2026 02:18
rubenvdlinde added a commit that referenced this pull request Aug 22, 2026
Gate 94 landed in #546 without a fixture bundle, and main's "Package invariants"
job is red because of it: the coverage ratchet in
test_gate_acceptance_matrix.sh is a hard failure for a gate declared in the
runner with neither a bundle nor an UNCOVERED.md row — deliberately, so a gate
cannot land untested in silence. This is that bundle.

The two arms differ ONLY in the host the shipped URLs point at, and planted/
reproduces the fleet's actual 2026-08-22 state: <bugs>, <website>, <repository>
and <screenshot> all still on codeberg.org a month after the last codeberg
remote was removed.

Two shapes are pinned on purpose:

- Both arms carry a prose mention of "codeberg" in <description>. Gate 94 is
  scoped to URL-bearing FIELDS, never to the file's prose — a scanner that
  grepped the whole file would fail the clean/ arm on that line, and a gate
  that flagged every historical mention would be noisy enough to switch off.
- Both arms carry an identical <screenshot> PATH, so a checker keyed on the
  path rather than the host would misgrade the pair.

The subject substring is `codeberg.org`, not a field name: the finding is
"this URL points at a retired host" and the URL is what a human needs to see.
A gate reporting only a count would satisfy a nonzero-exit check for free while
naming nothing.

Verified locally: acceptance matrix 181 passed / 0 failed, gate 94 now in the
covered set (67 of 74 gates fixtured).

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde pushed a commit to ConductionNL/openregister that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, added in ConductionNL/.github#546) flags six
shipped URLs here: website, bugs, repository and three screenshots, all still
pointing at codeberg.org.

GitHub is the only host; Codeberg is retired. The screenshots move to
raw.githubusercontent.com rather than a github.com/raw redirect, and all three
were checked rather than assumed — HTTP 200 each. A green gate with dead image
URLs would be worse than the finding.

Not caused by this branch's lock bump: gate-94 landed at 02:18 UTC and this
app's last development run was 01:51 UTC, so the PR runs are simply the first to
be measured against it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/filinq that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags nine shipped
URLs here — website, bugs, repository, three screenshots and three markdown
links in the description.

Two things had to change together, and only one of them is the host:

  codeberg.org/Conduction/DOCUDESK  ->  github.com/ConductionNL/FILINQ

The Codeberg URLs still carry the app's PRE-RENAME name. Swapping only the host
would have produced github.com/ConductionNL/docudesk — which resolves today
purely because GitHub redirects a renamed repo, and stops resolving the moment
anyone creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and all three were fetched rather than assumed — HTTP 200 each.

Not caused by this branch's lock bump: gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it.
rubenvdlinde pushed a commit to ConductionNL/dossiq that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/procest  ->  github.com/ConductionNL/dossiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/procest, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/integriq that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/openconnector  ->  github.com/ConductionNL/integriq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/openconnector, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/launchpad that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/launchpad  ->  github.com/ConductionNL/launchpad

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/launchpad, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/zaakafhandelapp that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/zaakafhandelapp  ->  github.com/ConductionNL/zaakafhandelapp

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/zaakafhandelapp, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/stackiq that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/softwarecatalog  ->  github.com/ConductionNL/stackiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/softwarecatalog, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde pushed a commit to ConductionNL/thematiq that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/nldesign  ->  github.com/ConductionNL/thematiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/nldesign, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.
rubenvdlinde added a commit to ConductionNL/dossiq that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/procest  ->  github.com/ConductionNL/dossiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/procest, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/filinq that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(info.xml): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags nine shipped
URLs here — website, bugs, repository, three screenshots and three markdown
links in the description.

Two things had to change together, and only one of them is the host:

  codeberg.org/Conduction/DOCUDESK  ->  github.com/ConductionNL/FILINQ

The Codeberg URLs still carry the app's PRE-RENAME name. Swapping only the host
would have produced github.com/ConductionNL/docudesk — which resolves today
purely because GitHub redirects a renamed repo, and stops resolving the moment
anyone creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and all three were fetched rather than assumed — HTTP 200 each.

Not caused by this branch's lock bump: gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/integriq that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/openconnector  ->  github.com/ConductionNL/integriq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/openconnector, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/launchpad that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/launchpad  ->  github.com/ConductionNL/launchpad

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/launchpad, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/openregister that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(info.xml): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, added in ConductionNL/.github#546) flags six
shipped URLs here: website, bugs, repository and three screenshots, all still
pointing at codeberg.org.

GitHub is the only host; Codeberg is retired. The screenshots move to
raw.githubusercontent.com rather than a github.com/raw redirect, and all three
were checked rather than assumed — HTTP 200 each. A green gate with dead image
URLs would be worse than the finding.

Not caused by this branch's lock bump: gate-94 landed at 02:18 UTC and this
app's last development run was 01:51 UTC, so the PR runs are simply the first to
be measured against it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/stackiq that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(psalm): stub OpenRegister's contract, which v1.9.0 stopped autoloading

hydra-gates v1.9.0 removed `OCA\OpenRegister\Contract\` from its runtime psr-4
autoload (ConductionNL/.github#531). The removal was correct — that prefix is
longer than openregister's own, so a vendored copy in ANY app defined the
contract for the whole process — but it was verified against PHPUnit only.

Psalm never runs the test bootstrap; it resolves types through the composer
autoload map. So the guarded require added to this app's bootstrap does nothing
for it, and 213 UndefinedClass errors appeared for a class the app typehints but
does not own.

This is the same situation as the decidesk event stubs already in this file: a
sibling Nextcloud app supplies the type at runtime, so it is absent from the
analysis path and gets declared here. A stub teaches the analyser the shape
without putting the class back into the runtime autoloader, which is what caused
the original defect.

Measured in this checkout on the real v1.9.0: 213 errors before, 0 after —
"No errors found!", psalm exit 0.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/softwarecatalog  ->  github.com/ConductionNL/stackiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/softwarecatalog, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/thematiq that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/nldesign  ->  github.com/ConductionNL/thematiq

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/nldesign, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde added a commit to ConductionNL/zaakafhandelapp that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(metadata): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags the shipped
URLs in this app's metadata — website, bugs, repository and screenshots — as
pointing at codeberg.org. GitHub is the only host.

Two things changed together, and only one of them is the host:

    codeberg.org/Conduction/zaakafhandelapp  ->  github.com/ConductionNL/zaakafhandelapp

The Codeberg URLs still carried the app's PRE-RENAME name. Swapping only the
host would have produced github.com/ConductionNL/zaakafhandelapp, which resolves today
purely because GitHub redirects a renamed repo — and stops the moment anyone
creates a repo at the old path.

Screenshots move to raw.githubusercontent.com rather than a github.com/raw
redirect, and every one was fetched rather than assumed: HTTP 200 each. A green
gate with dead image URLs would be worse than the finding it silenced.

Not caused by this branch's lock bump — gate-94 landed at 02:18 UTC, after this
app's last development run, so the PR runs are simply the first measured against
it. Pre-existing debt, newly visible.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
rubenvdlinde pushed a commit to ConductionNL/nextcloud-app-template that referenced this pull request Aug 22, 2026
… host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags seven shipped
URLs here: the three <documentation> links, website, discussion, bugs,
repository and the app-store screenshot. GitHub is the only host.

This repo keeps its name, so unlike the renamed apps only the host changes. The
screenshot moves to raw.githubusercontent.com rather than a github.com/raw
redirect, and was fetched rather than assumed — HTTP 200.

Worth fixing here above all: this is the template every new app is scaffolded
from, so the Codeberg URLs were being copied forward into each new app's
metadata.
rubenvdlinde added a commit to ConductionNL/nextcloud-app-template that referenced this pull request Aug 22, 2026
* chore(deps): refresh the shared Conduction locks

hydra-gates v1.8.2 -> v1.9.0
nc-vue      2.10.1 -> 2.11.1

Lock-only: both packages are already declared with caret ranges that
permit these versions, so nothing about what this app ACCEPTS changes
- only what it currently resolves to. Opened by the weekly fleet
shared-dependency bump, because a lock nobody re-resolves is a pin
nobody chose.

Merging is gated by this repository's own suite, deliberately: taking
hydra-gates v1.8.1 added patchObject() to a published interface, which
is a load-time fatal for any concrete double that implements it without
the method. CI is the only thing that can tell a safe bump from that.

* fix(info.xml): point shipped URLs at GitHub, not the retired Codeberg host

gate-94 (retired-git-host-metadata, ConductionNL/.github#546) flags seven shipped
URLs here: the three <documentation> links, website, discussion, bugs,
repository and the app-store screenshot. GitHub is the only host.

This repo keeps its name, so unlike the renamed apps only the host changes. The
screenshot moves to raw.githubusercontent.com rather than a github.com/raw
redirect, and was fetched rather than assumed — HTTP 200.

Worth fixing here above all: this is the template every new app is scaffolded
from, so the Codeberg URLs were being copied forward into each new app's
metadata.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Co-authored-by: Conduction <dev@conduction.nl>
rubenvdlinde added a commit to ConductionNL/dossiq that referenced this pull request Aug 22, 2026
…ow the moves

GATE 94 CAUGHT THIS REPO. The gate added in ConductionNL/.github#546 failed on
this PR with 9 shipped URLs pointing at Codeberg — procest's own appinfo/info.xml.

Worth recording why the fleet sweep missed it: that sweep read each repo's
DEFAULT branch, and procest's default is `main`, which had already been
repointed. `development` had not. So the app store metadata was correct on one
branch and stale on the branch everything is built from — exactly the split the
gate exists to find, and it found it on the change that introduced it.

Repointed the same way the fleet sweep does: repo name resolved from the API
(procest -> ConductionNL/dossiq, renamed 2026-08-21) and every asset URL probed
until one returns 200, rather than substituting the domain and hoping. `main`'s
version was NOT copied — it carries a different, single-screenshot set.

E2E, three scenarios following surfaces this change moved:
- Doorlooptijd's heading is the dashboard page's title now ("Processing time"),
  not the retired component's <h2>Processing Time Analytics</h2>. The old
  wording survives as the subtitle, which the same spec already asserts.
- The two Settings scenarios are retargeted from the retired in-app
  /apps/procest/settings at /settings/admin/procest. One of them carried
  FIXME(#719) saying the in-app page rendered only section chrome and that "the
  same components do render on /settings/admin/procest" — retiring that page
  resolves the FIXME rather than working around it, so the test is un-fixme'd.

gate-94 verified locally: 0 failures across 5 shipped metadata files.
rubenvdlinde added a commit to ConductionNL/dossiq that referenced this pull request Aug 22, 2026
…st side) (#1328)

* refactor(ai): delegate human-oversight decisions to hermiq (E1, procest side)

Hermiq owns AI oversight; procest owns the moment a handler accepts, rejects or
corrects a suggestion. This is procest's half of E1 — it lands AFTER hermiq #514,
which added the advisory Approval variant and the event contract this dispatches.

WHAT MOVES, AND WHAT DELIBERATELY DOES NOT. procest's audit log holds two kinds
of entry: `action: suggestion`, which records that the model RAN, and
`userAction: accepted|rejected|modified`, which records that a human JUDGED it.
Only the second is EU AI Act Art. 14 evidence. Sending the first would put rows
in hermiq's oversight log that nobody decided, so suggestion entries stay in
procest's own log.

VOCABULARY IS TRANSLATED, NOT FLATTENED. procest says `modified`; hermiq says
`overridden`. Getting that mapping backwards would file every correction as a
rejection, which is the one distinction an oversight audit most needs.

THE LOCAL WRITE STAYS. Delegation is additive, not a replacement: on an instance
without hermiq the local copy is the only one, and dropping it would make the
existence of an oversight trail depend on which apps happen to be installed.
`recordUserAction()` now also returns `delegated`, so the caller can tell
"recorded centrally" from "local only" — those look identical otherwise.

DELEGATION NEVER FAILS THE HANDLER. Unlike ContractDecisionDelegationService,
which fails closed because it GATES work, this records something that already
happened: the handler has acted and the case has moved on. Refusing after the
fact would turn an audit outage into a functional one.

MIGRATION RUNS THROUGH THE SAME PATH. MigrateAiOversightToHermiq replays existing
decisions through the identical event, so old evidence travels exactly as new
evidence does and hermiq never reads procest's register. Idempotent (stable
externalRef per source entry) and non-fatal — an upgrade must not fail because an
audit projection could not complete, and the local copies remain to replay from.

Retires `/settings/ai-oversight` and `/settings/ai-oversight/:id`, replacing the
menu entry with a deeplink to `/apps/hermiq/ai-oversight` (hermiq is
history-routed like procest, unlike OpenRegister which needs the `#/` form —
verified, not assumed).

Also records C2 and D1–D4 as DEFERRED in tasks.md: the fleet is mid-way through
consolidating the flow engine into OpenRegister (ADR-065), and re-expressing
automatic actions or decidesk's approval routes against a moving target would
have to be redone.

Tests: AiOversightDelegationServiceTest 10/10 covering each vocabulary mapping,
the suggestion-only refusal, document-vs-case subject resolution, the
no-subject refusal, map-value rendering, replay idempotency and the
hermiq-absent path. Adds tests/Stubs/Hermiq so the by-name contract is actually
exercised. Full suite 2313 tests green. psalm 0 errors. check:manifest Ajv PASS
(50 pages). l10n and prettier clean.

Live-verified: /apps/procest/settings/ai-oversight no longer resolves; exactly
one nav entry, pointing at /apps/hermiq/ai-oversight, which returns 200.

* fix(quality): close the three Hydra gate failures — and a real regression they exposed

CI ran gate-16, gate-26 and gate-60 against #1323. All three were genuine, and
gate-26 was pointing at something worse than a missing baseline.

gate-26 REVEALED FOUR DELETIONS THAT SILENTLY CAME BACK. It flagged
DoorlooptijdDashboard.vue and TenantOnboardingDashboard.vue as "new page
components" — files this change had DELETED. They were deleted in A3 and B3, and
then re-added by the F1 commit: `git add -A src/` re-staged them because they
were still sitting on disk. ProcessMiningDashboard.vue and TermijnDashboard.vue
came back the same way. All four were dead — nothing imports them, the manifest
routes to their widget replacements — so the app was correct and the repository
was not. Deleted again, and this is exactly the kind of thing that survives every
green check except a gate that asks "what is new here".

gate-16: 66 changed methods without @SPEC, now 0. The widget components, the
mixins and the three stores each carried only a file-level tag; every computed,
action, getter and lifecycle hook now points at the capability spec. Four members
had no docblock at all and got one.

gate-60: `ShieldAccountOutline` on the AVG link is not registered in
src/icons.js, so it rendered as NO icon rather than a fallback. Replaced with
`ShieldLockOutline`, which is both registered and ADR-077's `privacy` concept —
the AVG processing register is privacy, not administration.

The first attempt at that fix was WRONG and the gate caught it: it also changed
the admin link from `CogOutline` to `ShieldAccountOutline`, reading ADR-077's
"admin" row. Running the checker locally showed ADR-077 Tier A requires
`CogOutline` for the `settings` concept, and that the icon swapped in was itself
unregistered. Reverted; the lesson is that the ADR table has two rows that look
applicable and only the checker knows which one governs a given label.

Also removes an orphaned comment fragment the C1 visual-spec edit left behind.

All three gates verified locally against the real checkers from
ConductionNL/.github: spec-coverage count=0, visual-coverage PASS (7 new pages,
all with a visual proof), icon-vocabulary 0 failures. eslint 0 errors,
prettier clean, l10n en/nl in sync, check:manifest Ajv PASS, phpunit 2313 green.

* fix(ai): the oversight replay read the wrong key — it would have migrated nothing

phpstan caught a silent no-op before it ever ran. MigrateAiOversightToHermiq read
`$batch['results']`, but AiAuditLog::list() returns `{entries, total, limit,
offset}`. The key never existed, so `?? []` handed back an empty array on every
page, the loop broke on the first iteration, and the step would have reported
"AI oversight replay: no audit entries to consider" on an instance full of them —
while the upgrade went green and the evidence stayed behind.

This is the exact failure shape this change set has been finding all along: a
default-valued read turning missing data into confident wrong behaviour. Fixed to
`$batch['entries']`, and read WITHOUT a `??` fallback so a future shape change
fails loudly instead of quietly returning nothing.

MigrateAiOversightToHermiqTest pins it: an entries-bearing log must report
"1 decision(s) sent", not "no audit entries".

Also closes the other CI findings on this PR:
- phpcs: missing @PARAM for the new AiAuditService dependency, three internal
  calls needing named arguments, and a missing @SPEC on getName().
- phpmd: run() was doing paging AND per-entry handling AND reporting
  (cyclomatic 11, NPath 219, an else clause). Split into replayPage() and
  report(); the gate was right that two concerns were tangled.

phpcs 0 errors across lib/, phpmd clean, phpstan [OK] No errors, phpunit 2327
tests green.

* fix: gate-94 catches procest's own info.xml, and three e2e specs follow the moves

GATE 94 CAUGHT THIS REPO. The gate added in ConductionNL/.github#546 failed on
this PR with 9 shipped URLs pointing at Codeberg — procest's own appinfo/info.xml.

Worth recording why the fleet sweep missed it: that sweep read each repo's
DEFAULT branch, and procest's default is `main`, which had already been
repointed. `development` had not. So the app store metadata was correct on one
branch and stale on the branch everything is built from — exactly the split the
gate exists to find, and it found it on the change that introduced it.

Repointed the same way the fleet sweep does: repo name resolved from the API
(procest -> ConductionNL/dossiq, renamed 2026-08-21) and every asset URL probed
until one returns 200, rather than substituting the domain and hoping. `main`'s
version was NOT copied — it carries a different, single-screenshot set.

E2E, three scenarios following surfaces this change moved:
- Doorlooptijd's heading is the dashboard page's title now ("Processing time"),
  not the retired component's <h2>Processing Time Analytics</h2>. The old
  wording survives as the subtitle, which the same spec already asserts.
- The two Settings scenarios are retargeted from the retired in-app
  /apps/procest/settings at /settings/admin/procest. One of them carried
  FIXME(#719) saying the in-app page rendered only section chrome and that "the
  same components do render on /settings/admin/procest" — retiring that page
  resolves the FIXME rather than working around it, so the test is un-fixme'd.

gate-94 verified locally: 0 failures across 5 shipped metadata files.

* fix(dashboard): the straplines were declared under a key nothing reads

CnDashboardPage's prop is `description`, not `subtitle`. The manifest schema
accepted `subtitle`, Ajv passed, and all three converted dashboards rendered NO
strapline — the components they replaced each drew one, so the conversion
silently dropped it. A declaration that validates and does nothing.

The e2e suite is what caught it: the Doorlooptijd scenario asserts its heading
AND its strapline, the heading fix landed, and the strapline assertion then
failed on text that was never rendered. Worth noting the shape — an assertion
that survived a rewrite is what found a regression the rewrite introduced.

Verified live: `.cn-dashboard-page__description` now reads "SLA adherence and
processing time analysis".

Also fixes the Settings scenario's strict-mode violation. Retargeting it at
/settings/admin/procest worked — and that surface renders EVERY section, four of
which have their own labelled save ("Save mandate matrix settings", "Save
consultation settings", …). The retired in-app page rendered only chrome, so a
loose "Save" matched exactly one button there. Now matched with `exact: true`,
which is the Configuration section's own control.

check:manifest Ajv PASS, prettier clean.

* fix(dashboard): restore the back-to-Dashboard control the conversion dropped

Third and last thing the Doorlooptijd e2e scenario caught. The component this
replaced drew an explicit back-arrow to the Dashboard; the dashboard page type
draws no such control, so converting the page silently removed it. The spec
asserted it, which is how it surfaced — the affordance is restored as a
declarative `config.headerActions` navigate action rather than deleting the
assertion.

The icon took two tries and the gate was right both times. `ArrowLeft` mirrors
what the old component drew, but it is not registered in src/icons.js — it would
have rendered as NO icon, not a fallback — and ADR-077 Tier A requires
`ViewDashboardOutline` for the `dashboard` concept anyway. The action's
DESTINATION names it, not the direction of travel.

Live-verified: heading "Processing time", description "SLA adherence and
processing time analysis", and one "Dashboard" button — the three things the
scenario asserts, in one page load.

icon-vocabulary 0 failures, check:manifest Ajv PASS.

* docs(openspec): resume plan for C2/D now the flow engine is consolidated

The flow-engine consolidation has landed, so C2 and D1-D4 are no longer blocked.
PLAN-RESUME.md carries the state, the step order and the traps, written to
survive a context compaction rather than living in a conversation.

The thing worth reading before starting C2: `automaticAction` is NOT a
standalone object. `caseType.workflowSteps` embeds references to it in three
places (`automaticActions`, `config.autoActions`, `config.escalationRule`), so
retiring the two pages does not retire the concept — every case type in the
field points at these by id, and a migration has to rewrite those references or
keep them resolvable. That has to be established before the spec, not after.

D1 keeps one gate the flow engine did not open: it removes the two besluitvorming
routes that the active consume-decidesk-besluitvorming-leaf change deliberately
keeps alive, so it lands after that one, not beside it.

Also records the merge order for the open stack — #1328 into #1323, then #1323
into development, because #1323 alone still fails gate-16 and gate-60 and those
fixes live on #1328.
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