Skip to content

fix(setup): forbidden must empty the unmet lists — completed alone is inert - #576

Merged
rubenvdlinde merged 1 commit into
feat/vue-3from
wip/setup-forbidden-unmet
Aug 2, 2026
Merged

fix(setup): forbidden must empty the unmet lists — completed alone is inert#576
rubenvdlinde merged 1 commit into
feat/vue-3from
wip/setup-forbidden-unmet

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Follow-up to #575, which did not work.

What went wrong

#575 short-circuited completed when the server answers 401/403. CnAppRoot never reads completed. It gates the blocking wizard on

requiredUnmet.value.length > 0                                        // CnAppRoot.vue:2126

and the non-gating auto-open on

requiredUnmet.value.length === 0 && optionalUnmet.value.length > 0    // CnAppRoot.vue:2136

So the fix changed nothing a user could see.

Caught by live verification, not by the tests: after publishing 2.1.0-vue3.14 and bumping openbuild to it, rbac-editor still landed on "Set up this app / Welcome to OpenBuild" with .openbuild-schema-list count 0. The deployed bundle did contain the new code — this was a wrong gate, not a stale build.

The fix

forbidden now empties both unmet lists. A caller who may not read setup state has no unmet setup work of their own; reporting steps as unmet to them described work they could neither see nor do.

Why 10 green tests didn't catch it

The test asserted the broken behaviour outright:

// requiredUnmet still reflects the (unknown) server state — only the
// gate that decides whether to SHOW the wizard is affected.
expect(s.requiredUnmet.value.map((x) => x.id)).toEqual(['region'])

That comment was my assumption about which gate mattered, written without checking CnAppRoot. It was wrong, and encoding it as an assertion is what made the suite agree with a fix that did nothing.

Replaced with the assertion matching the real gate, plus:

  • the lists come back when a later fetch succeeds — otherwise the wizard would stay hidden from someone who now genuinely needs it
  • a 500 still shows the wizard to an admin

556 tests pass across tests/composables and CnAppRoot.

🤖 Generated with Claude Code

… inert

Follow-up to #575, which did not work. Caught by live verification, not by the
tests, because the tests asserted the wrong thing.

#575 short-circuited `completed` when the server answers 401/403. CnAppRoot
never reads `completed`. It gates the blocking wizard on

    requiredUnmet.length > 0

and the non-gating auto-open on

    requiredUnmet.length === 0 && optionalUnmet.length > 0

so the fix changed nothing a user could see. Verified against a live instance
after publishing 2.1.0-vue3.14 and bumping openbuild to it: rbac-editor still
landed on "Set up this app / Welcome to OpenBuild" with .openbuild-schema-list
count 0. The bundle did contain the new code — this was a wrong gate, not a
stale build.

`forbidden` now empties BOTH lists. A caller who may not READ setup state has
no unmet setup work OF THEIR OWN; reporting steps as unmet to them described
work they could neither see nor do.

The previous test actively asserted the broken behaviour —

    // requiredUnmet still reflects the (unknown) server state
    expect(s.requiredUnmet.value.map((x) => x.id)).toEqual(['region'])

