Skip to content

Skill-requirement enforcement never runs on the OpenRegister REST write path — violating characters persist with HTTP 201/200 #308

Description

@rubenvdlinde

Summary

LarpingApp's server-authoritative skill-requirement enforcement never runs for writes made through the OpenRegister REST objects API — the path the SPA and every REST client use. A character that violates a declared requiredSkills[] prerequisite is created and updated successfully.

CharacterRequirementListener, SkillRequirementService and SkillRequirementChecker (1001 lines) are correct and registered. The problem is upstream: OpenRegister does not dispatch the vetoable pre-write event on that write path, so the listener is never invoked.

This makes the spec's first scenario — "Direct API write bypassing the UI is rejected" — currently false, and 16 skill-requirement-enforcement scenarios untestable as written. They are the largest gate-19 cluster (50 findings total).

Reproduction (measured, not inferred)

Isolated rig: NC 34.0.0, openregister 0.2.17-unstable.36 (healthy 75-entry vendor/), larpingapp 0.1.39, register provisioned by this app's own tests/e2e/ci-seed.sh, firstrunwizard disabled.

Skill PROBE-adv declares requiredSkills: [PROBE-basic]. Character does not have PROBE-basic:

write required by spec observed
POST /apps/openregister/api/objects/{register}/{characterSchema} with skills:[adv] rejected before persistence HTTP 201, persisted
PUT …/{characterSchema}/{id} adding skills:[adv] rejected before persistence HTTP 200, persisted

The listener is not failing open either — it catches \Throwable and logs, and:

grep -c 'skill-requirement validation errored' data/nextcloud.log  ->  0

It is never called at all.

Root cause

ObjectsController saves through objectService->saveObject(...). In openregister/lib/Service/Object/SaveObjects.php:

grep -c 'ObjectCreatingEvent|ObjectUpdatingEvent'   -> 0          # the VETOABLE pre-write events
grep -n  'ObjectCreatedEvent|ObjectUpdatedEvent'    -> 72,73,1687 # positive control: the grep works
grep -n  '->insert(|->update('                      -> (none)

The vetoable events are dispatched only from MagicMapper::insertObjectEntity() (l.6764) and the two ObjectUpdatingEvent sites (l.6919, l.7158), reached via MagicMapper::insert() (l.8803). SaveObjects never calls those.

Those dispatch sites do honour isPropagationStopped() and getErrors(), so the veto contract itself is implemented — the REST path simply does not traverse it. Note the asymmetry: the post-write events fire on both paths (SaveObjects.php:1687 and MagicMapper.php:8832), the pre-write events on only one.

Why this is easy to miss

  • The app's unit tests exercise the service and the listener directly, so they are green.
  • gate-61 only inspects post-event listeners, so it cannot see this.
  • Any e2e test written in the acceptance direction ("this valid assignment succeeds") passes with the enforcement layer entirely absent.

That last point is not hypothetical. I wrote 13 full-stack tests for these scenarios. 10 failed. The 3 that passed all asserted that a write is accepted — and would pass with enforcement removed, exactly the shallowness #301 describes. They were deliberately not shipped: anchoring all 13 would have moved gate-19 from 50 to 37 while proving nothing.

Suggested resolution

  1. Raise on OpenRegister: SaveObjects must dispatch ObjectCreatingEvent/ObjectUpdatingEvent and honour stopPropagation() + getErrors(), as MagicMapper already does — otherwise the vetoable-event contract is not available to any consuming app on the REST path.
  2. Until then, the 16 skill-requirement-enforcement scenarios should stay as open gate-19 findings. They must not be closed with @e2e exclude: a browser can observe this perfectly well, and the reason would be "the feature does not work", which is a defect report, not an exemption.
  3. When it is fixed, the rejection-direction tests must land first — they are the only ones that can fail for the right reason.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions