Skip to content

Release: merge development into beta - #758

Open
github-actions[bot] wants to merge 117 commits into
betafrom
development
Open

Release: merge development into beta#758
github-actions[bot] wants to merge 117 commits into
betafrom
development

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR to sync development changes to beta for beta release.

Merging this PR will trigger the beta release workflow.

Reminder: Add a major, minor, or patch label to this PR to control the version bump. Default is patch.

rubenvdlinde and others added 30 commits August 18, 2026 15:57
…action-surface

Artifacts only. Every task box is unticked; nothing here is wired to anything
yet, and each change is picked up by `/opsx-apply` when it is scheduled.

Committed because these were sitting UNTRACKED in the shared checkout across ten
apps at once. An untracked directory is one file-sweep away from being swept
into an unrelated commit and one branch switch away from being lost, and these
carry the design reasoning rather than just a title.
…0260820130046

chore(release): 0.1.141-unstable.20260820130046
…0260820201847

chore(release): 0.1.141-unstable.20260820201847
…0260820203911

chore(release): 0.1.141-unstable.20260820203911
…0260820205557

chore(release): 0.1.141-unstable.20260820205557
`CnObjectDataWidget.editable` defaults to TRUE, so every property named in a
data widget's `include` list becomes a text box the user can type into. That
put lifecycle state and audit stamps — `status`, `lifecycle`, `submittedAt`,
`approvedBy`, `openedAt`, `closedAt`, `publishedAt`, `enactedAt` — in front of
users as editable fields.

These are written by the backend when a transition lands (`TransitionEngine`
stamps them through `saveObject()`), so an input for them is a control that
can only ever fail or confuse: the guarded path is the lifecycle buttons, and
`LifecycleValidationListener` rejects anything that is not a legal transition.

Locked with per-field `overrides.<field>.editable: false` rather than
`editable: false` on the widget: these panels mix process state with fields
the user legitimately edits, and a blanket lock would make those read-only
too.

NOT fixed here: widgets that declare no `include` at all render EVERY schema
property, and enumerating their fields in the manifest would drift the moment
the schema changes. 52 such widgets fleet-wide expose 124 process fields.
Closing those needs a server-side "system-owned" marker, which OpenRegister
does not have — `readOnly:true` has no bypass for backend callers and would
break the transition that writes the field. Filed as
ConductionNL/openregister#2644.

Inserted textually, one compact line per widget, so the diff is the change and
nothing else: a full JSON re-serialisation reflowed hand-compacted lines and
turned this into thousands of lines of churn. A verifier re-parses both files
and asserts the only structural difference is the added overrides, and that no
non-process field was locked.
…0260820212102

chore(release): 0.1.141-unstable.20260820212102
…0260820214311

chore(release): 0.1.141-unstable.20260820214311
…0260820220515

chore(release): 0.1.141-unstable.20260820220515
fix(manifest): process and audit fields are no longer editable inputs
…0260820223149

chore(release): 0.1.141-unstable.20260820223149
…0260820224848

chore(release): 0.1.141-unstable.20260820224848
stylelint 15.11.0 -> 17.14.1 with every package that peers on it:

  @nextcloud/stylelint-config           ^2.4.0  -> ^3.2.2
  stylelint-config-recommended-scss    ^13.1.0  -> ^17.0.1
  stylelint-config-recommended-vue      ^1.6.1  -> ^2.0.0
  postcss-html                          ^1.8.1  -> ^2.0.0
  stylelint-config-html                (absent) -> ^2.0.0

None can move alone: vue-config@2 peers 'postcss-html ^2.0.0' and
'stylelint-config-html >=2.0.0'; postcss-html@2 breaks vue-config@1.6.1's own
'^1.0.0' peer; scss-config@17 peers 'stylelint ^17'. @nextcloud/stylelint-config
is the member that decides it — v2.4.0 still declares indentation /
string-quotes / number-leading-zero / selector-list-comma-newline-after, all
removed in stylelint 16.

Then 10 real errors, in 4 files:
  8x  word-break: break-word  -> overflow-wrap: break-word
  1x  word-wrap: break-word   -> overflow-wrap: break-word
  1x  clip: rect(0,0,0,0)     -> clip-path: inset(50%)

Fixed by hand, NOT with --fix. The autofix also rewrites the 86 advisory
csstools/use-logical warnings (text-align: left -> start, padding-left ->
padding-inline-start, ...) across 28 files. Those change how the UI lays out
under RTL and have nothing to do with this bump; openbuild, scholiq and hermiq
all carry the same warnings unfixed. A dependency upgrade should not smuggle in
a directional-CSS change.