— which is why 10 green tests sat on top of a fix that did nothing. Replaced
with the assertion that matches the real gate, plus coverage that the lists
come BACK when a later fetch succeeds (or the wizard would stay hidden from
someone who now genuinely needs it) and that a 500 still shows it to an admin.
@rubenvdlinde
rubenvdlinde merged commit 4788bc1 into feat/vue-3 Aug 2, 2026
1 check passed
@rubenvdlinde
rubenvdlinde deleted the wip/setup-forbidden-unmet branch August 2, 2026 07:22
rubenvdlinde added a commit to ConductionNL/openbuild that referenced this pull request Aug 2, 2026
…ns can use the app (#87)

Picks up ConductionNL/nextcloud-vue#576: a 401/403 from /api/setup/status now
empties the unmet-steps lists, so a non-admin is no longer shown the
first-time-setup wizard.

Why this bump matters more than the version delta suggests: after #81 gave
non-admins OpenRegister-level read on the schemas, an editor still could not
use OpenBuild. They landed on "Welcome to OpenBuild / Set up this app" with
.openbuild-schema-list count 0 — the setup endpoints are admin-only, they
answer 403, and useSetupStatus read that as "setup unfinished".

Also corrects a drift found on the way: node_modules held 2.1.0-vue3.7 while
package.json and the lockfile both said 2.1.0-vue3.13. Nobody had run an
install since that bump, so local builds were linking a library three versions
behind the pin. This commit was built from a clean `npm ci`.

New: tests/e2e/non-admin-access.spec.ts, a regression suite for the OUTCOME
rather than any one layer. Two consecutive fixes for this looked green while
the app stayed broken for the user — openbuild#76's grant, then a nc-vue fix
that short-circuited `completed`, which CnAppRoot never reads. The only
assertion that would have caught both is "a non-admin sees the app", so that
is what these three tests assert:

  - an editor reaches the schema designer AND no setup wizard (both halves —
    asserting the wizard's absence alone passes on a blank page)
  - an editor sees the app they were granted
  - an outsider sees none — the control proving openbuild's row-level filter
    still runs on top of OR's coarse `authenticated` read grant

Verified live against the disposable instance: 3 passed.

Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
rubenvdlinde added a commit that referenced this pull request Aug 2, 2026
… inert (#577)

Port of #576 to the beta line, where #574 has the same defect: it
short-circuited `completed`, which CnAppRoot never reads. CnAppRoot gates the
blocking wizard on `requiredUnmet.length > 0` (CnAppRoot.vue:2008) and the
auto-open on `optionalUnmet.length > 0`, so the fix was inert here too.

Proven on the vue3 line: after publishing the equivalent of #574 there and
bumping openbuild to it, a non-admin STILL landed on "Set up this app" with
the new code present in the bundle. Fixing the gate is what made it work.

`forbidden` now empties BOTH lists. A caller who may not READ setup state has
no unmet setup work OF THEIR OWN.

The old test asserted the broken behaviour outright, which is why the suite
stayed green over a fix that did nothing. Replaced with the assertion matching
the real gate.

139 tests pass across useSetupStatus and CnAppRoot.
rubenvdlinde pushed a commit that referenced this pull request Aug 3, 2026
… `completed`

The setup wizard parked itself over the app on every fresh browser profile
even though `/api/setup/status` answered `200 {completed:true}` with every
step it reports marked done. That is the "the setup gate never clears"
report, and it is why ~98 pipelinq UI e2e specs died on selector timeouts:
Playwright always starts from a clean profile, so the localStorage dismissal
that hides this in day-to-day use is never present.

Root cause is a shape mismatch, not a status bug. An app's manifest may
declare optional steps its SetupController never emits a status key for —
pipelinq declares 7 steps and reports 4, with the optional `demo-data`
run-action reported nowhere. `useSetupStatus` maps a missing key to
`done: false`, so an UNREPORTED step is indistinguishable from an UNDONE one
and `optionalUnmet` is non-empty for ever. `optionalSetupGating` read only
those lists and never the server's own `completed` flag, so the auto-open
watcher fired on every mount, permanently.

Reproduced through a mounted CnAppRoot with pipelinq's real step list and the
verbatim payload its controller returns:

    optionalUnmet ['demo-data']  optionalSetupGating true
    setupWizardOpen true         overlay rendered true

`completed` is the server's authoritative answer, so it now short-circuits
the offer. A genuinely incomplete setup still auto-opens — covered by a
positive control alongside, without which the new test could pass merely by
never rendering the overlay.

Control: reverting only the `optionalSetupGating` body turns the new test red
and leaves its positive control green; restoring turns it green. 8/8 in this
file, 171/171 across the 14 CnAppRoot + setup suites.

This is the second mechanism behind the same symptom. The first — a 403
gating non-admins out entirely — was fixed in #574/#575/#576/#577; case C of
the accompanying spec pins it so it cannot regress.
rubenvdlinde added a commit that referenced this pull request Aug 3, 2026
…00 completed:true (#579)

* test(app-root): cover the setup GATE through CnAppRoot, not just the composable

Every existing first-time-setup test drove `useSetupStatus()` directly and
awaited `refresh()` by hand. Not one mounted CnAppRoot, so not one could
observe `phase` — and `phase` is the thing users hit. When the gate sticks
there is no `<main>` and no nav, so every UI e2e spec in the consuming app
dies on a selector timeout (~98 failures traced to it on pipelinq). That
entire class of failure was invisible to this suite.

Six cases through the real component: a positive control proving the gate is
observable at all, the completed happy path, async manifest arrival, a 403,
a second mount sharing the module-level cache, and a fresh manifest object
identity.

Case C is the one that mattered. `/api/setup/status` is admin-only
(`#[AuthorizedAdminSetting]`), so it answers 200 {completed:true} to an admin
and 403 to everyone else. Checking the endpoint in a browser as an admin
therefore shows it perfectly healthy while the non-admin e2e user is gated
out of the app entirely — two observations that look contradictory and are
not. That is why this read as "gates even though status says completed".

Control: replaying the file against `7b9639cb^` (pre-fix useSetupStatus)
fails C and passes A/B/D/E, so the suite genuinely discriminates the
behaviour that #574/#575/#576/#577 fixed rather than passing by construction.
Restored to HEAD, all six pass.

Tests only — no source change.

* fix(app-root): an UNREPORTED setup step is not an UNDONE one — honour `completed`

The setup wizard parked itself over the app on every fresh browser profile
even though `/api/setup/status` answered `200 {completed:true}` with every
step it reports marked done. That is the "the setup gate never clears"
report, and it is why ~98 pipelinq UI e2e specs died on selector timeouts:
Playwright always starts from a clean profile, so the localStorage dismissal
that hides this in day-to-day use is never present.

Root cause is a shape mismatch, not a status bug. An app's manifest may
declare optional steps its SetupController never emits a status key for —
pipelinq declares 7 steps and reports 4, with the optional `demo-data`
run-action reported nowhere. `useSetupStatus` maps a missing key to
`done: false`, so an UNREPORTED step is indistinguishable from an UNDONE one
and `optionalUnmet` is non-empty for ever. `optionalSetupGating` read only
those lists and never the server's own `completed` flag, so the auto-open
watcher fired on every mount, permanently.

Reproduced through a mounted CnAppRoot with pipelinq's real step list and the
verbatim payload its controller returns:

    optionalUnmet ['demo-data']  optionalSetupGating true
    setupWizardOpen true         overlay rendered true

`completed` is the server's authoritative answer, so it now short-circuits
the offer. A genuinely incomplete setup still auto-opens — covered by a
positive control alongside, without which the new test could pass merely by
never rendering the overlay.

Control: reverting only the `optionalSetupGating` body turns the new test red
and leaves its positive control green; restoring turns it green. 8/8 in this
file, 171/171 across the 14 CnAppRoot + setup suites.

This is the second mechanism behind the same symptom. The first — a 403
gating non-admins out entirely — was fixed in #574/#575/#576/#577; case C of
the accompanying spec pins it so it cannot regress.

---------

Co-authored-by: Ruben van der Linde <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