authorize.ts had none, and its own header is what says why that mattered: every deny path in the platform ends there, and the failure mode of a permissive default is not a bug report, it is someone else's session. It had none for a structural reason rather than an oversight. Every path needed two database calls, and the only file here that mocks officerdb leaks its stub into whatever runs after it — so the obvious approach would have traded one untested file for a flaky suite. So the decisions are split from the fetching. resolveEffectivePermissions, decideApiRequest and decideWsProvider are pure functions of their arguments; the async wrappers keep the role cache, the fail-closed catches and the shape callers use. No behaviour change, and the live gate still answers 401 on file-browser, users and tasks. REAL registry keys throughout, deliberately: a suite built on invented permissions keeps passing after a rename, which is the failure an authorization table can least afford. If a key here disappears this file goes red. Pinned beyond the happy paths — the owner carries no grants to consult; core is write unconditionally; a confined grant is dropped without a Linux user and all three drop together; execution and admin grants in the table are ignored even though no CHECK stops the row existing; an unknown key is ignored without disturbing what is legitimately held; self-service is exact in method AND path; a socket needs no level; an unauthenticated caller is refused every door. Two gaps are stated in the file rather than left to be found: ownerOnly has no declaring permission yet, so testing it would test the fixtures; and the grant cache's contract is a claim about call sites, not logic. Found while writing them: readOnlyWrites paths are relative to the permission's api prefix, and getting that wrong fails as "no permission covers this path" rather than as a level error — a misleading message for a misleading path, now commented at the test that hit it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.4 KiB
TODO — the index
Deferred work. The items live in todo/, one file each, grouped by theme; this file is the map
over them and is GENERATED — run bun run todo:index after any change rather than editing it here.
Format and the two-way-sync contract: todo/README.md.
Context, corrected 2026-08-07. This list used to open by saying Officer was "collapsing from multi-tenant / open-source-ready to a single-user platform", and told you to treat multi-tenant indirection as accidental complexity. That direction was reversed. The permission model shipped on 2026-08-07 to serve a real goal — deploy to the company server, onboard people, give each one their own Gitea account through the platform. Per-user scoping is now a requirement, and the items that proposed deleting it have been removed rather than left to mislead the next reader.
What did NOT reverse: execution permissions (terminal, chat, tasks, files, desktop, browser) run as
the owner's OS user and can never be granted. Indirection there really is accidental complexity.
Urgent
- due 2026-08-22 — Settings to choose which server each feature uses — fleet/settings-to-choose-which-server-each-feature
Themes
84 open, 25 closed, across 29 themes.
Records — no items, kept for the reasoning
- Affordances (2026-08-19)
- Inputs outside forms (2026-08-19) — swept, fixed, closed
- Release-binary dependencies (2026-08-19)
Newest ten
2026-08-22A plugin's permission should be on for every role by default, off only on request — multi-user/a-plugin-s-permission-should-be-on-for-every2026-08-22A user's settings should export and import as a plain file — settings/a-user-s-settings-should-export-and-import-as2026-08-22The same for system settings, owned by the Super Admin — settings/the-same-for-system-settings-owned-by-the2026-08-21No way to see which affordances are reaching your agents, or to turn one off — affordances/no-way-to-see-which-affordances-are-reaching2026-08-21Generalise it: deliver a turn to a named session, from anywhere in the backend — agent-delivery/generalise-it-deliver-a-turn-to-a-named2026-08-21The cost of resuming grows with the transcript — agent-delivery/the-cost-of-resuming-grows-with-the-transcript2026-08-21'An agent spawned by a background job is invisible in the interface' — background-jobs/a-spawned-agent-is-invisible-in-the-interface2026-08-21Show how long a turn took, beside its cost and tokens — chat/show-how-long-a-turn-took-beside-its-cost2026-08-21Map every custom component, centralise it, and let a theme override it — components/map-every-custom-component-and-centralise-it2026-08-21Watch a directory, and attach actions to what lands in it — file-watchers/watch-a-directory-and-attach-actions-to-what