step 4/4: the docs say permissions too, and capability means one thing again
44 files of prose — CLAUDE.md, AGENTS.md, TODO.md, 20 docs, both plugin design
documents, and the comment surface the earlier steps could not reach.
Applied against an explicit keep-list, not swept, because the word turned out to
have SIX meanings in this repository rather than the three the offscale doc
recorded:
permissions renamed (steps 1–2)
$OFFICER_ROOT/capabilities/ KEPT — the item store, and now the only thing
the word means that is ours
sidecar routing keys renamed to `handles` (step 3)
Lightning wallet KEPT — a domain term, and on the wire to the mobile apps
terminfo queries KEPT — XTGETTCAP, in the pty sidecar
InvoiceShelf KEPT — per-resource { write, bulkDelete } flags
The sweep still falsified two things, both caught by checking rather than by
review, and both in prose that discusses more than one meaning at once:
CLAUDE.md began claiming the item store lives at `$OFFICER_ROOT/permissions`.
It does not; that directory is on disk and full of skills and tools.
And the offscale doc's own note about the collision became
"Named `permissions`, NOT `permissions`" — a sentence that had eaten the thing
it existed to warn about.
Both restored, and the note rewritten to say what is now true: capability means
one thing of ours, and three that belong to somebody else's vocabulary.
Verified live after restart: self and admin permission endpoints 200, gated
route 200, agent-status 200, 9 grants intact with 6 permissions offered.
tsgo clean, 797 tests, 787 pass, same 7.
The rename is done. Four steps, no data lost, no client break that survived
the step it was introduced in.
This commit is contained in:
+31
-31
@@ -1,7 +1,7 @@
|
||||
# Working on Officer
|
||||
|
||||
The guide for anyone — human or agent — changing this deployment. It assumes you are working from the
|
||||
root of the install (the directory holding `platform/`, `capabilities/` and `data/`), which is where
|
||||
root of the install (the directory holding `platform/`, `permissions/` and `data/`), which is where
|
||||
agent sessions start.
|
||||
|
||||
Three directories sit there, and knowing which one a change belongs in is most of the job:
|
||||
@@ -9,7 +9,7 @@ Three directories sit there, and knowing which one a change belongs in is most o
|
||||
```
|
||||
$OFFICER_ROOT/
|
||||
├── platform/ the application — a git repo
|
||||
├── capabilities/ what the agent can do — a separate git repo
|
||||
├── permissions/ what the agent can do — a separate git repo
|
||||
├── data/ runtime state — NOT version controlled
|
||||
├── dockers/ containers the app store provisioned
|
||||
└── secrets/ the key store — 0600, and NOT in your data backup
|
||||
@@ -22,9 +22,9 @@ why `assertInstallLayout` refuses to boot from the wrong directory.
|
||||
Officer is a self-hosted platform: an AI agent, a terminal, a file browser, a code editor, email, a
|
||||
bitcoin wallet, a remote desktop and dashboards, behind one web app. **It is built around one owner**
|
||||
— user id 1, role `Super Admin`, who bypasses every permission check — and since 2026-08-07 also
|
||||
admits **additional accounts holding a strict subset of it**, governed by per-role capability grants.
|
||||
admits **additional accounts holding a strict subset of it**, governed by per-role permission grants.
|
||||
|
||||
So "which user" has three answers depending on the surface. For the **app** capabilities (gitea,
|
||||
So "which user" has three answers depending on the surface. For the **app** permissions (gitea,
|
||||
music, photos, email, calendar…) it is a real question with a real answer. For **confined** ones —
|
||||
terminal, chat, files — it is also real, because the account has its own Linux user and the kernel
|
||||
enforces the boundary; a grant there means nothing without that user, and `authorize.ts` drops it.
|
||||
@@ -35,15 +35,15 @@ That is five kinds, not four: `core`, `app`, `confined`, `execution`, `admin`. T
|
||||
files moved from `execution` to `confined` on 2026-08-11 with per-user Linux accounts.
|
||||
|
||||
This paragraph said "there is no tenancy, no roles, no other users" until 2026-08-07. Four roles exist
|
||||
and five non-owner accounts are live; treat the capability registry as the source of truth over any
|
||||
and five non-owner accounts are live; treat the permission registry as the source of truth over any
|
||||
prose, here or elsewhere.
|
||||
|
||||
## What is switched off (2026-08-13)
|
||||
|
||||
A core install runs **six** pm2 processes: `officer`, `officer-anthropic-proxy`,
|
||||
`officer-claude-code`, `officer-opencode`, `officer-pty`, `officer-headscale`. Everything else is a
|
||||
plugin, and every plugin router is commented out in `hono.ts` with its capability's `api` claim
|
||||
commented beside it — they must move together or `assertCapabilityTotality` refuses to boot.
|
||||
plugin, and every plugin router is commented out in `hono.ts` with its permission's `api` claim
|
||||
commented beside it — they must move together or `assertPermissionTotality` refuses to boot.
|
||||
|
||||
The implementations are all still on disk. Nothing was deleted; the mounts were switched off pending
|
||||
extraction into the plugin system.
|
||||
@@ -54,7 +54,7 @@ Also gone: the four ecosystem files (generated now, at setup, and gitignored), o
|
||||
`.env` holds three values — `PORT`, `PUBLIC_URL`, `POSTGRES_URL`. Every key lives in
|
||||
`$OFFICER_ROOT/secrets/officer-keys.db`, one per purpose. See `docs/secret-store.md`.
|
||||
|
||||
`platform/` and `capabilities/` each have their own `CLAUDE.md` with detail. This file is the layer
|
||||
`platform/` and `permissions/` each have their own `CLAUDE.md` with detail. This file is the layer
|
||||
above them: where things live, how to change them safely, and the things that are true of the running
|
||||
system but written down nowhere else.
|
||||
|
||||
@@ -62,19 +62,19 @@ system but written down nowhere else.
|
||||
|
||||
## Which directory does this change belong in?
|
||||
|
||||
**`capabilities/` — almost always start here.** Tasks, tools, skills, processes. It is *data*: plain
|
||||
**`permissions/` — almost always start here.** Tasks, tools, skills, processes. It is _data_: plain
|
||||
directories of Markdown and scripts, read fresh on every request. Adding a task, changing what a task
|
||||
does, renaming a category — none of that needs a code change or a restart.
|
||||
|
||||
**`platform/` — only when the mechanism itself is missing.** If a task needs a form control that
|
||||
doesn't exist, or an endpoint that isn't there, that's platform work. Adding a *capability* is not.
|
||||
doesn't exist, or an endpoint that isn't there, that's platform work. Adding a _permission_ is not.
|
||||
|
||||
**`data/` — never edit by hand.** `DATA_PATH`. Holds the owner's managed home, per-account email
|
||||
SQLite stores, job logs, the queue, sidecar state. It is not backed up by git; deleting things here
|
||||
destroys the only copy.
|
||||
|
||||
A useful test: **would this differ between two Officer installs?** Domain, paths, credentials → `.env`.
|
||||
Which tasks exist and what they're called → `capabilities/`. Everything else → `platform/`.
|
||||
Which tasks exist and what they're called → `permissions/`. Everything else → `platform/`.
|
||||
|
||||
## Git
|
||||
|
||||
@@ -87,7 +87,7 @@ support it needs, and a half-pushed pair leaves the deployment inconsistent.
|
||||
Keep history linear: `git pull --rebase`, not `git merge`. The remote moves — the owner develops on
|
||||
this box too — so expect to rebase before pushing. Say so before force-pushing anything.
|
||||
|
||||
Commit messages: simple lowercase, no prefixes, explaining *why*.
|
||||
Commit messages: simple lowercase, no prefixes, explaining _why_.
|
||||
|
||||
---
|
||||
|
||||
@@ -137,13 +137,13 @@ This trips people up repeatedly. It is also why script tasks are handed `OFFICER
|
||||
|
||||
### Services this box depends on
|
||||
|
||||
| port | what | used by |
|
||||
|------|------|---------|
|
||||
| 9010 | Officer itself | — |
|
||||
| 9002 | Kokoro TTS | text-to-speech |
|
||||
| 8178 | whisper.cpp | transcription |
|
||||
| 1234 | vision model (Qwen2.5-VL) | OCR |
|
||||
| 5432 | Postgres | the database |
|
||||
| port | what | used by |
|
||||
| ---- | ------------------------- | -------------- |
|
||||
| 9010 | Officer itself | — |
|
||||
| 9002 | Kokoro TTS | text-to-speech |
|
||||
| 8178 | whisper.cpp | transcription |
|
||||
| 1234 | vision model (Qwen2.5-VL) | OCR |
|
||||
| 5432 | Postgres | the database |
|
||||
|
||||
URLs live in Settings → System, stored in the `server_config` table — **not** in `.env`. If
|
||||
transcription or OCR fails, check the service is up before reading any code.
|
||||
@@ -155,22 +155,22 @@ transcription or OCR fails, check the service is up before reading any code.
|
||||
This is what most requests will be about. Tasks appear in the file browser's right-click menu under
|
||||
**Run Task**, grouped into submenus by category.
|
||||
|
||||
A task is a directory under `capabilities/tasks/<slug>/` with a `TASK.md` — frontmatter plus a body —
|
||||
A task is a directory under `permissions/tasks/<slug>/` with a `TASK.md` — frontmatter plus a body —
|
||||
and, for script mode, a sibling `run.sh` / `run.py` / `index.ts`. **The directory name is the task's
|
||||
identity**; renaming it breaks every reference to it.
|
||||
|
||||
`capabilities/CLAUDE.md` documents the format. It is accurate but **incomplete** — the following are
|
||||
`permissions/CLAUDE.md` documents the format. It is accurate but **incomplete** — the following are
|
||||
used heavily by real tasks and appear nowhere in it:
|
||||
|
||||
| convention | what it does |
|
||||
|---|---|
|
||||
| `category: Video` | which submenu the task appears in. Order comes from `capabilities/categories.yaml`; an unlisted category still works, sorting after the listed ones. A category with no tasks never renders. |
|
||||
| `inline: true` | runs ephemerally in the modal instead of becoming a job |
|
||||
| `inline: ask` | offers both — **Run here** and **Run as job** |
|
||||
| `INPUT_INCLUDE` | newline-separated paths, injected by the modal on a multi-selection. **The single most used input in the library** — a task that ignores it silently processes the whole folder instead of the selection. |
|
||||
| `INPUT_GROUP_CONFIG` | per-track-layout config for folder-wide video runs |
|
||||
| input types `audio_tracks`, `subtitle_tracks`, `subtitle_edit` | render real track pickers, driven by probing the file |
|
||||
| `config.perGroupTracks`, `config.folderKeepAll`, `config.perGroupAllFiles` | opt into the folder track-picker behaviour — on **script** tasks, though the docs describe `config` as pipeline-only |
|
||||
| convention | what it does |
|
||||
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `category: Video` | which submenu the task appears in. Order comes from `permissions/categories.yaml`; an unlisted category still works, sorting after the listed ones. A category with no tasks never renders. |
|
||||
| `inline: true` | runs ephemerally in the modal instead of becoming a job |
|
||||
| `inline: ask` | offers both — **Run here** and **Run as job** |
|
||||
| `INPUT_INCLUDE` | newline-separated paths, injected by the modal on a multi-selection. **The single most used input in the library** — a task that ignores it silently processes the whole folder instead of the selection. |
|
||||
| `INPUT_GROUP_CONFIG` | per-track-layout config for folder-wide video runs |
|
||||
| input types `audio_tracks`, `subtitle_tracks`, `subtitle_edit` | render real track pickers, driven by probing the file |
|
||||
| `config.perGroupTracks`, `config.folderKeepAll`, `config.perGroupAllFiles` | opt into the folder track-picker behaviour — on **script** tasks, though the docs describe `config` as pipeline-only |
|
||||
|
||||
Inputs reach a script twice: positionally in `args` order, and as `INPUT_<NAME>` environment
|
||||
variables.
|
||||
@@ -190,7 +190,7 @@ Two ways a task does work:
|
||||
House style for file-processing tasks, worth keeping consistent:
|
||||
|
||||
- Never delete or modify the source; write output beside it.
|
||||
- Handle a single file *and* a directory, recursively.
|
||||
- Handle a single file _and_ a directory, recursively.
|
||||
- Honour `INPUT_INCLUDE`.
|
||||
- No caching. Re-running redoes the work and overwrites — and say so in the body, because it also
|
||||
overwrites edits.
|
||||
|
||||
Reference in New Issue
Block a user