| M1-49/1 |
2026-10-14 |
Base int/m1-wave14 at fd504d9, branch feat/M1-49-authz-explain in its own worktree; the branch is not merged locally and not pushed, the integration lane ports it. Text this ticket adds is English. |
Instructions of the launcher, which override the brief's merge step (as M1-44/1). |
| M1-49/2 |
2026-10-14 |
The preview text takes the primary subtag of the caller's users.locale: de or en, anything else de; text_locale names it. Dates are the instant expires_at in the caller's users.time_zone (invalid or empty: UTC), de as 02.01.2006, en as 2006-01-02. The templates live in authz/domain/previewtext (catalog_de.go, catalog_en.go); the golden files hold the example of 07 section 9.3. |
Brief "Plain-text rendering"; 01 section 9 (language follows users.locale, product default German). |
| M1-49/3 |
2026-10-14 |
A delegation limit of 07 section 2.6 (delegation_exceeds_own_level, delegation_exceeds_own_capabilities, subject_kind_not_allowed, owner_only) gives 200 with allowed = false and denial_reason, decided with CheckCreate on the caller's rights read like authorize. Such a preview runs no dry run: effects, invariant_violations and hints are empty, the text holds the summary, abilities, capabilities and the denial line; the target must still exist (404 otherwise). |
07 section 9.3: the preview shows why a grant would fail; a refused grant has no effects, and an agent grant with trigger cannot even be stored (the check of grants). |
| M1-49/4 |
2026-10-14 |
The preview runs the apply step of createGrant (createGrant after authorizeGrant, both shared with CreateGrant) in a unit of work with the same rights change (grantRightsChange), calls RightsHook.Evaluate (the invariants and the views after the change, the code of BeforeCommit without appending or publishing) and the hints inside it, and returns the package sentinel errPreviewRollback, so the unit rolls back before the sync and audit hooks. kernel/txn gets no dry-run mode. A preview inside an ambient unit of work is an internal error, because its rollback would fail the outer unit. |
R-RE-10; brief "Dry run in the real transaction". previewGrant is exempt from the Idempotency-Key stage, so no ambient unit exists on the HTTP path. |
| M1-49/5 |
2026-10-14 |
"View as" in M1 is a snapshot: actor.Actor.ViewAs is *actor.ViewAs{UserID, StartedAt}, set only by authz inside viewAsUser; kernel/authorize.Principal gains ViewAsStart. From StartedAt + authz.view_as_max_duration on, authorize and Scope answer step_up_required with reason step_up_required (no new reason code); a view without a start counts as ended. The snapshot lists the spaces and channels of Scope for message.read and space.read with that context, without dm channels and archived structure, each with the intersected rights of authorize; a space seen only through a channel is read without capabilities. Audit entry access.view_as on the person with target_user_id and expires_at. |
07 section 9.2 and 4.2 steps 4 and 7; brief "access.view_as". |
| M1-49/6 |
2026-10-14 |
The capabilities line of the preview always names upload files and confirm approval cards (yes or no) and appends export and manage only when the grant has them; terminal never appears (host grants answer 404 before M2-39). |
Brief catalogue; the two always-listed capabilities are the ones of channel grants a person meets every day. |
| M1-49/7 |
2026-10-14 |
The three new methods of authz.Admin take the caller like every other method of the port: ExplainAccess(ctx, caller, target), PreviewGrant(ctx, caller, cmd) with the existing CreateGrantCmd, ViewAs(ctx, caller, userID). AdminDeps gains Engine (*Authorizer) and Hook (*RightsHook), taken from Authorizer and Rights when they are these types, so other modules' test worlds stay unchanged. |
The port passes the caller explicitly since M1-29; the use cases need the snapshot code and the rights check of authz itself. |
| M1-49/8 |
2026-10-14 |
Source ports: PrincipalSource gains Profiles (public view, status, locale and time_zone of people) and OwnerIDs (owners rows not revoked, any status); StructureSource gains SpaceDetails and ChannelDetails (names, default operator, the fields of Space and Channel); AgentPolicy gains BillingModesSupported, which revises M1-51/11 because organization_ready now reads it. The agent status comes from PrincipalSource, the effective tool class is domain.EffectiveToolClass. Identity and agents implement the additions with the queries they had; chat has no adapter in the base, so the fake and the empty structure of internal/wire implement StructureSource. |
Brief scope "source ports": capabilities, names of the base kept. |
| M1-49/9 |
2026-10-14 |
No new SQL in authz/adapters/postgres: the candidates of an explanation come from ActiveGrantsOnTargets (grants not revoked on the instance and the target's path, valid or not) and ActiveMembershipsOfGroups, the trigger set of any agent from the I1 world, now the shared triggerWorld that VerifyInvariants uses too. |
The brief asks to generalise the I1 triggerer query, not to copy it; the existing queries already return every row needed. |
| M1-49/10 |
2026-10-14 |
access.explain without owner or manage is 403 forbidden with reason owner_only, the reason of the first alternative of the catalogue requirement owner or manage, not capability_missing as the brief's design note says. |
The same requirement row serves grant.revoke, whose owner_only tests M1-48 holds; changing the order would change their reason. |
| M1-49/11 |
2026-10-14 |
Explanation of a space: grants on the instance and the space count, grants on its channels do not, so a person who sees the space only through a channel is not listed. The default operator of the space has level write and an operator source (R-AZ-13); guard_blocked names the default operator for a space and the effective operator for a channel. Agents whose horizon counts for via_agent are the agent subjects of grants on the path and the operators of the target's channels. Grants not in effect are no source; an entry with level none stays only with via_agent. |
Brief "Candidates" and "Sources". |
| M1-49/12 |
2026-10-14 |
TestExplainSources checks G-5 (Lotse, write, space Support) as inherited on Support/Allgemein and as direct on the space; the brief's line says "direct G-5" for the channel. |
G-5 is a grant on the space (07 section 11.1), so on a channel of the space it is inherited by the brief's own source rules. |
| M1-49/13 |
2026-10-14 |
effects of a preview: one per principal and target of DiffVisible (added, removed, changed); a channel carries the levels before and after and the capabilities it gains, a space appears as none to read or read to none. |
The difference of access.changed (04 section 8.2) knows spaces only as visible or not. |
| M1-49/14 |
2026-10-14 |
Hints: trigger_blocked_tool_class per active operator agent of the target with effective class host when a person or group gains trigger and SR2 blocks it (a person that is an active owner is not blocked); read_horizon per operator A, channel K in H(A) and person of the subject who may trigger A but cannot read K after the change (a group: one hint per pair with the count); group_reach with the active members whose membership is in effect; expiry_missing for a person without expiry. The trigger agents of the abilities line exclude agents with an I1 violation and those SR2 blocks. |
Brief "hints" and "Plain-text rendering". |
| M1-49/15 |
2026-10-14 |
Files outside the brief's scope that the ticket changed: kernel/authorize/authorizer.go (ViewAsStart), identity/app/principalsource.go, agents/adapters/authzsource/source.go, internal/wire (the key authz.view_as_max_duration, the empty structure), api/routes/real_test.go, cmd/bothy/config_test.go, the generated configuration schema and docs/operations/configuration.md (the key is active). |
Enabling an operation and registering a key need these places (as M1-48/12). |
| M1-49/16 |
2026-10-14 |
Review round: a read_horizon hint names only channels the caller reads (rights of authorize); a manager who cannot read the channel K gets no hint for it, the owner gets all. The template renderer substitutes placeholders in one pass, so a name holding {where} stays as written. Not changed (Minors): via_agent on a space target does not consider agents whose only grant is on a channel of the space (M1-49/11); the default operator's write on a space is not part of the differential test, whose worlds have no default operator; read_horizon is reported for every person of the subject, also one that had trigger before. |
Hints must not reveal what the caller may not see (05 rule 2, R-AZ-01); the rest are hint details without wrong access. |
| M1-49/17 |
2026-10-14 |
Red team round 1 (NEIN, one Major), fixed with TestPreviewEffectsOnlyOfTheProposedGrant: the dry run folds in every due expiry of the instance (M1-30), so effects listed principals and channels that have nothing to do with the grant, also to a manager who cannot read them. effects now hold only the principals the grant reaches (its subject, or the active members of its subject group), only targets at or below the grant's target and only targets the caller reads (a space through itself or one of its channels). Minors not changed: a view-as call for an id that is no person answers 404 without an audit row; a manager sees the name and reach of a group subject; I1 violations cover the whole instance like the 409 of createGrant; a host target is 400 from the shape check before authorize. |
05 rule 2; the preview shows the effect of this grant. |
| M1-49/18 |
2026-10-15 |
Port onto main: code and tests are taken byte for byte from the integration squash, with the part of INT-W15/1 that concerns this ticket (the Admin port holds the access use cases and embeds ClaimSync of M1-42; the fake store tests and the append-only files keep both sides). The later entries of api/operations.yaml use the key milestone of main. The code comment of domain/previewtext, the chapter of authz.view_as_max_duration in the schema test and config-schema.json name 07-permissions.md; the changelog entry, the module README (status line, code list, port line and the new section before "Expiry (M1-30)") and the key row of docs/operations/configuration.md are written into the English text at the same places, and M1-49/10 and M1-49/15 quote the English catalogue requirement and status. The German catalogue and the German golden file of the preview text stay German: they are product text for the locale de, and 07 section 9.3 on main keeps the example in German. For the language gate of main (D-W-94) the two files are path exceptions in tools/docs/language-allow.txt, the German expectations in previewtext_test.go and app/preview_test.go stand between language-check: off and language-check: on comment lines, and the package example of previewtext uses the locale en; the tests are otherwise unchanged. Generated files are regenerated, not merged. |
main is English; the integration fix belongs to the ticket whose use cases it joins (INT-W15/1). |