The frontend's Action and Resource union types in frontend/src/permissions.ts are hand-maintained mirrors of the Go constants in internal/auth/types.go. PR LeanerCloud/cloud-commitments-cli#1730 made the frontend side self-consistent — the runtime ALL_ACTIONS/ALL_RESOURCES arrays are now derived from those unions via a Record<Action, true> exhaustiveness check, so an array that drifts from the union fails tsc.
But the Go-to-TypeScript seam is still hand-maintained. Nothing detects the union drifting from Go.
That gap is what produced LeanerCloud/cloud-commitments-cli#1629 in the first place: the group-edit form's hardcoded <select> options had fallen 13 actions and 2 resources behind the backend, silently dropping permissions on save and — worse — widening view:history to view:*.
The union and the Go constants are in exact agreement today: 20 actions, 11 resources, matching both directions. So this is a guard against future drift, not a live defect.
Why the obvious fix does not work
cmd/gen-permissions already generates from internal/auth and is CI-enforced through the permissions-codegen pre-commit hook, so extending it looks like the natural answer. It was investigated and it does not help as-is:
Go has no enumerable list of actions or resources — only individual const declarations. Adding a generator step therefore requires a new hand-maintained Go slice, which moves the seam rather than removing it. A hand-maintained Go slice that drifts from the Go consts is the same class of bug one layer down.
What would actually close it
Make the Go side enumerable at the source, then generate:
- Add
auth.AllActions() / auth.AllResources() returning the complete sets, with an exhaustiveness guarantee that does not rely on someone remembering to append. Options worth weighing: a go:generate step that parses the const block, a linter check that every Action-typed const appears in the slice, or restructuring the consts so the slice is the source and the individual names derive from it.
- Then extend
cmd/gen-permissions to emit the TypeScript unions, so tsc failing on drift becomes CI failing on drift.
Do not add a hand-maintained Go slice and call it done — that is the failure mode this issue exists to prevent.
Priority
Low urgency: the two sides agree today and LeanerCloud/cloud-commitments-cli#1730 makes the frontend internally consistent. But the cost of the drift when it recurs is a silent permission-corruption bug, which is why it is worth doing rather than closing as won't-fix.
Minor related nit: a comment introduced by LeanerCloud/cloud-commitments-cli#1730 says there is "exactly one place to update", which is true within the frontend but reads as repo-wide. Worth scoping when this is picked up.
Found during the adversarial review of LeanerCloud/cloud-commitments-cli#1730 (F3).
Findings from the 2026-09-02 codebase audit
Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.
A11-011 (medium)
A concrete drift instance for this issue, from finding A11-011. Besides the permissions.ts union this issue tracks, frontend/src/users/permissionMatrix.ts:11 keeps its own hand-written ACTIONS list of seven verbs, while ACTION_EXHAUSTIVENESS_CHECK in permissions.ts enumerates twenty. The matrix therefore renders no row for approve-any, retry-any, cancel-any, update-any, execute-any, sell-any, revoke-any or any -own variant, so an admin auditing which groups can approve a purchase sees dashes everywhere and concludes the capability is not granted. The group-edit form was already migrated onto ALL_ACTIONS for exactly this reason (see the drift comment at groupModals.ts:166-188); the matrix was not. The only test touching the file is an accessibility assertion, so nothing catches the drift.
The frontend's
ActionandResourceunion types infrontend/src/permissions.tsare hand-maintained mirrors of the Go constants ininternal/auth/types.go. PR LeanerCloud/cloud-commitments-cli#1730 made the frontend side self-consistent — the runtimeALL_ACTIONS/ALL_RESOURCESarrays are now derived from those unions via aRecord<Action, true>exhaustiveness check, so an array that drifts from the union failstsc.But the Go-to-TypeScript seam is still hand-maintained. Nothing detects the union drifting from Go.
That gap is what produced LeanerCloud/cloud-commitments-cli#1629 in the first place: the group-edit form's hardcoded
<select>options had fallen 13 actions and 2 resources behind the backend, silently dropping permissions on save and — worse — wideningview:historytoview:*.Current state (verified during the LeanerCloud/cloud-commitments-cli#1730 review)
The union and the Go constants are in exact agreement today: 20 actions, 11 resources, matching both directions. So this is a guard against future drift, not a live defect.
Why the obvious fix does not work
cmd/gen-permissionsalready generates frominternal/authand is CI-enforced through thepermissions-codegenpre-commit hook, so extending it looks like the natural answer. It was investigated and it does not help as-is:Go has no enumerable list of actions or resources — only individual
constdeclarations. Adding a generator step therefore requires a new hand-maintained Go slice, which moves the seam rather than removing it. A hand-maintained Go slice that drifts from the Go consts is the same class of bug one layer down.What would actually close it
Make the Go side enumerable at the source, then generate:
auth.AllActions()/auth.AllResources()returning the complete sets, with an exhaustiveness guarantee that does not rely on someone remembering to append. Options worth weighing: ago:generatestep that parses the const block, a linter check that everyAction-typed const appears in the slice, or restructuring the consts so the slice is the source and the individual names derive from it.cmd/gen-permissionsto emit the TypeScript unions, sotscfailing on drift becomes CI failing on drift.Do not add a hand-maintained Go slice and call it done — that is the failure mode this issue exists to prevent.
Priority
Low urgency: the two sides agree today and LeanerCloud/cloud-commitments-cli#1730 makes the frontend internally consistent. But the cost of the drift when it recurs is a silent permission-corruption bug, which is why it is worth doing rather than closing as won't-fix.
Minor related nit: a comment introduced by LeanerCloud/cloud-commitments-cli#1730 says there is "exactly one place to update", which is true within the frontend but reads as repo-wide. Worth scoping when this is picked up.
Found during the adversarial review of LeanerCloud/cloud-commitments-cli#1730 (F3).
Findings from the 2026-09-02 codebase audit
Added by an automated audit of
3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd(tip oforigin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report:docs/audits/codebase-audit-2026-09-02.md.A11-011 (medium)
A concrete drift instance for this issue, from finding A11-011. Besides the
permissions.tsunion this issue tracks,frontend/src/users/permissionMatrix.ts:11keeps its own hand-writtenACTIONSlist of seven verbs, whileACTION_EXHAUSTIVENESS_CHECKin permissions.ts enumerates twenty. The matrix therefore renders no row forapprove-any,retry-any,cancel-any,update-any,execute-any,sell-any,revoke-anyor any-ownvariant, so an admin auditing which groups can approve a purchase sees dashes everywhere and concludes the capability is not granted. The group-edit form was already migrated ontoALL_ACTIONSfor exactly this reason (see the drift comment at groupModals.ts:166-188); the matrix was not. The only test touching the file is an accessibility assertion, so nothing catches the drift.