Result: 10 errors -> 0, exit 0. The 86 warnings are unchanged and non-blocking.
…0260820231107

chore(release): 0.1.141-unstable.20260820231107
…0260820233952

chore(release): 0.1.141-unstable.20260820233952
build(deps-dev): move the whole stylelint family to 17 as one upgrade
…0260821000125

chore(release): 0.1.141-unstable.20260821000125
github-actions Bot and others added 30 commits August 24, 2026 09:31
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The catalog register's slug moves with the app's identity, and a repair step
renames the existing row BEFORE the import so the rename lands on the register
that already holds the data.

Why the repair step is not optional. OpenRegister resolves a register by SLUG
and by nothing else, and its not-found branch is not an error path — it is the
"create a new one" path. Shipping the renamed slug in the register JSON alone
would therefore rename nothing: the import finds no match, CREATES A SECOND,
EMPTY REGISTER, and every stored object stays behind on the old row, reachable
by nothing. Nothing errors; the app just looks new.

Why it moves no data. An object is bound to its register by NUMERIC id — every
shard table's `_register` column holds the id, and the tables are named
`oc_openregister_table_<registerId>_<schemaId>`. The slug appears nowhere in
the physical layout, so this is a one-column UPDATE on one row (id 11 on the
dev instance, which it keeps).

The step is idempotent, non-destructive, and never throws (it runs under
<install>, where an escaping exception aborts the install and the app never
enables). When both the old and new slugs already exist it REFUSES and renames
neither — merging two registers is a decision about data, not a rename. It also
re-points a stored app-config `register` value that still holds an old slug,
guarded on the VALUE rather than the key.

`vng-gemma` deliberately stays exactly as it is: it holds VNG GEMMA reference
data, is not this app's own store, and its name is answered to elsewhere.

