| M1-48/1 |
2026-10-13 |
Base int/m1-wave12 at da08f58, branch feat/M1-48-authz-api in its own worktree; the branch is not merged locally and not pushed, the integration lane ports it. Text this ticket adds is English, also in German chapters and READMEs. |
Instructions of the launcher, which override the brief's merge step and language (as M1-44/1). |
| M1-48/2 |
2026-10-13 |
Non-owners see grants only inside their manage scope; filters narrow it and never widen it. The scope (spaces and channels with effective manage, a channel also through its space or the instance) comes from the rights snapshot per request (Authorizer.AdminScope) and is applied in the query. |
Brief ruling 1; 07 section 2.6 item 6. |
| M1-48/3 |
2026-10-13 |
A grant or group the caller may not read is 404 for every operation on it, with the same status, body and headers as a random id (AZ-P4). For grants this changes the visibility rule of M1-28: a grant on a space or channel is visible to owners and to holders of manage there, no longer to everyone who reads the target. |
Brief ruling 2 and the table "Visibility and authorization per operation"; reading a target does not make its administration data readable. |
| M1-48/4 |
2026-10-13 |
Revoking an already revoked grant succeeds with the stored grant, without change, event or rights version, and is audited with no_change (06 section 2 "No effect"). Every revocation needs a reason that is not blank after trimming; 07 section 9.5 says so. |
Brief ruling 3; 03 R-DM-09 and 07 9.5. |
| M1-48/5 |
2026-10-13 |
Deleting a group revokes its live grants with the reason group_deleted (revoked by the caller) in one rights change and keeps the memberships as history; Admin.DeleteGroup takes no reason. |
Brief ruling 4; deleteGroup has no body. |
| M1-48/6 |
2026-10-13 |
member_count and the member list show memberships in effect at the request: not removed, not expired (at expires_at already over). In group.changed of a deletion the count is that of the memberships kept as history. |
Brief ruling 5; 07 section 2.5. |
| M1-48/7 |
2026-10-13 |
The delegation limits (CheckCreate, CheckRevoke) run in the requirement step of domain.Decide (decideDelegation), after the requirement and before "view as" and step-up; the guard step keeps agent.invoke and token.create. |
Brief scope 3 and 07 4.2 "Requirement before step-up": a caller beyond the limits must not be asked for a step-up. |
| M1-48/8 |
2026-10-13 |
getGrant and the instance filter of listGrants check an instance grant as grant.read on the caller as resource principal, whose requirement is owner; for getGrant a forbidden becomes 404. A host grant is 404 until M2-39. listGrants without a target filter authorizes through AdminScope only: a non-owner without manage gets an empty page, not 403. |
05 lists grant.read on space, channel, host and principal only; the brief makes instance grants owner only and lets a principal filter narrow instead of refuse. |
| M1-48/9 |
2026-10-13 |
createGroup with source = oidc_claim checks that provider_id names an existing provider through the foreign key groups_provider_id_fkey (400, /provider_id, invalid), not its kind. A local group with provider_id or external_ref is 400 with code forbidden; a taken claim is 409 conflict on /external_ref. |
authz must not read identity tables and has no port for providers; the claim sync of M1-42 only reads groups of OIDC providers, so a group bound to another kind stays empty and harmless. |
| M1-48/10 |
2026-10-13 |
updateGroup without name and description is 400 validation_failed on /name with code required; names are trimmed on create and update. |
The brief requires at least one of them; the schema GroupUpdate cannot say so. |
| M1-48/11 |
2026-10-13 |
Field codes of the new checks: in_past (an expiry that is not after the request; before not_after_creation), unknown_subject, unknown_user, duplicate (a capability listed twice), required and forbidden (a missing or forbidden id of a pair, in the body or as #query/<name> in listGrants). The audit details of grant.create name a group subject as subject_id, like every other subject. |
The brief's codes and audit details. |
| M1-48/12 |
2026-10-13 |
Files outside the brief's scope that the ticket had to change: internal/wire (construction, Handlers.Access, the configuration keys group.name_max_chars and group.description_max_chars with their tests), api/routes/real_test.go (the enabled table), cmd/bothy/config_test.go, tools/configdoc/planned.yaml (the rule group. is stale once the keys are registered) and the generated configuration reference. |
Enabling an operation needs its handler in the start (api/README "Enable an operation (handler ticket)"); a registered key must leave the planned list. |
| M1-48/13 |
2026-10-13 |
Red team round 1 (NEIN, two Majors), fixed with a test each: (1) createGroup and createGrant check the shape before authorize without the caller's id (domain.CheckGrantShape, CheckClaim, groupShape) and build the row after it, so an anonymous call is 401, not 500 (TestAnonymousWritesAre401); (2) behind the Idempotency-Key stage a raced revocation, deletion or removal records the call without effect inside the late rights change instead of returning errRaced, because a failed late rights change fails the whole unit (M1-31/2); that attempt raises permission_version without a difference, as M1-31/9 accepts for enableUser (TestConcurrentKeyedRevocations). Minors fixed: every operation with an id runs authorize before it answers a missing resource, the class of M1-31/16 (TestNoExistenceOracleBeforeVisibility); the reason of a revocation is checked like the reason of a grant (domain.CheckReason); deleting a group revokes only grants in effect, an expired one is reported as expired by the fold of the same change. Not changed: the sentence of 07 9.5 stays English (instruction of the launcher: everything English, M1-48/1); listGroups under "view as" and the receive action grant.read of grant.changed for resource grant have no caller before M1-49 and M1-65; until M1-52 and M1-53 wire a structure source, targeted grant reads of spaces and channels answer 404 in production, as every structure lookup does; AdminScope reads all spaces and channels per list request (bounded by the size of the instance, like Scope). |
Findings of the red team in the scope of this ticket. |
| M1-48/14 |
2026-10-15 |
Port onto main: code and tests are taken byte for byte from the integration squash, with the test world of INT-W14/7 without the identity keys of M1-45, which comes after this ticket on main. Code comments, the chapter keys of group.name_max_chars and group.description_max_chars and the rulings name the English chapter files and headings (03-data-model.md "Configuration values of this chapter", 06 "No effect", 07 4.2 "Requirement before step-up", api/README "Enable an operation (handler ticket)"); the sentence of 07 section 9.5 replaces the English sentence at the same place. The module README and the server changelog name the reasons step_up_not_possible and claim_managed_group after their registered codes (403 forbidden, 409 conflict), as the problem code check of main requires. The main-only case TestVisibleByResourceKind of M1-28 expects the visibility rule of M1-48/3: a grant on a space is hidden from a reader without manage and shown to a holder of manage. Generated files are regenerated, not merged. |
main is English and does not hold M1-45 yet; the case was added on main after the integration base and still asserted the rule M1-48/3 replaces. No other behaviour changes. |