Skip to content

docs(adr): write the ADR-023 adoption record 27 @spec tags already pointed at - #68

Merged
rubenvdlinde merged 1 commit into
developmentfrom
chore/adr-023-adoption-record
Aug 8, 2026
Merged

docs(adr): write the ADR-023 adoption record 27 @spec tags already pointed at#68
rubenvdlinde merged 1 commit into
developmentfrom
chore/adr-023-adoption-record

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

Writes openspec/architecture/adr-023-action-authorization.md, the file that
27 @spec tags in six files already pointed at and that had never been
written. No code changes.

file dangling tags
src/components/admin/ActionAuthMatrix.vue 8
lib/Service/ActionAuthService.php 6
lib/Controller/ActionMatrixController.php 4
lib/Repair/ApplyActionBaseline.php 4
lib/Repair/InitializeActions.php 4
src/services/api.js 1

An entire implemented capability — action RBAC service, admin matrix endpoint,
two repair steps, the admin UI and its API client — was annotated against
nothing.

Why a local file, when ADR-023 is a hydra ADR

The decision is company-wide and its canonical home stays
hydra/openspec/architecture/adr-023-action-authorization.md. This is an
adoption record: what LaunchPad concretely does to satisfy it, derived from
this repo's code. Where the two disagree, hydra wins and this file is the bug.

The anchors cannot point at hydra instead. gate-46 matches @spec openspec/…
and resolves it inside the repo, so a cross-repo path would not be a reference —
it would be invisible to the gate entirely.

Everything recorded was read out of the implementation: the four-step resolution
order in requireAction() and why the @all sentinel is tested before the
admin-only short-circuit; default-deny through getAllowedGroups() including the
corrupt-JSON path; why the @ prefix cannot collide with a real Nextcloud group
id; why the matrix editor is gated with #[AuthorizedAdminSetting] rather than
by the matrix itself; and why ApplyActionBaseline broadens only pristine
entries — an admin who narrowed an action must not have it re-broadened on
upgrade.

It also records an invariant that holds today and is worth keeping: the seed and
the enforced set are equal in both directions.

seeded not enforced: []
enforced not seeded: []
seed=78 used=78

Drift either way is silent. A seeded-but-unenforced action is a configuration
surface that governs nothing. An enforced-but-unseeded one falls through
default-deny to ["admin"] and quietly becomes admin-only on every install,
with nothing reporting an error.

Proof it can fail

gate-46 spec-anchor-existence, whole tree, hydra-gates at .github@main
(756fe89) — the two arms differ only by the presence of the file:

ARM A  without the ADR   27 findings
ARM B  with the ADR       0 findings

And the zero is real, not a gate gone blind. After the change, gate-46 still
fires on both of its failure modes:

@spec openspec/architecture/adr-999-invented.md
  -> "@spec target file not found"                                    (1 finding)

@spec openspec/architecture/adr-023-action-authorization.md#requirement-no-such-heading
  -> "@spec anchor not found in …"                                    (1 finding)

The second matters most: a fragment naming a heading this new file does not
contain still fails, so the file cannot be used to launder future anchors.

Full-tree gate delta

No CI job in this repo produces a full-tree verdict — push runs scope to ~1 file,
PR runs to the diff — so this was measured directly, both arms in the same
environment:

failing gates findings
origin/development @ 70420eb5 15 71
this branch 14 44

Fixed: gate-46 (27 findings — the largest single bucket on this repo, 38% of
all findings). Regressions: none.

Measurement note, because it nearly cost me a false claim: my first baseline
run reported 16 failing gates, with gate-22 manifest-validation and
gate-53 effective-manifest-crossref failing for wiring reasons ("Ajv is not
resolvable"). Ajv became resolvable between runs, so the 16-vs-14 comparison
would have credited this PR with a gate-22 fix it did not make. Re-measured
with both arms under ajv-present: 15 vs 14. Worth noting separately that once
ajv resolved, gate-53 stopped reporting a wiring failure and started
reporting a real finding — 1 cross-reference failure in the effective
manifest — which the wiring failure had been masking. That is pre-existing on
development and is not addressed here.

…inted at

Six files carried 27 `@spec openspec/architecture/adr-023-action-authorization.md`
tags — ActionAuthService, ActionMatrixController, both action repair steps, the
admin matrix component and its api.js client — against a file that had never
been written. Every one was a dangling reference (gate-46
spec-anchor-existence), so an entire implemented capability was annotated
against nothing.

The decision is company-wide and its canonical home is hydra. This is the
LaunchPad adoption record: what this repo concretely does to satisfy it,
derived from the code, so the anchors resolve to something that describes what
they annotate. Anchors cannot point at hydra directly — gate-46 matches
`@spec openspec/…` and resolves it inside the repo, so a cross-repo path would
not be a reference, it would be invisible.

Recorded, all read out of the implementation: the four-step resolution order in
requireAction() and why `@all` is tested BEFORE the admin-only short-circuit;
default-deny through getAllowedGroups() including the corrupt-JSON path; why the
`@` prefix on the sentinel cannot collide with a real group id; why the matrix
editor is gated with #[AuthorizedAdminSetting] instead of by the matrix itself;
and why ApplyActionBaseline broadens only pristine entries — an admin who
narrowed an action must not have it re-broadened on upgrade.

Also records an invariant that holds today and is worth keeping: the seed and
the enforced set are equal in both directions (seed=78, used=78, neither
difference non-empty). Drift either way is silent — a seeded-but-unenforced
action governs nothing, and an enforced-but-unseeded one falls through
default-deny and becomes admin-only on every install with nothing reporting it.

gate-46 spec-anchor-existence, whole tree, hydra-gates at .github@main 756fe89:

  without this file  27 findings
  with this file      0 findings

Not a blind pass — the gate still fires after the change:

  @SPEC …/adr-999-invented.md            -> target file not found        (1)
  @SPEC …/adr-023-…md#requirement-no-such-heading
                                         -> anchor not found in file     (1)

No code changes.
@rubenvdlinde
rubenvdlinde requested a review from Rem-Dam as a code owner August 8, 2026 10:31
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/launchpad @ 16285e0

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-manifest
composer ✅ 102/102
npm ✅ 548/548
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-08 10:38 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit b3ed9c5 into development Aug 8, 2026
27 checks passed
@rubenvdlinde
rubenvdlinde deleted the chore/adr-023-adoption-record branch August 14, 2026 09:32
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