Also fixes an inconsistency the app-id rename left behind: `x-openregister.app`
has said `stackiq` since #708 while the register row still said `voorzieningen`
— which is precisely the fork this step exists to prevent. Installs that have
imported since then may already carry an empty `stackiq` register, and the step
refuses rather than merging on those.
…ace moved (#728)

* fix(decidiq): contract delegation was silently off — the event namespace moved

`ContractApprovalService` pinned `\OCA\Decidesk\Event\DecisionRequestedEvent`.
The decision app renamed to `OCA\Decidiq` with no compatibility alias, so
`isDelegationConfigured()` began returning false on instances where the app was
installed, and contract approvals stopped delegating.

Nothing reported it, and nothing could: `class_exists` going false is exactly
what an uninstalled optional app looks like, which is the case the guard was
written for. The feature reads as "not configured" rather than "broken".

Measured on a running instance:
  OCA\Decidesk\Event\DecisionRequestedEvent   MISSING
  OCA\Decidiq\Event\DecisionRequestedEvent    EXISTS

The constant is now a LIST, newest first, resolved to the first that exists. The
old spelling stays until no supported install ships it — dropping it would break
the integration in the other direction during a staggered upgrade, which is the
window this broke in to begin with.

An app cannot move another app's class name; it can only follow it. So the test
asserts that property rather than a literal: the list names the current
namespace, tries it first, and still names the old one. Pinning one spelling
fails a test now instead of silently disabling a feature later.

NOT changed: `SOURCE_APP = 'softwarecatalog'`. That is this app's id AS THE
DECISION APP KNOWS IT and is echoed back on the conclusion event, so it moves
only when both sides move together.

Found by a fleet sweep for stale cross-app namespaces after the same defect was
confirmed in dossiq; openregister (→ Keepiq) and decidiq (→ Filinq) carried it
too and are fixed in their own repos.

* fix(psalm): resolve the event class ONCE, and narrow it where it is used

Psalm rejected the previous commit: `Type null cannot be called as a class` at
`new $eventClass(...)`. It was right, and the shape it caught is worth naming.

The method called `isDelegationConfigured()` — which resolves the class and
throws away the answer — and then resolved AGAIN at the call site with no guard.
Two lookups of the same thing, only the first of them checked. The second had a
`?string` reaching `new`, so an instance with no decision app installed would
have gone from a clear "delegation is not available" to a fatal on a null class
name.

Now it resolves once, narrows, and fails closed on the null — the same refusal
as before, from the value actually used.

⚠️ I did not run psalm locally before pushing; I ran phpunit, phpcs and phpstan,
and phpstan does not flag this. One tool green is not the gate green, and the
tools disagree by design.

psalm "No errors found", phpcs 0, phpstan [OK] on the touched file.
#724)

The final step had no `task`, so the guided tour stopped without telling the
user where to go next. It now closes on the documentation, per the fleet rule
that a walkthrough's last step points somewhere.

The CTA targets the `Documentation` nav item that already exists in this
app's menu, so it lands on a real destination rather than a URL invented for
the copy.

The same step also carried voice defects the shared writing skill bans:
"Nicely done" is praise rather than voice, the em-dash is stripped
fleet-wide, and "reopen this tour anytime from the … menu" is housekeeping in
the one line a user is most likely to act on. The title now states what the
user actually has, and the body says what to do with it.
…es this site (#726)

* fix(docs): publish from development, to the worker that actually serves this site

Two silent failures, both of which had to be fixed before this site could
update at all.

1. The workflow triggered on `documentation`, a branch that exists but
   nobody updates. Green and idle for months while the live site aged.

2. `worker-name` was never passed, so the callee derived it from `cname`.
   Since the app-id rename `cname` is the NEW host, while the worker that
   actually holds the custom domains is still named after the OLD app id.
   The derived name points at a worker that does not exist — deploying it
   CREATES a second worker while both custom domains keep routing to the
   original. Every deploy green, reaching nobody, with the live-site
   verification added in ConductionNL/.github#555 as the only thing that
   would ever have noticed.

Measured today: both hostnames still serve the pre-rename title while
docs/docusaurus.config.js has carried the new one since the rename.
Nothing has carried a build to the edge.

* fix(docs): ship the og:image file the config already names

The docs build FAILS, and has been failing — it was simply never run,
because the workflow triggered on a branch nobody updates. Making the
trigger correct surfaced it on the first run.

The app-id rename updated `docusaurus.config.js` to point og:image at
the new filename and left the actual PNG under its old name, so the
AI-baseline validator's last check fails:

  ✗ og:image URL resolves to a file in the build

and `npm run build` exits 1 via postbuild. Nothing could have published
even with a correct trigger and a correct worker.

Renames the asset to the name the config has been asking for. No
references to the old filename remain.

Verified locally: npm ci --legacy-peer-deps && npm run build now exits 0
with all 10 AI-baseline checks passing.
GitHub is the only host for this org — Codeberg was a mirror and is no
longer used, including for issues. Many of these links also carried a
PRE-RENAME repo name: this repo is now ConductionNL/stackiq (was
softwarecatalog), nldesign is thematiq, OpenConnector is integriq, and
decidesk is decidiq.

Converted (24 files):
- 21 PHP @link docblock tags -> github.com/ConductionNL/stackiq (13 files)
- 4 PHP @link tags naming OpenConnector -> github.com/ConductionNL/integriq
- 8 README dependency links (openregister, opencatalogi, nldesign->thematiq,
  tilburg-woo-ui, launchpad)
- docs/static/llms.txt org link -> github.com/ConductionNL (its label already
  said "GitHub")
- .forgejo/workflows/tests-live.yml: the live `git clone` of openregister was
  still pointing at codeberg.org — CI would clone from a dead host.
- 4 hydra change records ("repo" field, issue-less only)

Issue / PR numbers deliberately NOT mapped. Codeberg numbers do not correspond
to GitHub ones, so rewriting only the host would point at a real but unrelated
GitHub item. The two decidesk PR-160 links are replaced with plain text.

Deliberately left alone (see PR body):
- 32 "configuration" URLs in lib/Settings/softwarecatalogus_register.json.
  These are RUNTIME-fetched register config pointing at
  opencatalogi/.../publication_register_magic.json — a file that does not
  exist in ConductionNL/opencatalogi on ANY branch. Repointing the host would
  swap a dead Codeberg URL for a dead GitHub one and merely look fixed.
- 4 hydra records whose codeberg "repo" sits next to an issue number/URL.
- The beta-surface-alignment openspec requirement that MANDATES the word
  "Codeberg" in docs/GOVERNMENT-FEATURES.md, and that doc line.
- .forgejo runner labels and the CODEBERG_TOKEN secret reference.
This repository's only issue forms lived under `.forgejo/issue_template/`.
GitHub is the fleet's only host, so those forms are invisible to everyone
filing an issue here.

Two approved fleet changes make this urgent:

1. `.forgejo/` is being removed fleet-wide. Without this port that removal
   would delete the only issue forms this repo has, leaving contributors
   with a blank issue box.
2. The shared library's `DEFAULT_FORGE` moves from `codeberg` to `github`.
   The in-product "Request a feature" deep-link then targets a GitHub Issue
   Form named exactly `feature-request.yml`. If that file is absent GitHub
   silently drops every pre-filled field instead of erroring, so the app
   context (app, page, surface, object, spec-ref) would be lost without a
   single visible failure.

Copies all four templates to `.github/ISSUE_TEMPLATE/`, keeping the
filenames identical. `.forgejo/` is deliberately left untouched; its removal
is a separate later change.

Conversion is lossless: Forgejo's issue-template schema is derived from
GitHub's, and every construct used here (markdown/input/textarea/dropdown
blocks, `render: shell`, `validations.required`, `labels`, `assignees`,
`title`) is valid GitHub issue-form syntax. Nothing was dropped or reworded.
The top-level `type: "Feature"` in feature-request.yml was verified against
the ConductionNL org issue types, where "Feature" exists and is enabled.

No `config.yml` was added: `.forgejo/issue_template/` has no equivalent.
'quality / E2E Tests (Playwright)' has been red on development. The product
is fine; the assertion was ambiguous.

Each component row renders the NAME and its purl, so getByText('lodash')
substring-matched two spans — <span>lodash</span> and
<span>pkg:npm/lodash@4.17.21</span> — and Playwright's strict mode failed
the assertion rather than choosing one. Adding the purl column is what made
a previously-unique string match twice, so this broke without either the
component or the test being edited.

The express assertion had the identical defect and never ran: the lodash
line failed first and masked it. Fixing only the reported line would have
surfaced express as the next failure. Verified against the fixtures —
cyclonedx-1.5-valid.json is express/pkg:npm/express@4.19.2 and vue.

The absence assertion is deliberately left as a substring match: for
'is it gone' the looser match is the STRONGER claim, since it also fails if
the purl survives, and toHaveCount tolerates multiple matches so strict mode
never applies there.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…stackiq application id (#723)

* fix(repair): move OpenRegister schemas from softwarecatalog onto the stackiq application id

OpenRegister resolves a REGISTER by slug alone, but a SCHEMA by the PAIR
(application, slug) via SchemaMapper::findByApplicationAndSlug(). This app passes
appId: Application::APP_ID = 'stackiq', while every schema it already owns still
carries application = 'softwarecatalog'. The pair matches nothing, and ImportHandler's
not-found branch is not an error path — it is the create-a-new-one path. The next
import therefore builds a second, EMPTY schema set under the new application id while
every stored object stays bound to the old rows. Nothing errors; the app renders
empty collections.

Measured on a live install: 21 schemas under softwarecatalog, zero under stackiq, and
zero slug collisions — all 21 move cleanly.

A DIFFERENT COLUMN from RenameDutchSchemaSlugs (that step rewrites
openregister_schemas.slug, this one openregister_schemas.application), and the third
store keyed by app id after oc_appconfig and oc_preferences. Registered after
RenameDutchSchemaSlugs so the collision check sees the final slugs, and before
InitializeSettings because that triggers the register import.

The step refuses rather than merges where a slug already has a twin under the new
application id, distinguishes a FAILED READ from an EMPTY RESULT, never deletes a
schema and never throws — it runs under <install>, where an escaping exception aborts
the install.

* fix(repair): run RenameDutchSchemaSlugs before the schema re-point on install

E2E failed on one test: the SBOM component table showed "lodash" twice where
the second import should have replaced the first. Development is 95/0; this
branch was 94/1, and the pre-merge commit on this branch was green — so the
merge introduced it.

Cause is a missing prerequisite in <install>. MigrateSchemaApplicationId says
of its own position:

  POSITION IS LOAD-BEARING IN BOTH DIRECTIONS. After RenameDutchSchemaSlugs,
  so the collision check is judged against the slugs the rows will actually
  carry; before InitializeSettings, which is what triggers the register import.

<post-migration> honours that — MigrateRegisterSlug, RenameDutchSchemaSlugs,
then MigrateSchemaApplicationId. <install> ran the first and third and skipped
the second, so on the fresh-install path the collision check was judged
against pre-rename slugs. That path is not an edge case: an app-id rename
presents to Nextcloud as a fresh install, which is the whole reason this block
repeats the steps.

The failure mode follows from the step's own contract — it refuses rather than
merges, leaving two rows where it sees a twin. Two schema rows is two
component rows, which is the duplicate "lodash" the test caught.

I had also claimed in the merge commit that the ordering constraint was
against MigrateRegisterSlug. It is not; it is against RenameDutchSchemaSlugs,
a different class rewriting a different column.
GitHub is the only host this organisation publishes to. No local checkout has
a Codeberg git remote, so nothing is pushed there and no workflow under
.forgejo/ has ever run for this repository.

Issue templates: the 4 templates under `.forgejo/issue_template/` were
already ported to `.github/ISSUE_TEMPLATE/` and were verified present there
before deletion (including `feature-request.yml`, which the in-product
"Request a feature" deep-link targets by that exact filename).

Release workflows: the deleted `.forgejo/workflows/` release jobs
(release-beta.yml release-stable.yml ) are superseded by
`.github/workflows/release.yml`, which is the live release path for this
repository.

.github/workflows/ is untouched — that is the live CI. Any CODEBERG_TOKEN
reference lived only inside the deleted files and goes with them.

Removes 12 file(s) under .forgejo/.
* fix(docs): pass secrets to the reusable documentation workflow

A called workflow receives no secrets from its caller unless they are passed
explicitly or inherited. Without `secrets: inherit` the callee sees an empty
`secrets.CF_API_TOKEN`, its "Publish to the Cloudflare Worker" step skips
itself on its own guard, and the run finishes green having written only
gh-pages — which nothing serves. The live docs site never changes and no
check goes red to say so.

Measured on planninq run 32715324775: all three jobs green, GitHub Pages
deploy success, Worker publish skipped, warn step reporting the Worker was
not updated.

* fix(docs): map the Cloudflare secrets explicitly instead of inheriting all

`secrets: inherit` handed the reusable documentation workflow every secret
this repo holds — the Nextcloud signing cert and key, the appstore token, the
deploy keys — for the sake of two Cloudflare values.

It also would not have worked. The org secrets are CLOUDFLARE_API_TOKEN /
CLOUDFLARE_ACCOUNT_ID and `inherit` passes secrets under their original names,
while the callee reads CF_API_TOKEN / CF_ACCOUNT_ID — so the publish step
would still have skipped itself and the run would still have gone green over
an unchanged live site.

Maps the two names explicitly instead, so nothing else crosses the boundary.
Depends on ConductionNL/.github#568, which declares both as optional secrets
on the callee: an explicit mapping only compiles for names the callee declares.

* fix(docs): map the Cloudflare secrets from the names that actually exist

This branch introduced the mapping reading `secrets.CLOUDFLARE_API_TOKEN` /
`secrets.CLOUDFLARE_ACCOUNT_ID`, which are not secrets anywhere in this org.
Mapping from a non-existent secret is not an error - it yields an empty string -
so the callee`s "Publish to the Cloudflare Worker" step would have skipped itself
on its own guard and the run would still have gone green, exactly the failure
this PR set out to fix. Caught before merge; measured on planninq run
32760529026, where the same spelling did land.

The real org secrets are CF_API_TOKEN / CF_ACCOUNT_ID, the same names the callee
declares and the same ones ConductionNL/.github deploy-docs.yml reads directly.
Only the mapping values change; the keys stay.

The comment claimed the names differ on each side. They do not, and that claim is
what produced the wrong values. Replaced with the reason that still holds:
`secrets: inherit` would hand the callee every secret this repo holds for the
sake of two Cloudflare values.
Closes #739.

Running the repo's own lint fixer produced a file the repo's own format check
rejected, so fixing lint the documented way landed a red `Frontend Check
(format)`.

It is not a rule disagreement, and eslint-config-prettier cannot prevent it.
That package only turns rules OFF, and the culprit is not a formatting rule:
`import-extensions/ban-inline-type-imports` rewrites an import, and its AUTOFIXER
emits its own text —

    import type {Page} from '@playwright/test';

— which is not what prettier wants (`{ Page }`, no semicolon). Any autofixer that
constructs source can do this; disabling formatting rules does not stop it.

So `lint-fix` now re-runs the formatter afterwards, which makes the two tools
ordered instead of competing: eslint decides what the code SAYS, prettier decides
how it LOOKS, in that order, always. That matches the config's own stated
doctrine — "exactly one of them is allowed an opinion and prettier is it".

Verified both directions on a clean tree: `eslint --fix` alone leaves
prettier --check REJECTING, and the chained script leaves it passing.

WORTH KNOWING, and the reason this went unnoticed: the two tools do not cover
the same files.

    lint    -> eslint src            (src only)
    format  -> prettier "**/*.{js,ts,vue,css,scss}"   (everything, incl. tests/)

`tests/e2e/**` is format-checked but never linted, so an eslint --fix run there
is outside the workflow CI exercises — which is exactly where I hit this. Whether
to widen eslint's scope to tests/ is a real decision with a findings backlog
behind it, so it stays in #739 rather than riding along here.
…m repo root (#743)

The 2026-08-24 fleet structure audit found softwarecatalog carrying 63 root
files beyond the nextcloud-app-template baseline — the largest root in the
fleet, against a fleet median of ~40. Most were one-off investigation scripts
and status notes from finished work.

Removed (39 files, ~6.5k lines):

- 27 one-off scripts: check_*.php, debug_*.php, find_objects_*.php,
  cleanup_*.php, enhance_archimate_service.php, and the test_*/test-* shell
  and PHP scripts superseded by test_archimate_unified.sh or by the
  Newman/Postman suite.
- test_small_archimate.xml — an unreferenced fixture; the kept ArchiMate
  suite reads lib/Settings/GEMMA_release.xml instead.
- 11 point-in-time status docs: AMEF_TESTING.md, ARCHIMATE_IMPORT_FIX.md,
  ARCHIMATE_PROJECT_STATUS.md, CIRCLE_TEST_DOCUMENTATION.md,
  FIX_VERIFICATION_SUMMARY.md, INTEGRATION_TEST_RESULTS.md,
  KOPPELINGEN_GEBRUIK_REFACTOR.md, README_DEBUG.md,
  WORKFLOW-UNSTABLE-RELEASE.md, Openregister.md, aanvullende-informatie.md
  is retained (issues.md links it).

Deliberately KEPT, because each is live-referenced:

- test-setup.sh — invoked as `bash stackiq/test-setup.sh` by
  .claude/commands/test.md and six persona test skills.
- compare_archimate.php — documented in docs/ARCHIMATE_QUICK_TEST.md and
  docs/ARCHIMATE_TESTING_GUIDE.md (4 call sites).
- compare_archimate.py — invoked by test_archimate_unified.sh:260.
- test_archimate_unified.sh — the consolidated ArchiMate suite.
- issues.md — NOT scratch. 3,659 lines tracking 137 VNG IGS issues and
  1,026 acceptance criteria; README.md calls it "the master file", it is
  listed in .distignore, and nine .claude commands/skills read it to drive
  the persona test suite.
- BUG_FIX_ORGANISATION_USER_ASSIGNMENT.md — named by open task 4.1 of the
  active openspec change organisation-parent-hierarchy-rbac-fix.

test_archimate_unified.sh's header comment is updated so its "this replaces"
list reads as history rather than pointing at files that no longer exist.

Root file count: 96 -> 57.

Refs ADR-099 Decision 2 (the repository root is a closed set).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Part of the 2026-08-25 fleet structure audit (ADR-100 Decision 2: the
repository root is a closed set; generated files are never tracked).

Ignore rules added: .stale/ /.e2e-state/ .phpunit.cache

`.stale/` was missing from ALL 19 fleet repos and is the one that
matters most operationally: agent scratch there grew unbounded and
filled the dev disk once already.

Refs ConductionNL/hydra ADR-100.

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Every `@self.configuration` in this register pointed at

  https://codeberg.org/Conduction/opencatalogi/raw/branch/master/
    apps-extra/opencatalogi/lib/Settings/publication_register_magic.json

which returns **HTTP 404**. Verified with curl, not assumed from the host
name. Three separate things had rotted at once:

  host ...... codeberg is no longer where opencatalogi lives
  path ...... the old monorepo layout `apps-extra/opencatalogi/...` is gone;
              the app is at its repo root now
  filename .. `publication_register_magic.json` was renamed to
              `publication_register.json`

So repointing the host alone would still have 404'd. The replacement is the
live file, confirmed HTTP 200 on main, master and development; `main` is the
repo's default branch and is what this now uses.

This matters because a `configuration` URL is FETCHED at register-import time.
A 404 there does not announce itself as a broken link — the import simply does
not get the configuration it asked for, which is the quiet-failure shape this
codebase has been bitten by before.

32 occurrences, all byte-identical, replaced in one pass. JSON re-parsed clean
afterwards.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Found by gate-96 (manifest-copy-style, ConductionNL/.github#581).

Two getting-started tour steps, both already real user copy.

The second takes a colon rather than a period, because the clause after the
dash explains what "the heart of your catalogue" means and belongs to the same
sentence:

  "Organisations are the heart of your catalogue: they own the contracts,
   modules and compliance records you track."

The welcome step also loses the comma splice the dash was hiding, joining with
"and" instead of leaving two sentences fused.

Verified: gate-96 0 findings over 153 strings, check:manifest PASS, test:l10n
PASS, check:schema-l10n PASS.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(nav): a Flows surface in this app, on the shared page types

ADR-110 Decision 4. A flow is app-specific — it operates on this app's objects —
so the authoring surface belongs here rather than behind a deep link to another
app's list. The ENGINE stays single (ADR-065): these pages are a scoped view
onto OpenRegister's one native flow store, not a per-app store.

Two manifest pages and one settings entry, no component files: `type: "flows"`
and `type: "flow-detail"` are shipped page types in @conduction/nextcloud-vue
2.19.0, scoped by `config.app`.

Note the layout of the diff: entries are appended textually rather than by
reserialising the manifest. A `json.dump` round-trip rewrote pipelinq's file as
a 3,950-line diff for a 20-line addition — correct output, unreviewable change.

* build(deps): @conduction/nextcloud-vue 2.19.0 for the flows page types

Required by the manifest change: `type: "flows"` / `type: "flow-detail"` are
rejected by the compiled validator in earlier versions, and CI installs with
`npm ci` — so the LOCK is what decides, not the `^2.x` range. Several of these
locks were pinned many minors back, which is why some lockfile diffs are large:
npm restructures the nested tree (mostly @esbuild platform binaries under
@nextcloud/vue) to satisfy 2.19.0's peers. No direct dependency other than
@conduction/nextcloud-vue changes.

* fix(icons): register Sitemap, or the Flows entry renders with no icon

An icon name a manifest uses but src/icons.js does not register renders as
NOTHING — not a fallback (ADR-077 rule 3). The Flows menu entry this PR adds
uses `Sitemap`, and this app never registered it, so the entry would have
shipped with an empty icon slot.

Caught by gate-60 icon-vocabulary. I had checked `Sitemap` was registered in
dossiq and carried the assumption to the fleet; each app keeps its own icons.js,
and six of the twelve did not have it. The six failing gate runs were exactly
those six apps.

Both halves are required: the import alone is dead code, the registry entry
alone does not resolve.
)

* fix(l10n): regenerate all 38 browser catalogues, and add the check

Every locale catalogue was stale: l10n/<locale>.json is read server-side by PHP
`$l->t()`, while the browser only ever sees `OC.L10N.register(...)` from
l10n/<locale>.js, and a raw .json is not served from an app directory at all.
A key added to the JSON and forgotten in the JS renders in English for every
browser with nothing reporting it.

Ported keepiq's generator, which reads the app id from appinfo/info.xml rather
than hard-coding it — a catalogue registered under a stale id is silently
ignored by `t()`, which matters in a fleet that renames apps.

Backfilled first, then generated. This app had only 4 keys living solely in
.js, but the same step run blind cost opencatalogi 21,662 translations and
integriq 5,240, so the order is now fixed rather than judged per app.

Verified with the assertion that matters: comparing every rebuilt .js against
its pre-change version, keys DROPPED = 0. Not "no locale has fewer keys than
its json" — that comparison is blind to this failure, which is how dossiq
silently lost 631 real translations before I went back and re-checked.

nl.js carries 691 keys, de.js 278. check:l10n-js exits 0 after the build.

* fix(l10n): translate the 59 untranslated manifest strings

The catalogue fix in the previous commit made the browser able to READ Dutch.
This gives it Dutch to read.

59 strings: the getting-started tour, the nav, and the organisation, contract,
module and compliance surfaces. Domain terms as this catalogue's users have
them: leverancier, moduleversie, compliance-claim, onderbouwend bewijs,
audittrail.

GEMMA stays GEMMA, and the two catalogue descriptions keep the Dutch word the
domain actually uses: "Blader door de dienstencatalogus, gefilterd op
GEMMA-architectuurdimensie." The English original wrote "the service (dienst)
catalogue" precisely because dienst is the term of record.

Compliance stays Compliance. It is the word on the page in Dutch
organisations, and "naleving" would read as a translation of a label nobody
calls that.

Built on the same branch rather than a fresh one, because the generator these
translations need is in this PR and not yet on development.

Verified: 0 manifest strings left without Dutch, keys DROPPED = 0, nl.js
registers under "stackiq" with 750 keys and resolves "Organisations" ->
"Organisaties", check:l10n-js PASS.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…760)

This app already has the generator and the `check:l10n-js` script; it just
never ran them in CI. That is the whole difference between an app that stays
translated and one that quietly stops.

Adding a key to l10n/<locale>.json and forgetting the .js is invisible without
this check: the server renders Dutch, the browser renders English, and every
other check passes. `l10n/<locale>.json` is read server-side by PHP; the
browser only ever sees `OC.L10N.register(...)` from `l10n/<locale>.js`, loaded
as a script tag.

Measured across the fleet today: the apps running this check had zero drift,
while the four without it had accumulated 142, 329, 257 and 1,090 unreachable
entries between them. Same code, same generator. The check was the difference.

It also caught a translation PR that merged green having changed nothing a
browser loads, which is how the whole thing started.

Appended to the existing frontend-checks list rather than replacing it, so
every check this repo already runs still runs.

Verified before pushing: the workflow YAML still parses, and
`node scripts/build-l10n-js.js --check` exits 0 on this tree, so the new leg is
green on arrival rather than red for someone else to clean up.
* fix(deps): development cannot npm install

* fix(deps): rebase the lock on development's, not a from-scratch resolve

The previous commit deleted package-lock.json before installing. That
turns a five-package pin into a full re-resolution: on pipelinq it moved
172 package versions, added 64 and removed 132, when five were intended.

One of those unintended moves broke boot. dexie went 4.4.4 -> 4.4.5, and
@conduction/nextcloud-vue's published dist BUNDLES its own dexie copy, so
the app loaded two and Dexie throws at module load:

  pageerror: Two different versions of Dexie loaded in the same app:
             4.4.5 and 4.4.4

The E2E boot gate caught it -- "the bundle loaded but rendered nothing" --
while build, lint, stylelint and unit tests were all green. A passing
build says nothing about whether the app mounts.

Starting from development's lock and letting npm move only what the
manifest forces cuts the change to 85/17/46 and leaves dexie alone.

Control: development's own E2E run is 309 passed / 1 failed with no
dexie pageerror and no boot-gate failure, so the breakage was mine.

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
Untracked:
  build.log
  changed.tsv
  inst.log
  lint.log
  stylelint.log
  t2.log
  test_unit.log
#764)

The App Store keys everything on the app id, so renaming <id> to stackiq made
it a brand new store entry starting from nothing. The release workflow
derives its version baseline from the git tags and info.xml of THIS repo,
neither of which knows anything about what shipped as softwarecatalog -- so the
version line restarts below it. filinq was about to publish 0.0.40 while
docudesk sits at 0.1.0-beta.3 on the store.

The store has no version ordering rule (_check_permission validates
existence and ownership only), so that uploads with a 200 and is then
never offered to anyone already on the higher version.

previous-app-id folds the old entry's published versions into the
baseline, so the renamed app picks the line up instead of restarting it.
* feat(demo): generated demo data for every schema (ADR-111 rules 1-2)

This app declares schemas and shipped no demo data, so it opened on an empty
list: the person evaluating it had to author objects by hand against a schema
they did not know yet. Fleet-wide, 562 of 598 schemas were in that state.

🔴 GENERATED, NOT WRITTEN. Every value is derived from the schema that will
validate it — `enum` picks from the enum, `pattern` is satisfied, `format`
drives the shape, `minimum`/`maxLength` are honoured, `required` is always
populated. Hand-written demo data is wrong in a way nobody sees until the demo
(a status outside its own enum, a required field omitted) and it fails at
import, in front of whoever asked for the demo.

Produced and validated by the single file gate-99 also runs:
`vendor/conduction/hydra-gates/scripts/lib/generate_mock_register.py`.
`--keep` preserves curated objects and tops up only what is short.

🔴 IT DOES NOT INSTALL ITSELF (ADR-111 rule 3). `x-openregister.type: mock` is
imported ON DEMAND — sample data appearing on a production instance because
somebody upgraded is a data-integrity incident, not a convenience:

    occ openregister:descriptors:list --app=<app> --import=<register>

The setup-wizard step offering this on first run (ADR-111 rule 4, gate-100)
follows once OpenRegister's shared installer lands — deliberately not
twenty-one copies of the same logic.

Verified: `--check` re-validates every object against its own schema with
jsonschema and reports zero findings.

* fix(demo-data): attribute the descriptor to the app id, not the directory

`x-openregister.app` is what the descriptor inventory resolves a register to
an app by, and the generator was writing the CHECKOUT DIRECTORY name into it —
naming an app that does not exist. A cross-app id is a runtime lookup: it
finds nobody rather than erroring. The file is renamed to match and the
directory-named one removed, so exactly one mock descriptor remains.

---------

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
#777)

Co-authored-by: Conduction Release Bot <release-bot@conduction.nl>
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