Repository navigation
fix(access): only administration managers change the segment; portal absence reports get their owner stamped on the server - #1129
Merged
rubenvdlinde merged 2 commits intoSep 28, 2026
Conversation
…absence reports get their owner stamped on the server LearniqSettings gets an authorization block (staff read, administration-managers write), so an instructor can no longer change the organisation's segment through the objects API. ExcuseRequest.required keeps only what every caller sends; ExcuseRequestOwnerStamp fills the pupil, submitter, level and tenant of a portal report from the profile (a guardian only for a child that lists them), derives learnerRef for every other write, and refuses any write that still lacks the pupil, a submitter or the school, as SubmissionOwnerStamp does for Submission.
Contributor
Quality Report — ConductionNL/learniq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ❌ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 107/107 | |||
| npm | ✅ | ✅ 747/747 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test. |
||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-27 18:52 UTC
Download the full PDF report from the workflow artifacts.
This was referenced Sep 28, 2026
Contributor
Quality Report — ConductionNL/learniq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 107/107 | |||
| npm | ✅ | ✅ 747/747 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ❌ | ||||
| Newman | ⏭️ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test. |
||||
| Hydra gates | ❌ |
Quality workflow — 2026-09-28 07:26 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
added a commit
that referenced
this pull request
Sep 28, 2026
Resolves the IntegrityListenerRegistrar conflict with #1129 by moving the attempt time limit into its own AttemptLimitListenerRegistrar, called next to the attempt gate, since the merged registrar reached phpmd's coupling limit. Full PHPUnit after the merge: 2005 tests, no failures.
rubenvdlinde
added a commit
that referenced
this pull request
Sep 28, 2026
…ver and delete LearnerProfileLookup #1129 landed with a LearnerProfileLookup caller while this change was open; with it moved, no caller is left and the facade goes.
rubenvdlinde
added a commit
that referenced
this pull request
Sep 28, 2026
… one resolver find a learner's profile (#1147) * fix(learners): back-fill learnerRefs on existing submissions, and let one resolver find a learner's profile Submissions written before the server stamped learnerRefs (#1056) and learnerRef (#1068) never reached the portal. BackfillSubmissionLearnerRefs, a post-migration repair step after InitializeSettings, stamps what the write-path stamps derive today, saves only rows that differ, and never overwrites on a failed lookup. Portal\LearnerProfileLookup, added twice as a copy of LearnerRefResolver (#1068, #1096), folds into it: byRef() and resolveAcrossTenants() join resolve(), so no caller's read changes. LearnerProfileLookup stays as a deprecated facade because #1129 adds a caller; delete it once none is left. See openspec/changes/learnerrefs-backfill-and-lookup-dedupe/. * chore(release): move the version so an upgrade runs the new submission back-fill * chore(learners): record the verification of learnerrefs-backfill-and-lookup-dedupe * fix(learners): move the excuse request owner stamp to LearnerRefResolver and delete LearnerProfileLookup #1129 landed with a LearnerProfileLookup caller while this change was open; with it moved, no caller is left and the facade goes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope
Two write defects: any instructor could change the organisation's segment, and every portal absence report was refused before learniq saw it.
LearniqSettingsgets anauthorizationblock, andExcuseRequest's owner fields move fromrequiredinto a server-side stamp, the way #1068 did it for Submission.What changes
LearniqSettings had no
authorizationblock, so the register cascade (read-writefor instructors, hr, compliance-officers, team-leads) applied. The menu entry was admin-only; the objects API was not. Now:SegmentServicereads and writes the row with_rbac: falsefrom the admin-only setup contract, so the wizard is unaffected.ExcuseRequest listed
learnerId,submittedBy,submittedAuthLevelandtenant_idas required. Portaliq'screateExcuseRequest(pupilminTrust: low, guardianminTrust: substantial, portaliq#607) sends only the dates, reason, kind and attachment, plus its scope stamp. OpenRegister validatesrequiredinsaveObject()beforeObjectCreatingEvent, so every portal report got a 400. NowrequiredisdateFrom,dateTo,reason,reasonKind, andExcuseRequestOwnerStamp(on create and update, registered inIntegrityListenerRegistrar):learnerId, alearnerRef):learnerId,learnerRef,tenant_idfrom the pupil's LearnerProfile; unknown pupil or failed lookup refuses the write;submittedAuthLevel: basic;submittedByRefset): refused unless the child's profile lists that guardian inguardianRefs;submittedByis the guardian's user id when they have one;submittedAuthLevel: substantial;learnerRefderived fromlearnerId(a client value is replaced),submittedAuthLevel: basicwhen none is sent;submittedByorsubmittedByRef) or the school is refused withexcuse-owner-missing.The recorded level is the minimum the portal action enforced, because portaliq does not send the level it reached.
Versions: both schemas
0.2.0to0.3.0,info.version0.28.1to0.29.0. Docs: a short section on portal reports indocs/user-guide/user/05-attendance.md.Tests
ExcuseRequestOwnerStampTest(13 tests): pupil and guardian stamps, guardian without an account, guardian of another child, unknown pupil, failed lookup, signed-in create without a pupil, staff create (derived ref, forged ref replaced, default and kept level), missing owner fields, update keeping the stored ref, another schema ignored, registration on create and update.ExcuseRequestRegisterTest(3): the required list, and every required field is in both portal actions' field lists (read from the realPortalContributionProvider).SegmentFeatureFlagsRegisterTest::testOnlyAdministrationManagersChangeTheSegment.Verification (on this branch,
bb390304, cut froma84b6273)composer check:strict(lane semaphore,TMPDIR=$PWD/.tmp): exit 1 from inherited reds only. lint, phpcs, psalm (no errors), phpstan (no errors) green. phpmd: one finding,CaseListenerRegistrarcoupling 13, on development (fix(register): repair what the serial landing of round one still leaves wrong (replaces #1006) #1126 clears it); a newExcessiveMethodLengthonIntegrityListenerRegistrar::register()that this change first caused was fixed by the extraction described below. PHPUnit: 1936 tests, 12 failures, the identical set development carries (compared as a set, not a count; fix(access): enforce the access ratchets on the import records, first aid and the import guards (D23) #1124 and fix(register): repair what the serial landing of round one still leaves wrong (replaces #1006) #1126 close them). No new failure.npm run lint: exit 0 (19 inherited warnings).npm run format: exit 0.npm run test:l10n: no such script.npm run check:specs,check:schema-l10n,check:l10n-js: exit 0.openspec validate settings-and-excuse-authorization: valid.opsx-verify(headless, code level; no live instance for this lane): 3 of 3 tasks, 2 of 2 requirements implemented, every scenario pinned by a named test, design D1 to D4 followed, attendance user guide updated. 0 critical, 0 warnings. One suggestion: if portaliq ever passes the trust level it reached, record that instead of the action's minimum (D3).HYDRA_GATE_BASE_REF=origin/development --scope-to-diff: exit 4, 38 of 39 applicable gates ran; gate-3 stub-scan, gate-16 spec-coverage, gate-47 security-change-has-tests, gate-101 pass. The reds are the inherited set named on fix(access): enforce the access ratchets on the import records, first aid and the import guards (D23) #1124: gate-108 (orderLineIdon three mockEntitlementobjects, from feat(payments): retire orders for shillinq; raise fee contributions there and grant entitlements on its settled signal (D19) #1082), gate-113 (payments spec cites tests feat(payments): retire orders for shillinq; raise fee contributions there and grant entitlements on its settled signal (D19) #1082 deleted), gate-112 newman-reach, gate-53 and gate-68 (the gate's Node script fails to load as an ES module on this box).Landing
Cut from
development(a84b6273), independent of #1124 and #1126, so its full-suite failure set is development's known twelve. All three PRs editlib/Settings/learniq_register.json(info.versionandinfo.description); the schemas touched here (LearniqSettings,ExcuseRequest) are touched by neither of the other two.IntegrityListenerRegistrar: the Submission owner stamp moves, unchanged and in the same order, into a privateregisterOwnerStamps()beside the new one, which keepsregister()under phpmd's method length limit.OpenSpec change:
openspec/changes/settings-and-excuse-authorization/.CI (read once, 2026-09-28)
PHPUnit in CI: 1936 tests, 12 failures, development's known set; phpmd red on the inherited
CaseListenerRegistrarcoupling. The CI PHPUnit job also runs with coverage, wherefailOnRiskyandbeStrictAboutCoverageMetadataturn 10 tests into risky ones (CourseSharingControllerTest,StoreControllerTest,CourseShareExportServiceTest,CourseMetadataFilterTest,CourseStorePublisherTest: they execute classes their#[CoversClass]does not list). That makes the job exit 1 even with zero failures. None of those files is touched here; it is inherited from the course store changes and does not show locally with--no-coverage. CI Hydra Gates runs full-tree and reports the same inherited set as a local full run (gate-3, gate-25, gate-49, gate-55, gate-62, gate-108); none is on a file this PR touches.🤖 Generated with Claude Code