diff --git a/docs/plans/2026-11-10_system-type-toolsets-design.md b/docs/plans/2026-11-10_system-type-toolsets-design.md index bdcbab5ea..67b1fcd1d 100644 --- a/docs/plans/2026-11-10_system-type-toolsets-design.md +++ b/docs/plans/2026-11-10_system-type-toolsets-design.md @@ -1,5 +1,7 @@ # System-type toolsets at launch +Superseded 2026-11-12 by `docs/plans/2026-11-12_starter-packs-design.md` under Jason's starter-packs ruling; do not build this design. + Status: design only. Nothing here is built and no `roles/` or contract file is changed. Prepared 2026-11-10 against `refactor` `8630b8dfc` for phase P2 of #1873 (unified workspace). PRD requirement: REQ-SYS-2, "An agent working in a diff --git a/docs/plans/2026-11-12_starter-packs-design.md b/docs/plans/2026-11-12_starter-packs-design.md new file mode 100644 index 000000000..f174eb173 --- /dev/null +++ b/docs/plans/2026-11-12_starter-packs-design.md @@ -0,0 +1,1283 @@ +# Starter packs + +Status: design only. Nothing here is built. No `roles/`, `contracts/` or +schema file is changed. Prepared 2026-11-12 against `refactor` `2ad4b30eb`. +It replaces `docs/plans/2026-11-10_system-type-toolsets-design.md`, which is +superseded and must not be built. + +## The ruling + +Jason, 2026-11-12, about 20:05Z: + +> I agree. Starter packs instead of over-restrictive configurations. We +> provide a handful of pre-configured options for system types, then the user +> can customize to their needs. Agent roles will be the main limiter or +> permitter of actual capabilities in the system. The default starter packs +> can affect the various Kanban styles, workflow templates, agent roles, and +> other primitives in the system. The user and their agents will be able to +> customize the system operations by allowing the import and implementation of +> the various roles, workflows, and other available items as needed. The user +> will also have the option to create new workflows within their system. The +> agent roles will be customizable within the absolute constraints that are +> offered. We have discussed that no agent can grant permissions beyond those +> they already possess. The org or project configurations will establish the +> "sandbox" for agent operations. The user and chief/assistant can work +> together to customize the org/project as needed to accomplish a goal. The +> user can also save a custom org/project template for use repeatedly to meet +> their needs. + +Earlier he said a system type is there "to provide a basic configuration using +templates for agent setup and system function", and that the user customizes +the system while working inside it. He judged the toolset gating design +counterproductive. + +What this design takes from that: + +- A pack is a starting point that is copied in once. It is not a switch that + stays on and gates what agents can do. +- Roles are the limit. A pack grants nothing; every agent it proposes is hired + through the existing hire decision, bounded by the chief's own role. +- The organization or project is the sandbox. Nothing a pack, a saved + template or an agent does reaches outside it. +- Customizing from inside uses the paths that already exist: the T1 definition + verbs for people, hire and change decisions for the chief, and the chief's + `organization.change` grant for settings that carry no capability. No new + human gate is added. + +## 1. Inventory + +Status means: **exists** (built and usable by a pack as is), **partial** +(built, but a pack cannot set it or it lacks a piece a pack needs), **missing** +(not found in the code). + +| Item | Where | Status | Notes | +|---|---|---|---| +| System-type plug-in kind | `packages/plugins/src/kinds.mjs:84-86` (`switch: "list"`, export `systemType`) | exists | A pure label today. | +| The five system-type modules | `packages/plugins/system-type/{coding,creative-writing,marketing,montage,business-management}/index.mjs`, `systemType()` | partial | Each returns `{id, label, description, toolsets: []}`. Nothing else. | +| Loading system types | `packages/plugins/src/system-types.mjs:5` `installedSystemTypes` | exists | Any refused module, or keys other than `description,id,label,toolsets`, throws `system-type-invalid`. | +| Org enable | `plugins['system-type']` in the org file; the kind is checked by `checkPlugins` (`packages/plugins/src/kinds.mjs:146-148`, `unknown-kind`, called at `packages/organization/src/organization-config.mjs:358`) and each module at `organization-config.mjs:360-365`; fenced by `systemTypesFence`, `packages/organization/src/organization.mjs:126-131` (`organization-system-types-human-only`) | exists | Read back by `system-types.current`, `packages/bus/src/runtime.mjs:904`. Console `GET/POST /api/system-types` (`packages/webui/src/system-types-http.mjs:4-9`), right `org.setup` (`packages/organization/src/org-rights.mjs:90`). | +| Project selection | `project.system-types[.set]`, `packages/bus/src/broker.mjs:8476-8495`; table `project_system_types` in `packages/bus/src/migrations/3t-3u.sql` (append-only) | exists | Human only. CLI `mosaic project system-types` and `system-types-set`, `packages/cli/src/project.mjs:17-18`. | +| Other system-type surfaces | CLI `mosaic org system-types installed\|enabled` (`packages/cli/src/org.mjs:5,15,49,51`); channel verbs (`packages/plugins/channel/cli/index.mjs:9-10`); Console page `packages/webui/src/public/system-types.js` with hooks in `workspace.js:173,193-194`, `chat.js:623`, `serve.mjs:23,85-86,826`, `webui/src/cli.mjs:9,193`; refusal `system-type-not-enabled` (`packages/bus/src/refusals.mjs:183`); bus reads `system-types.installed` and `system-types.current` with the `installedSystemTypes` import (`packages/bus/src/runtime.mjs:30,903-904`); right `'system-types.console': 'org.setup'` (`packages/organization/src/org-rights.mjs:90`); the `project` dispatch list (`packages/cli/src/cli.mjs:781`) | exists | All label plumbing; none affects an agent. | +| System type effect on agents | launcher, harness, `packages/organization/src/resolve.mjs` | missing | No launch path reads system types (no hits in those files). Every module's `toolsets` is empty. | +| Shipped role templates | `roles/*.json` + `.md` | exists | V2 roles: assistant, chief-executive-officer, chief-financial-officer, chief-information-officer, chief-information-security-officer, chief-marketing-officer, chief-of-staff, coder, companion, continuity, cto, fact-checker, ideas, merge-gate, pm, proofreader, research, reviewer, security-reviewer. `researcher.json` is V1 and refused by `readTemplate`. | +| Template identity | `packages/organization/src/definitions.mjs:174,180` `templateHash`, `templateHashes` | exists | Name plus sha256. | +| Org definitions: import, invent, edit, remove | `packages/organization/src/definition-verbs.mjs:61` `definitionVerbs`; CLI `mosaic org definitions ...` (`packages/cli/src/org-definitions.mjs:15`) | exists | `authorize` (line 38) refuses any agent (`human-required`) and needs `org.hire`. Writes go through `writer.setDefinition`; a definition in use refuses `definition-in-use`. | +| Template diff | `packages/organization/src/definitions.mjs:252` `diffDefinition` | exists | Compares a copy with its template's current text. | +| Composition check | `packages/organization/src/composition.mjs:443` `checkComposition` | exists | Refuses hard waits (R4). Flags every line above the chief's own role (tools, network, actions, credentials, messaging) and every resolve right. | +| Plug-in role templates | `roleTemplates` in `packages/plugins/src/manifest.mjs:48` `checkManifest`; read by `packages/organization/src/composition.mjs` | partial | A PL1 manifest may declare templates with plugin-prefixed names. I did not find the hire path reading them. | +| Hire as a decision | `packages/organization/src/hire-decisions.mjs:65` `hireDecisions` (`prepare`); `authorizeHire` line 32; broker `#raiseHire`, `packages/bus/src/broker.mjs:6578` | exists | `instance.hire` fails `decision-required` (6236). Only the chief raises (`role-not-chief`, 6583). A human with `org.hire` starts and approves in one step through `hire.start.console`. Flagged and credential lines need the owner; root needs the instance owner. The definition name `hire-` is set at `hire-decisions.mjs:260`; `apply` (line 337 on) writes it and the instance. The input needs a `route` (`hire-decisions.mjs:198-199`). | +| Autonomy setting | `packages/organization/src/autonomy.mjs:2-4` | exists | Classes cross-role, merge, policy, authority. Authority defaults to in the loop. Owner-only (`autonomy.*` null in `VERB_RIGHTS`). | +| The chief's own settings grant | `organization.change` in `roles/chief-of-staff.json`; broker `organization.set`, `packages/bus/src/broker.mjs:6247-6340` | exists | The chief changes limits directly under its within-role grant. The trail records each change. | +| Task board columns | `STORE_STATES`, `packages/bus/src/broker.mjs:334`; `packages/tasks/src/startup.mjs:6` `BUCKETS`; `packages/webui/src/public/tasks-board.js:21` `STATES` | partial | Five fixed states (todo, in-progress, in-review, blocked, done) with fixed labels. Nothing configures them. | +| Kanban styles | `packages/webui/src/public/tasks-board.js:146` `kanban`, `:156` `list` | missing | Two layouts (REQ-WS-8). No per-org or per-project style, label or order. | +| Workflow templates | grep for "workflow" in `packages`, `scripts`, `roles`, `contracts`, `missions` | missing | No workflow document, store or engine. The one built-in flow is the fixed request, PRD and approval path (`packages/bus/src/refusals.mjs:194,251`, "Role workflow", "Role PRD workflow"); it is not pack-settable and this design leaves it alone. | +| Mission and task templates | `missions/hello.json`; `tasks/*.json`; schemas in `scripts/mosaic-task.mjs:94,139` | partial | These drive headless worker runs (`run-task.sh`), not org operations. `missions/` holds one file. A pack does not carry them. | +| Skills | repo `skills/` (16 skills); `scripts/skill.sh` install/activate into `/skills-enabled`; role `skills` key resolved in `packages/harness/src/bundle.mjs:126-192` | exists | Machine-scoped. A role names skills; launch refuses a missing one. A pack can name skills but cannot install them. | +| Plug-ins (code) | `packages/plugins/src/loader.mjs:123` `loadInstalled`; `packages/plugins/src/install.mjs:71` `installPlugin` (hash pin, read-only files) | exists | A module runs with the host's authority (loader header). Install is an instance-owner act (R10). | +| Workspace and dashboard sections | `packages/webui/src/workspace-read.mjs:13` `WORKSPACE_SECTIONS` | partial | Fixed: projects, sessions, inbox, tasks, metrics. No per-org configuration found. | +| `mosaic org template render` | `packages/organization/src/org-service.mjs:92` `templateRender`; `packages/organization/src/org-template.json` | exists | Renders human, chief, assistant and one `work` project. Presets from harness and model-provider modules (`discoverPresets`, line 61) overlay `plugins`, `launch` and assistant vars. Console `GET /api/org-template`, `/api/org-presets` (`packages/webui/src/org-admin.mjs:413-419`). | +| Project settings | org file `projects.` keys root, repo, remote, git, agents (`packages/organization/src/organization-config.mjs:306`); repo file `.mosaic/project.json` (`packages/organization/src/project.mjs`, read only) | partial | No board or workflow setting per project. | +| Agent levels | `packages/organization/src/agent-levels.mjs` (`AGENT_LEVELS` line 15, `checkProjectAgents` line 111, `resolveAgentLevels` line 207) | exists | Root and project agent entries refuse the instance keys that bind an agent to one organization: credentials, holder, tracker, identity, publish, hosts (`BOUND_KEYS`, line 21; `noBoundKeys`). This is instance material, not the role's credential request lines. | +| Copy credential bindings | `copyBindings` fence, `packages/organization/src/organization.mjs:32-36`; writer at line 550 | exists | Owner-approved binding decision only. | +| Pack format, apply, saved templates | none | missing | This design. | + +Missing building blocks, plainly: workflow templates, Kanban styles (labels, +order, hidden columns), any per-org or per-project board setting, a pack +format, a pack apply path, and saved org or project templates. Montage has no +video roles or tools in the repository; the OpenMontage integration was not +found. + +## 2. What a pack is + +A pack is a declarative JSON document. It lists items to copy into an +organization or a project. Once applied it is not a running module, it never +stays switched on, and it grants nothing. How the document is produced +differs by source (next subsection): a shipped or installed pack is code that +returns the document. + +### Where packs live + +| Source | Location | Who adds one | +|---|---|---| +| Shipped | Core plug-in modules `packages/plugins/starter-pack//index.mjs`, exporting `interfaceVersion = 1` and `starterPack()`, which returns the document | Reviewed commit | +| Installed | PL1 hash-pinned plug-ins under `/plugins`, kind `starter-pack` | Instance owner, at the host (R10) | +| Saved by a user | `/templates//.json`, mode 0600, written with `wx` (never overwritten) | The organization's owner, through `template.save` (section 5) | + +Shipped and installed packs follow the loader exactly: the same component and +symlink checks, the same refusal codes, a refused installed pack omitted and +logged, a refused core pack failing the listing as `installedSystemTypes` +(`system-types.mjs:5`) does until SP9 removes it. + +An installed pack is executable PL1 code. The loader imports its `index.mjs` +(`loadPinnedModule`, `packages/plugins/src/loader.mjs:244-256`), and a loaded +module runs with the host's authority (`loader.mjs:1-6`). Importing it can do +anything the host can, before `checkPack` ever sees the document it returns. +The hash pin proves the bytes are the ones the owner installed, not that they +are safe. So installing a pack is the same trust act as installing any PL1 +plug-in: the instance owner does it at the host (`installPlugin`, R10), under +the same host-authority warning. `checkPack` guards what a pack copies into +an organization; it does not contain the code that produced it. Someone who +wants to share a pack without that trust shares a saved template file, which +is data only. + +A saved template is read, never imported or run. `parseJsonFileText` +(`packages/organization/src/util.mjs:66`) is a bare `JSON.parse` with no file +checks, so `readSavedTemplate` does them first: + +- the account is the calling session's `s.human`, which the owner-only + verbs already require to equal the organization's `human`. It is checked + against `ID_PATTERN` (`packages/organization/src/vocabulary.mjs:189`), + and the template name against `NAME` (`packages/plugins/src/kinds.mjs:20`), + before any path is built, so `/` and a leading `.` never reach the file + system. When row 193 opens these verbs to members, the account becomes + the member's verified account id under the same check; +- the path resolves only under `/templates//`; + `saved:` never names another account's file; +- `lstat` refuses a symlink, a file not owned by the instance's uid, and a + mode wider than 0600; the account directory is created 0700; +- the size is checked before the read and refuses above 256 KiB; +- the parsed document passes `checkPack` with origin `saved`. + +Saved templates are visible only to their saver. The `packs` reads list +shipped and installed packs only; saved ones appear only through +`template list`, which is owner only and lists the caller's own. The agent +verbs refuse origin `saved` with `human-required`, so an agent in one +organization can never preview the owner's role text from another. + +The origin is set by the code that read the document, from where it was +read: `shipped` for a core module, `installed` for a PL1 module, `saved` for a +file under `/templates/`. It is a closed set of those three values +and never a field the document supplies, so a shipped or installed pack +cannot claim the saved rules. Origin `saved` relaxes exactly the two rules +named below and runs every other structural, role, scope and content check. + +### The document (packVersion 1) + +```json +{ + "packVersion": 1, + "id": "coding", + "version": "1.0.0", + "label": "Coding", + "description": "A small software team with review before merge.", + "scope": "org", + "definitions": { + "coder": { "template": "coder", "sha256": "<64 hex>" }, + "release-writer": { + "from": { "template": "coder", "sha256": "<64 hex>" }, + "role": { "tools": ["read", "edit"], "authority": { "withinRole": ["record.write"] } }, + "instructions": "...", + "personality": { "summary": "..." } + } + }, + "hires": [ + { "name": "coder-1", "definition": "coder", "level": "org", "reason": "writes the code" } + ], + "board": { + "columns": [ + { "state": "todo", "label": "Backlog" }, + { "state": "in-progress", "label": "Building" }, + { "state": "in-review", "label": "Review" }, + { "state": "blocked", "label": "Blocked" }, + { "state": "done", "label": "Merged" } + ], + "layout": "kanban" + }, + "workflows": { + "change": { + "description": "One change from task to merge.", + "steps": [ + { "id": "plan", "title": "Write the task", "roles": ["pm"], "state": "todo" }, + { "id": "build", "title": "Implement", "roles": ["coder"], "state": "in-progress" }, + { "id": "review", "title": "Review", "roles": ["reviewer"], "state": "in-review" }, + { "id": "merge", "title": "Approve the merge", "roles": ["human"], "state": "done" } + ] + } + }, + "skills": ["ms-sdlc", "ms-unslop"] +} +``` + +Pack definitions carry no `rev`. Apply sets `rev: 1` before +`checkDefinition`, which requires it (`definitions.mjs:30,85`). + +`checkPack(doc, {origin})` (new, in `packages/plugins/src/starter-pack.mjs`) +refuses with `starter-pack-invalid` unless all of these hold: + +- Exact top-level keys as above. Origin `saved` also allows one more key, + `packs`, a list of at most 32 `{id, version}` entries (`id` matching + `NAME`, `version` semver, no other key). Nothing else differs by origin except + the `from` rule below. `scope` is `org` or `project`. +- `id` matches `NAME` (`packages/plugins/src/kinds.mjs:20`). `version` is a + semver string. +- Each `definitions` entry is one of two shapes: + - A template reference `{template, sha256}`. The name must be a V2 role in + `roles/` and the hash must equal `templateHash` now. A stale pin refuses + `starter-pack-template-changed`, so a pack never imports text nobody + reviewed against it. + - A full entry in the T1 shape (`ENTRY_KEYS`, `definitions.mjs:30`). It + must name `from` and pass `checkDefinition`. Its role must narrow the + template named in `from`, line by line, under the new + `narrowsTemplate(role, template)`: every tool, action, skill, messaging + target and credential request line is in the template, and the network + is no wider. This is "no grant above the templates". Origins `shipped` + and `installed` may not carry a role without `from`. Origin `saved` may + (an invented role); it still passes `checkComposition` on apply and is + hired only through a decision. +- Role `credentials` lines are credential requests: a service and scope + names, the same key `ROLE_KEYS` allows today (`definitions.mjs:32`) and + `entryFromTemplate` copies from a template (`definitions.mjs:198-213`). A + pack may carry them as a subset of the `from` template. Credential + material is a different thing and never allowed (next subsection). +- `hires`: at most 8. Each `name` passes `requireId`. Each `definition` names + an entry in this pack's `definitions`, so every hire is covered by a hash + pin and `starter-pack-template-changed`. There is no `level` field: a hire + is org-level in an `org`-scope pack and project-level in a `project`-scope + pack, never root. A hire carries no `route`; the raiser supplies it, by + default from the organization's `launch` settings. +- `board.columns` holds the five `STORE_STATES`, each exactly once, in any + order, each with a label of 1 to 32 characters. `layout` is `kanban` or + `list`. The tracker contract does not change (REQ-TASK-1, REQ-WS-8). +- Each `workflows` entry has 1 to 20 steps. Each step's `state` is in + `STORE_STATES`. Each step's `roles` is a list of 1 to 8 names, each a + definition in the pack, a V2 template, or `human` (the user does that + step). A list means "any of these", for steps like "the relevant officer + drafts". +- `skills` entries match `NAME`. They are a list of what the roles need, not + an install. + +The held-credential scan cannot run at load: a file load has no credential store +(`NO_STORE`, `definitions.mjs:51`). The bus runs the credential store's +`assertClean` on every string of the pack at preview, at apply and at template +save, as `checkDefinition` does for definitions today (`definitions.mjs:81`). + +### What a pack never contains + +The schema has no key for any of these, so a pack carrying one refuses: + +- credential material: instance `credentials`, `copyBindings` or a binding + `copyId`, tokens, tracker bots, identity, publish, holder or hosts; +- members, rights or the owner; +- the autonomy setting or arbiters; +- plug-in selections (`plugins`), including the task tracker and theme; +- `launch` (models, limits, keep, noHire, account); +- a root-level hire; +- any role line outside the `roles/` rails. `checkComposition` still runs on + every definition at apply time and refuses a hard wait (R4) and + `role.hire`, `role.fire` or `role.change` in a hired composition + (`NEVER_HIRED`, `composition.mjs:51`, R9). + +### What a board style and a workflow do + +**Board.** Labels, order and a default layout over the five fixed states. The +tasks view aggregates every organization (REQ-WS-8, `docs/prd/mosaic-stack.md` +around line 1102), builds one set of columns, and groups each column's cards +by organization (`tasks-board.js:146-150`). So: + +- an organization's or project's labels and order apply only when the view is + filtered to that one organization or project; across several, today's labels + and order stay; +- `layout` is a default used only when the viewer has no saved choice. The + viewer's choice is kept per device in `localStorage` (`tasks-board.js:18-20`) + and wins. + +**Workflow.** A workflow is guidance text that agents plan by. It is not an +engine and moves no task by itself. In v1 the generated prompt +(`prompt`, `packages/harness/src/bundle.mjs:271`) gains a read-only workflows +section for the organization or project the session runs in. It goes to the +chief, to any role holding `task.create` or `task.assign`, and to any role a +step names, which sees its own steps. The section says that workflows are the +organization's guidance, that they grant nothing, and that the contract and +the role rails win on any conflict. The chief and the pm then write and +assign tasks by them through the task verbs they already hold. A step with +`human` in its `roles` is one the agents leave to the user. + +Workflows are inert data. No runner exists, and this design adds none. A +runner that acts on workflow steps is a separate design with its own +sign-off. Any such runner must resolve a live hired instance for each step +and authorize every action against that session's own effective role and +project sandbox, as every bus verb does today. A step's `roles` names are +labels for planning, never authority. + +Workflow text reaches other agents' prompts, so its writers matter. Only +the owner (a person with `org.hire` once row 193 lands) or a holder of +`organization.change` writes it. The chief can already message any agent, +so a workflow is a standing copy of text the chief could send anyway, but +it persists in every matching prompt. That is why it renders as quoted +data under a fixed heading, below the contract (SP7). The text still gets the held-credential scan and a size cap (20 steps, titles up to 120 +characters, description up to 1,000). + +### Versioning + +`packVersion` is the schema version; an unknown one refuses. `version` is the +pack's own release. An apply records `{id, version, sha256}` where `sha256` +is the hash of the canonical JSON that was applied. A new pack version is a +new document; it never edits an organization that applied an older one. + +## 3. Applying a pack + +### What apply does + +Apply copies items in once. After that the organization or project owns them, +exactly as if a person had made them by hand. The steps: + +1. **Preview.** `pack.preview` reads the pack and the organization and returns + a plan: each item marked `add`, `same` or `kept` (section 3, re-apply), the + flagged and credential lines each proposed hire would carry against the + current chief, any proposal whose definition is in `launch.noHire`, the + skills not enabled on this machine, and a digest of the plan plus the + organization file revision. Nothing is written. +2. **Apply.** `pack.apply` takes the digest. It refuses `pack-plan-stale` if + the revision or plan changed. It commits once, through a new writer + operation `applyPack(id, plan, {revision, actor, assertClean})`. + `writer.setDefinition` cannot do this: it commits one definition per call + and refuses any other change in the same write + (`organization-definitions-only`, `organization.mjs:401-430`). `applyPack` + reuses its rules, not the function: + - each `add` definition gets `rev: 1`, passes `checkDefinition` with + `assertClean` and `checkComposition`, and keeps `from`; + - an agent actor that writes any definition refuses `human-required`, as + `setDefinition`'s guard does; + - the write refuses if any key changes outside `definitions`, `board`, + `workflows`, `projects.

.board`, `projects.

.workflows` and `packs`; + - it writes the `board` and `workflows` items marked `add` and appends + `{id, version, sha256, origin, scope, project, at, by, added, + proposedHires}` to a new `packs` key. `added` names each item this + apply wrote. Each `proposedHires` entry records the pin the chief will + raise it with: `{template, sha256}` for a template reference, + `{definition, rev}` when this apply wrote the definition, or + `{inline, sha256}` (the canonical hash of the full entry) when it did + not. + + One commit means a crash leaves either the whole pack or none of it, so the + re-apply table never meets half-applied items without a `packs` record. +3. **Hires.** Apply never hires. It leaves the pack's hires as proposals in + the `packs` entry. Each becomes a hire through the existing paths: + - the chief raises `hire.raise` (`broker.mjs:6237`) with the proposal + and a route it chooses. After a person's apply, which wrote the + definitions, the pin is `{definition, rev}`; after an agent apply, + which writes none, it is a template or inline pin (next subsection + but one). Either way the broker computes the + flagged and credential lines, and the decision routes by the authority + autonomy level; + - or a person with `org.hire` starts it with `hire.start.console`, which + shows the same lines and needs the owner for any flagged one. + + Most shipped hires carry flagged or credential lines, so under the + existing `authorizeHire` rule (`hire-decisions.mjs:40,46`) the owner + approves them on every autonomy setting. That is today's rule for any + hire of these roles, not a gate this design adds. Section 7 lists each + proposal's lines. + + A proposal is filled when an instance by that name exists. A pack applied + to a project proposes project-level hires, which land in + `projects.

.agents`; those entries refuse instance credential material + (`BOUND_KEYS`, `agent-levels.mjs:21`). + +### The surface + +| Layer | Name | +|---|---| +| CLI (owner verb) | `mosaic org pack list`, `mosaic org pack preview ID PACK [--project P]`, `mosaic org pack apply ID PACK --digest D [--project P]`; `mosaic org board set ID FILE [--project P]`; `mosaic org workflow set ID NAME FILE [--project P]`, `mosaic org workflow remove ID NAME [--project P]` | +| Console route | `GET /api/packs`, `POST /api/orgs//packs/preview`, `POST /api/orgs//packs/apply`, `POST /api/orgs//board`, `POST /api/orgs//workflows/`, `DELETE /api/orgs//workflows/` | +| Bus ops, Console | `packs.console` (read), `pack.preview.console`, `pack.apply.console`, `board.set.console`, `workflow.set.console` | +| Bus ops, unsuffixed | `packs`, `pack.preview`, `pack.apply`, `board.set` and `workflow.set`. The CLI reaches the bus as a plain human session, and `broker.mjs:6047` refuses a `.console` verb from one (`human-required`), so the CLI words call these, as `org-definitions.mjs:86-89` calls the unsuffixed `definition.*`. They serve the organization's human only, gated as the definition writes are (`broker.mjs:6305-6308`) (SP4), and an agent whose role file lists `organization.change` (SP5) | +| Remove | `workflow.set` (or `.console`) with `workflow: null` removes that workflow | +| `VERB_RIGHTS` | `packs.console` to `board.read`. `pack.preview.console`, `pack.apply.console`, `board.set.console` and `workflow.set.console` to `null` (owner only). The unsuffixed verbs are not in `VERB_RIGHTS`: unsuffixed keys there are reader session verbs (`BOARD_VERBS`, `broker.mjs:199`) | + +Owner only, for now, because that is what the bus does today for definition +writes. `VERB_RIGHTS` maps the T1 definition verbs to `org.hire`, but the +bus still lets only the organization's one human through until row 193 +(`broker.mjs:6303-6308`, refusing `human-required`), and a non-owner +member's Console session reaches only the four hire verbs +(`broker.mjs:6221`). The pack verbs follow the same rule, so this design +widens no human boundary. When row 193 opens definition writes to +`org.hire` holders, these four verbs move to `org.hire` in the same change. +Board and workflow writes go through the same writer op as apply +(`applyPack`, with only the board and workflow keys in the plan), so they +share its key fence. + +### The chief applying a pack + +The roles-and-rails design says the chief proposes a definition only inside a +decision (section 5). The agent `pack.apply` respects that. + +Who may call it: any role whose file lists `organization.change`, the same +test `organization.set` uses (`broker.mjs:6248-6255,6321`). The shipped chief +holds it. A role without it gets what `organization.set` gives it today: +`decision-required` (`#classification`, `broker.mjs:4743`; asserted in +`packages/bus/tests/chief-of-staff.test.mjs:255-261`), so the existing +decision path stays and no new gate appears. The chief could hire another agent holding it as a plain hire, +since step 7 does not flag a line the chief already holds; that agent could +then change boards and workflows too, which carry no capability. "The chief" +in section 4 means any holder of `organization.change`. + +What it does: + +- it writes `board` and `workflows` directly under `organization.change`, + the same way `organization.set` writes limits today + (`broker.mjs:6247-6340`), through `applyPack`; +- it refuses a pack of origin `saved` with `human-required` (section 2); +- a workflow step that names a definition this apply cannot write is left + out, and preview marks that workflow, so an agent apply never leaves a + step naming a role that does not exist; +- it writes no definitions. Each proposed hire is raised by the chief (only + the chief raises, `role-not-chief`). For a template-reference definition + the role is `{template, sha256}`, which the `launch.noHire` check reads + (`hire-decisions.mjs:219`) and which keeps `from`. Only a full-entry + definition goes as `{inline}`; that path starts from an empty role + (`hire-decisions.mjs:227-228`), drops `from` and skips `noHire`, so preview + marks such a proposal when its `from` template is in `launch.noHire`. The + broker computes the lines against the chief either way, and the approved + hire writes `hire-` as it does now. +- the chief raises a proposal with `hire.raise {proposal: {pack, project?, + hire}, route, reason}`. The broker reads the matching + `packs[].proposedHires` entry from the loaded organization (SP3 keeps + `packs`). It fills `op: "hire"`, `name`, `level` and `role` from the + recorded pin and refuses any `role`, `name` or `level` the caller also + sends. Then it checks the pin: a `{template, sha256}` whose `templateHash` + moved refuses `decision-stale`; a `{definition, rev}` whose entry moved + refuses `definition-rev-conflict`; an inline proposal reads the full entry + stored in the `packs` entry, refuses `starter-pack-proposal-stale` if its + canonical hash differs from the pin, and refuses `definition-not-hireable` + (the code `hire-decisions.mjs:219` gives for a template) if its `from` + template is in `launch.noHire`. It then passes + `role: {inline: compositionOf(entry)}`. The broker strips `proposal` + before `prepare`, so `requestKeys` (`hire-decisions.mjs:27,190`) is + unchanged. A raise without `proposal` is today's path, unchanged. This + is needed because `hire.raise` passes its args unchanged to `#raiseHire` + (`broker.mjs:6237-6240`), and the existing `decision-stale` and + `definition-rev-conflict` checks (`hire-decisions.mjs:225-226`) compare + against what the caller sends, not against the recorded pin. + +### A second pack, and re-applying + +Items merge by name and nothing is overwritten: + +| Item in the pack | Organization already has | Result | +|---|---|---| +| Definition | nothing by that name, never added by this pack | `add` | +| Definition | nothing by that name, but an earlier apply of this pack id lists it in `added` | `removed`, no write | +| Definition | same composition, instructions and personality | `same`, no write | +| Definition | different (edited, or a newer pack version) | `kept`. The preview shows `diffDefinition`. The person may import the pack's version under another name with `--as`. | +| Board | no `board` key | `add` | +| Board | a different board | `kept` | +| Workflow | nothing by that name, never added by this pack | `add` | +| Workflow | nothing by that name, but an earlier apply of this pack id lists it in `added` | `removed`, no write | +| Workflow | a different workflow by that name | `kept`, with `--as` | +| Proposed hire | an instance or open hire decision by that name | dropped from the new proposals | + +`removed` keeps a deliberate removal from coming back on its own: the +earlier `packs` entries act as the tombstone. The tombstone is per pack id: +pack B naming an item pack A added and the owner removed is a plain `add`. +The person may bring the item back on purpose with `--as` and a new name, which the preview shows as an +`add`. The same rule covers a board the organization removed after this pack +added it. + +There is no forced overwrite flag. To take a newer pack version of a definition +a hired agent uses, the person or chief raises a change decision, which is the +existing path. + +### Removing a pack + +Removing a pack takes nothing away. Items were copied, so the organization +keeps them. The `packs` entry stays as history. Uninstalling an installed pack +plug-in uses `removePlugin` (`install.mjs:99`); a pack is never "enabled", so +`plugin-enabled` never refuses it, and no organization changes. + +## 4. Customizing from inside + +The ruling asks that the user and their agents can import roles, workflows and +other items, create new workflows, and customize roles inside fixed limits. +The table shows who does what. Every row reuses a path that exists or adds a +verb at the same level as its neighbours. None adds a person to a path that +had none. + +"The chief" in this table means any role whose file lists +`organization.change` (section 3) for board, workflow and pack writes. Hire +and change decisions stay chief-only. + +| Change | A person does it directly | The chief does it | The limit at that step | +|---|---|---|---| +| Import a role template | `definition.import` (T1; `org.hire` in `VERB_RIGHTS`, owner at the bus until row 193) | Inside a hire or change decision, role `{template, sha256}` | `checkComposition`. Nothing runs until a hire. | +| Customize or invent a role | `definition.edit` or `invent` (T1) | Inside a hire or change decision, role `{inline}` | `checkComposition`; lines above the chief are flagged and only the owner approves them (R8). Editing a definition in use refuses, so a running agent never widens without a change decision. | +| Hire, fire, change an agent | `hire.start.console` (`org.hire`) | `hire.raise` (only the chief: `role-not-chief`, `broker.mjs:6583`; hired agents never hold `role.hire`, R9) | Broker-computed flagged and credential lines; authority autonomy level; root needs the instance owner. | +| Apply a pack | `pack.apply.console` (owner; `org.hire` once row 193 lands) | `pack.apply` under `organization.change`, no definitions | As above for every hire it later proposes. | +| Import a workflow or board from a pack | Part of apply | Part of the chief's apply | No capability. | +| Create or edit a workflow | `workflow.set.console` (owner; `org.hire` once row 193 lands) | `workflow.set` under `organization.change` | At write time, steps name existing definitions, templates or `human`, and the five states. A workflow grants nothing; agents read it as guidance below the contract. | +| Change board labels or order | `board.set.console` (owner; `org.hire` once row 193 lands) | `board.set` under `organization.change` | The five states stay. | +| Project agents, board and workflow | The same verbs with `--project` | The same, under `organization.change` | Project agent entries refuse instance credential material (`BOUND_KEYS`, `agent-levels.mjs:21`). | +| Enable a skill on the machine | `scripts/skill.sh activate` (instance owner) | Not available | Machine-scoped. | +| Install a pack plug-in | `installPlugin` (instance owner, R10) | Not available | Host authority. | +| Autonomy level | Owner only | Never (R12) | Unchanged. | + +**"No agent grants beyond its own permissions", step by step.** + +1. A pack: its definitions narrow the shipped templates (`narrowsTemplate`). +2. Apply: definitions pass `checkComposition`; apply writes no instance. +3. The chief's proposal: the broker, not the chief, computes the flagged lines + against the chief's own role (`checkComposition` step 7, + `composition.mjs:491`). The chief cannot omit them. +4. Resolve: an agent resolver can resolve only a plain hire with no flagged + or credential lines, not at root, not about itself (`authorizeHire`, + `hire-decisions.mjs:39-42`). Anything flagged goes to the owner. Of the + shipped proposals only `cmo` is plain (section 7), so in practice the + owner approves the rest, as for any hire of those roles today. +5. A hired agent: `role.hire`, `role.fire` and `role.change` refuse in its + composition (`NEVER_HIRED`, R9), so it cannot hire in turn. +6. Saved templates go through steps 2 to 5 again in the new organization. + +**The sandbox.** Every agent a pack proposes is an org or project agent. It +runs in one organization, sandboxed as the instance uid with only its role's +tools (R6, R7). A project agent sees only that project's workspace. A pack +cannot name another organization, a host, a credential or a root-level hire. + +**The assistant.** The assistant does not hire, direct agents or assign tasks +(REQ-ASSIST-1). It helps the user pick and preview a pack and passes the +request to the chief. It gets no new verb. + +## 5. Saving a template + +### The verb + +| Layer | Name | +|---|---| +| CLI (owner verb) | `mosaic org template save ID NAME [--project P] --digest D`, with a preview `mosaic org template save ID NAME --preview` | +| Console route | `POST /api/orgs//templates/preview`, `POST /api/orgs//templates` | +| Bus op | `template.save.console`, `VERB_RIGHTS` null (owner only) | +| Lists | `mosaic org template list`, `GET /api/templates`: only the caller's own | + +Owner only, because a saved template copies the organization's role text, +which is the owner's work product, out of the organization. The preview shows +the exact JSON that will be written; save takes its digest. + +### What is captured + +- `definitions`: role, instructions, personality and `from`. Definitions named + `hire-` are renamed `-` (for example `coder-1`), so no + decision id or instance name leaves the organization. +- `hires`: one proposal per org instance (or project agent, with `--project`): + a generated name `-` and the definition. Instance names are not + copied; they often name a person or a client. The level follows the template's scope. Never + the chief or the assistant, which every organization already has. +- `board` and `workflows`. +- `skills`: the union of the captured roles' `skills`. +- `packs`: `{id, version}` for each pack applied before, and nothing else. + The org's own entries carry `by`, `project`, `at`, `added` and + `proposedHires`; none of those is copied. Applying the template records + one new entry in the target organization's `packs` with origin `saved`; + the template's own `packs` list is not copied into it. + +The result is a pack document read later with origin `saved` (section 2). A +role in a saved template may lack `from` (an invented role) and the document +may carry `packs` provenance; those are the only differences from a shipped +pack. Every role still passes `checkComposition` on apply and is hired only +through a decision. + +### What is stripped + +Everything not listed above. In particular: + +- the owner, members, accounts and rights; +- identities: `human`, instance `identity`, `publish`, `holder`, `hosts`, + tracker bot names and ids; +- credential material: instance credentials, `copyBindings` and their + `copyId`, `orgIncarnation` and `generation`; +- `launch` (models, limits, keep, noHire, account, grant scope), `autonomy`, + `arbiters`, `plugins`, `appearance`, `usage`, `replyLimits`, `retired`; +- instance and organization `vars`; +- project roots, repos, remotes and git state; +- definition `rev` counters and `hireDecision` references; +- everything on the bus: run history, sessions, threads, tasks, decisions, + the trail. + +Role `credentials` lines are kept. They are requests (a service and scope +names), part of what the role is, the same lines a shipped template carries. +They carry no token or binding. On apply the broker lists them as credential +lines on the hire, so the owner approves each one (`authorizeHire`), and the +credential itself arrives only through the owner-approved binding, as today. + +Free text is the owner's work product and is kept: instructions, +personality, workflow titles and descriptions, and board labels. It can still +carry private details, so save handles it in three ways: + +1. Save replaces each literal occurrence of the organization id, its project + ids, its instance names, the owner's account name and the project roots + with the placeholders `{org}`, `{project}`, `{agent}`, `{owner}` and + `{root}`. The preview lists every replacement by field path. +2. The preview shows every kept free-text field in full, grouped by field + path, so the owner reads what leaves before taking the digest. A field the + owner wants gone is edited in the organization first and saved again. +3. Save runs `assertClean` on every string, as definitions do. That check is + credential-value detection only: it refuses text that contains a value the + credential store holds now (`assertNotHeld`, `packages/bus/src/credentials.mjs:721-723`). + It does not find names, paths, clients or a secret the store never held. + A hit refuses `template-secret-suspected` and names the field path, never + the text. + +Nothing else scrubs free text. The owner is the reviewer of what a template +says, which is why save is owner only and shows the exact JSON first. + +### Applying one to a new organization + +1. `mosaic org template render ID --output FILE`, then `mosaic org create ID + --from FILE`: the base organization with human, chief and assistant, as + today. +2. `mosaic org pack apply ID saved: --digest D`: the same preview and + apply as a shipped pack. `saved:` resolves only under the caller's + own account directory (section 2), and the caller must own the target + organization. +3. The chief raises the proposed hires. + +There is no create-time shortcut that skips the hire decisions. + +## 6. System types + +**Decision: removed outright.** Jason, 2026-11-12, about 21:05Z, answering +section 9 question 1: + +> The only deployed system is the dev environment on dragon-lin. It can be +> wiped and reconfigured. No reason to hold on to legacy for this change. + +The `system-type` kind is deleted and `starter-pack` takes its place. A +pack's `label` is what the UI shows as the system type. The reasons: + +- Nothing reads system types today except the enable and select verbs, and + every module's `toolsets` is empty. A list switch that grants nothing has no + job once packs exist. +- Keeping both would leave a second, empty configuration surface next to the + real one. + +There is no retired kind, no fence, no conversion step and no new refusal +code. + +### Org files + +`system-type` leaves `KINDS`. An org file that still names it, even with an +empty list, refuses to load as an unknown kind: `checkPlugins` refuses +`unknown-kind` (`packages/plugins/src/kinds.mjs:146-148`, run at +`packages/organization/src/organization-config.mjs:358`), as it does for any +other name not in `KINDS`. Nothing is carried over, since the old modules +carried nothing. The one deployed system, the dragon-lin dev environment, is +wiped and reconfigured (the ruling above). + +### `project_system_types` and the bus schema step + +A new bus schema step `3v-3w` drops the table: + +- `packages/bus/src/migrations/3v-3w.sql` (new) drops the four triggers + `project_system_types_no_update`, `project_system_types_no_delete`, + `project_system_types_no_replace` and `project_system_types_business`, + the index `project_system_types_project`, and the table, all of which + `3t-3u.sql` created. Its rows go with it. This is the one deletion of + append-only rows in this go, and the ruling above is the authority for + it. SQLite removes the table's `sqlite_sequence` row when it drops the + table. +- `packages/bus/src/store.mjs`: `SCHEMA_VERSION` `'3v'` to `'3w'` (line + 17); a `{from: '3v', to: '3w'}` entry after the `3u-3v` step + (lines 121-122); `project_system_types` out of `timedTables` + (lines 124-150); `SCHEMA_SHA256` (line 16) recomputed, because + `packages/bus/schema.sql` loses the same DDL and line 153 refuses a + mismatch (`schema-source-mismatch`). + +The pins the step moves: + +- `packages/bus/tests/schema.test.mjs:533-534`: the table count + (`count('table') - 1`) goes from 33 to 32 and the trigger count from 130 + to 126. The view count stays 5. The comment at line 532 gains the + 3v to 3w line. +- The restore-guard lists: `COUNTED` in `scripts/stack-release.sh:443` and + `LISTED` in `packages/cli/tests/stack-release.test.mjs:888` lose the + table, so T45 and T46 (`stack-release.test.mjs:940`) still find every + table in exactly one list. The two subtests that use it as their sample + table (`stack-release.test.mjs:906,928`) take another listed table. +- Every test that asserts `'3v'` or edits the `SCHEMA_VERSION = '3v'` line: + 20 files today (`grep -rlE "'3v'" packages/*/tests`), the same set the + `3u-3v` step changed in `9b7bf11af`. +- The tests that cut each step's text out of `schema.sql` to build an older + fixture (`request-intake.test.mjs:282,320`, + `request-console.test.mjs:313-316`, `request-continuation.test.mjs:234`, + `prd-activation.test.mjs:202`) and the table lists that name it + (`account-migration.test.mjs:22`, `grant-migration.test.mjs:25`, + `gitea-migration.test.mjs:39`, `session-clear.test.mjs:535`, + `thread-shares.test.mjs:411`). Once `schema.sql` lacks the `3t-3u` text, + cutting it is a no-op and the replayed chain (`3t-3u`, then `3v-3w`) still + ends at the fresh digest. SP9 removes the dead cuts and the list entries. + +### Code removed + +- `packages/plugins/src/kinds.mjs:84-86` (the kind), + `packages/plugins/src/system-types.mjs`, and + `packages/plugins/system-type/` (the five modules; SP2 reuses their ids, + labels and descriptions as text, nothing else). +- The module check at `organization-config.mjs:360-365`; `systemTypesFence` + and its two calls (`packages/organization/src/organization.mjs:126-131,320,332`) + with its refusal `organization-system-types-human-only`. +- `'system-types.console'` in `CONSOLE_VERBS` (`packages/bus/src/broker.mjs:181`) + and in `VERB_RIGHTS` (`packages/organization/src/org-rights.mjs:90`), its + handler (`broker.mjs:6112-6130`), `project.system-types[.set]` + (`broker.mjs:8476-8495`), and the `'system-type'` half of the tester test + at `broker.mjs:6263` (the `model-provider` half stays). +- `system-types.installed` and `system-types.current` with the + `installedSystemTypes` import (`packages/bus/src/runtime.mjs:30,903-904`). +- `system-type-not-enabled` from `packages/bus/src/refusals.mjs:183`. +- CLI words `mosaic org system-types installed|enabled` + (`packages/cli/src/org.mjs:5,15,49,51`), `mosaic project system-types` + and `system-types-set` (`packages/cli/src/project.mjs:17-18,33-34,50-58`), + and their entries in the `project` dispatch list + (`packages/cli/src/cli.mjs:781`). +- The channel verbs `project.system-types` and `project.system-types.set` + (`packages/plugins/channel/cli/index.mjs:9-10`). +- Console: `packages/webui/src/public/system-types.js` and + `system-types.css`, `packages/webui/src/system-types-http.mjs`, and the + hooks in `public/workspace.js:5,173,193-194`, `public/chat.js:623,691`, + `public/index.html:10`, `public/bus.js:1926` (a comment), + `src/serve.mjs:23,85-86,826,945,997,1068-1073` and + `src/cli.mjs:9,193`. +- Tests: `system-type.test.mjs` in `packages/bus`, `packages/cli`, + `packages/organization` and `packages/plugins`; + `packages/webui/tests/system-types-browser.test.mjs`, + `system-types-http.test.mjs` and `helpers/system-types-fake.mjs`; and the + system-type cases in the other test files that + `grep -rlnE "system-type|systemType|system_type" packages` lists. +- Docs: the system-type text in `packages/cli/README.md`, + `packages/organization/README.md` and `packages/plugins/README.md`; + REQ-SYS-1 and REQ-SYS-2 (next subsection) and their titles in + `packages/tasks/requirements.json:73-74`. + +### REQ-SYS-1 and REQ-SYS-2 + +Current (PRD 1.20, `docs/prd/mosaic-stack.md:1247-1252`): + +- REQ-SYS-1: "System types are a plug-in kind. Its modules include coding, + creative writing, marketing, Mosaic Montage (the OpenMontage fork) and + business management. An organization enables a list of them." +- REQ-SYS-2: "A project may use any system type its organization enables, and + none is required. An agent working in a project gets the toolsets of the + system types that project uses." + +Proposed: + +- **REQ-SYS-1.** Starter packs are a plug-in kind. Mosaic Stack ships five: + coding, creative writing, marketing, Mosaic Montage and business + management. A pack is a starting configuration of role definitions, + proposed hires, a board style and workflow templates. An organization or + project copies a pack in once and then owns and changes what it copied. A + user can save an organization or project as their own template and apply it + the same way. +- **REQ-SYS-2.** Applying a pack grants nothing by itself. Every agent it + proposes is hired through the hire decision (REQ-AGENT-3), bounded by the + chief's own role, with any line above it approved by the owner. A pack never + sets credentials, rights, the autonomy setting or plug-in choices. Removing + a pack removes nothing it copied. + +## 7. The five shipped packs + +Every organization already has a chief and an assistant from +`org-template.json`, so no pack lists them. Columns map onto the five fixed +states in the order todo, in-progress, in-review, blocked, done. Every role +named is a shipped V2 template. + +**What each proposed hire carries against the chief.** The chief holds tools +`read, grep, find, ls`, network `api-only`, and gitea `read:repository, +read:user` (`roles/chief-of-staff.json`). `checkComposition` flags every line +above that (step 7) and every resolve right (step 5). Under `authorizeHire` +any flagged or credential line sends the hire to the owner. That is the rule +today for any hire of these roles; packs add no gate. The lines below are +tools, actions, resolve rights and credentials read from `roles/*.json`. +Messaging lines also count; SP2 checks in the exact broker-computed list. + +| Proposal | Flagged lines | Credential lines | Who approves | +|---|---|---|---| +| `pm` | tools `write, edit`; actions `task.create, task.assign, task.reassign, task.schedule, task.update.assigned, task.close`; crossRole `task.priority.change, task.scope.change`; resolve `decision.resolve.delivery` | gitea `write:issue`; vikunja task scopes | owner | +| `coder` | tools `write, edit, bash`; actions `task.update.assigned, git.push.working, review.request`; crossRole `task.scope.change` | gitea `write:issue, write:repository`; vikunja task scopes | owner | +| `reviewer` | tool `bash`; actions `review.verdict, task.update.assigned` | gitea `write:issue, write:repository`; vikunja task scopes | owner | +| `research` | tool `web`; action `library.write` | gitea `write:repository` | owner | +| `ideas`, `proofreader`, `continuity` | action `library.write` | gitea `write:repository` | owner | +| `chief-marketing-officer` | none found | none | plain: by the authority autonomy level | + +### Coding + +| Item | Contents | +|---|---| +| Definitions | `coder`, `reviewer`, `security-reviewer`, `pm`, `merge-gate`, `cto` (template references) | +| Proposed hires | `pm-1` (pm), `coder-1` (coder), `reviewer-1` (reviewer) | +| Board | Backlog, Building, Review, Blocked, Merged; kanban | +| Workflows | `change`: pm writes the task, coder implements, reviewer reviews, the user (or a hired merge-gate) approves the merge (`roles: ["human", "merge-gate"]`) | +| Skills | ms-sdlc, ms-unslop, ms-file-read, ms-file-write, ms-goal, ms-communications | + +`cto`, `security-reviewer` and `merge-gate` are definitions to hire when the +team grows, not first proposals. The chief is the default technical arbiter +(`org-template.json` `arbiters`), so a new team does not need a `cto` first. + +### Creative writing + +| Item | Contents | +|---|---| +| Definitions | `ideas`, `research`, `proofreader`, `fact-checker`, `continuity`, `companion` | +| Proposed hires | `ideas-1` (ideas), `proofreader-1` (proofreader), `continuity-1` (continuity) | +| Board | Ideas, Drafting, Editing, On hold, Finished; kanban | +| Workflows | `chapter`: ideas outlines, the user or companion drafts (`roles: ["human", "companion"]`), continuity checks, proofreader edits | +| Skills | ms-unslop | + +### Marketing + +| Item | Contents | +|---|---| +| Definitions | `chief-marketing-officer`, `research`, `ideas`, `proofreader`, `fact-checker` | +| Proposed hires | `cmo` (chief-marketing-officer), `research-1` (research), `proofreader-1` (proofreader) | +| Board | Ideas, In progress, Approval, Blocked, Live; kanban | +| Workflows | `campaign`: research finds the angle, ideas drafts, fact-checker checks claims, proofreader edits, the user approves publishing (`roles: ["human"]`) | +| Skills | ms-unslop | + +Publishing outside the organization is `message.external` or a hard wait; no +role here holds it, so the user publishes. + +### Mosaic Montage + +| Item | Contents | +|---|---| +| Definitions | `ideas`, `research`, `proofreader`, `reviewer` | +| Proposed hires | `ideas-1` (ideas), `research-1` (research) | +| Board | Concept, Script, Review, Blocked, Final; kanban | +| Workflows | `episode`: research gathers sources, ideas writes the script, proofreader edits, reviewer checks | +| Skills | ms-unslop | + +This pack is a placeholder for scripting work. No video or montage role or +tool exists in the repository, and the OpenMontage integration was not found. +The module name is still open (PRD 1.20 open question 1). + +### Business management + +| Item | Contents | +|---|---| +| Definitions | `chief-executive-officer`, `chief-financial-officer`, `chief-information-officer`, `chief-information-security-officer`, `pm`, `research`, `continuity` | +| Proposed hires | `pm-1` (pm), `research-1` (research) | +| Board | To do, Doing, Waiting on review, Blocked, Done; list | +| Workflows | `decision-brief`: research gathers facts, pm writes the task, the relevant officer drafts (`roles` lists the four officers), the user decides (`roles: ["human"]`) | +| Skills | ms-unslop, ms-goal, ms-communications | + +The officer roles hold only `message.send` and `record.write` today +(`roles/chief-*.json`). They are definitions to hire when wanted, not +proposals. The workflow's "relevant officer" step lists all four in its +`roles`. + +## 8. Build slices + +The slices run in order, SP1 to SP9, each landing with its own tests and +the full suites green. One go from Jason covers all nine (his "full send", +2026-11-12 about 21:05Z, with the section 9 answers). Nothing in a slice +waits on a further design choice, a release, or data on a live instance. + +**What the go covers.** No slice edits a file under `roles/` or `contracts/`. +The repository has no separate schema files; the schemas are the validators +named below, and every change to one is listed in its slice. The changes +that need sign-off are these: + +- a new plug-in kind and a new `switch` value (SP1); +- new org file keys `packs`, `board` and `workflows`, and project keys + `board` and `workflows` (SP3); +- new verbs in `VERB_RIGHTS` (`packages/organization/src/org-rights.mjs:34`) + and `CONSOLE_VERBS` (`packages/bus/src/broker.mjs:181`): + `packs.console`, `pack.preview.console`, `pack.apply.console`, + `board.set.console`, `workflow.set.console` (SP4), and + `template.save.console`, `template.list.console` (SP8). Every write verb + is owner only (`null`) until row 193; +- human CLI verbs `packs`, `pack.preview`, `pack.apply`, `board.set`, + `workflow.set` (SP4) and `template.save`, `template.list` (SP8), for the + organization's human only until row 193; +- agent verbs `packs`, `pack.preview`, `pack.apply`, `board.set` and + `workflow.set` under `organization.change` (SP5); +- `hire.raise` gains an optional `proposal` argument (SP5); +- new text in agents' generated prompts (SP7); +- data leaving an organization into a file (SP8); +- a PRD wording change, the `system-type` kind and its verbs, routes, CLI + words and Console page removed, and a bus schema step `3v-3w` that drops + `project_system_types` with its rows (SP9). + +New refusal codes are added to `packages/bus/src/refusals.mjs` by the slice +that first uses them: `starter-pack-invalid`, `starter-pack-not-found`, +`starter-pack-template-changed` (SP1); `pack-plan-stale` (SP4); +`starter-pack-proposal-stale` (SP5); `template-secret-suspected` (SP8). +SP9 removes `system-type-not-enabled` and +`organization-system-types-human-only`. + +**Owner boundary.** "Owns" lists the files a slice may change. Where a +shared file is listed with a scope in brackets, the slice changes only those +lines. A slice that needs a change outside its list stops and reports; it +does not widen itself. Tests for a slice live beside the files it owns. + +### SP1: the pack kind and its checker + +- Owns: `packages/plugins/src/starter-pack.mjs` (new), + `packages/plugins/src/kinds.mjs`, `packages/bus/src/refusals.mjs` (the + three SP1 codes), `packages/plugins/tests/starter-pack.test.mjs` (new). +- Schema change: `KINDS` gains `"starter-pack"` with `interfaceVersion: 1`, + `exports: ["starterPack"]` and a new `switch: "none"`. `namesFor` refuses + any setting for a `none` kind with `bad-setting`, so an org file can never + switch a pack on through `plugins`. This touches nothing else: `switch` is + read only in `namesFor` (`kinds.mjs:123,128`), and the loader starts each + kind at `names = []` and calls `namesFor` only when the key is present + (`loader.mjs:190-195`), so a `none` kind loads no names and `loader.mjs` + needs no change. +- Builds: `checkPack(doc, {origin})` and `narrowsTemplate(role, template)` + as section 2 states; `installedStarterPacks({dataRoot})`, which lists + shipped and installed packs through `loadInstalled` and sets the origin; + `readSavedTemplate({configDir, account, name})` with every file check in + section 2 (the account, from the caller's `s.human`, against `ID_PATTERN` + and the name against `NAME` first, `lstat`, no symlink, owner uid, mode + 0600 or narrower, 256 KiB checked before the read), then + `parseJsonFileText` and `checkPack` with origin `saved`. +- Acceptance: a valid pack loads. Each of these refuses with its code: a + forbidden key, a stale template hash, a role wider than its template, a + credential line wider than its template, a root hire, a ninth hire, a hire + naming no pack definition, a bad state, a duplicate column. A shipped or + installed pack with a `packs` key or a role without `from` refuses; a + saved one with either passes. A saved `packs` list with a 33rd entry, an + entry with a key other than `id` and `version`, or a version that is not + semver refuses. `readSavedTemplate` refuses a name of `..`, a name holding + `/`, an account holding `/` or starting with `.`, a symlink, a 0644 file + and a 300 KiB file, and a missing file refuses `starter-pack-not-found`; + an account holding a dot, an underscore or a leading digit, which + `ID_PATTERN` allows, reads its own file. +- Red before: `checkPack` refuses a pack whose definition carries + `copyBindings` with `starter-pack-invalid` (today the module does not + exist); `plugins: {"starter-pack": ["coding"]}` refuses `bad-setting` + (today `checkPlugins` refuses `unknown-kind`, `kinds.mjs:146-148`, so the + assertion fails). + +### SP2: the five shipped packs + +- Owns: `packages/plugins/starter-pack/{coding,creative-writing,marketing,montage,business-management}/` + (new: `index.mjs` and `expected-lines.json` each), + `packages/plugins/tests/starter-pack-shipped.test.mjs` (new). +- Schema change: none. +- Builds: the five documents in section 7, each pinned to today's + `templateHash` values. +- Acceptance: each loads through SP1 with origin `shipped`. For each + proposed hire, the flagged and credential lines `checkComposition` + computes against `roles/chief-of-staff.json` equal `expected-lines.json`; + `chief-marketing-officer` is the only proposal with none. A template edit + that moves a hash turns this test red until the pack is re-pinned, which + is the intended review point. +- Red before: loading `starter-pack/coding` finds no module. + +### SP3: org file keys and the writer op + +- Owns: `packages/organization/src/organization-config.mjs`, + `packages/organization/src/organization.mjs` (`applyPack` only), their + tests. +- Schema change: `TOP_KEYS` (`organization-config.mjs:32`) gains `packs`, + `board` and `workflows`. The project entry keys (`organization-config.mjs:306`) + gain `board` and `workflows`. Shapes, each refusing unknown keys: + - `board`: `{columns: [{state, label}] x5, layout}`, all five + `STORE_STATES` once, labels 1 to 32 characters, layout `kanban` or + `list`; + - `workflows`: `{: {title, description, steps: [{title, state, roles}]}}`, + names matching `NAME`, 1 to 20 steps, titles up to 120 characters, + description up to 1,000, each step's `roles` 1 to 8 names. The reader + checks only shape: each step role matches `NAME` or is `human`. + `applyPack` checks at write time that each name is an org definition, a + V2 template or `human`, and refuses `invalid-request` otherwise. A name + that later stops resolving stays in the file. The prompt renders it as + written, and preview and the workflow editor mark it "names no current + role". So removing a definition or a template never stops an org file + from loading over guidance text; + - `packs`: a list of `{id, version, sha256, origin, scope, project, at, by, added, proposedHires}`, + origin one of `shipped`, `installed`, `saved`. An inline + `proposedHires` entry stores the full role entry beside its + `{inline, sha256}` pin, so the SP5 raise can read it. +- Builds: + - the reader keeps `packs`, `board` and `workflows` on the loaded + organization object, and `board` and `workflows` on each `projects[id]`. + Today the copy after `organization-config.mjs:306` keeps only `root`, + `repo`, `remote` and `git`, so widening the key list alone would load + the keys and then lose them; + - `applyPack(id, plan, {revision, actor, assertClean})` on the writer with + the rules in section 3: one commit, `rev: 1` on each added definition, + `checkDefinition` with `assertClean`, `definition-in-use` as + `setDefinition` refuses it, `human-required` when an agent actor writes + a definition. Plan keys are limited to `definitions`, `board`, + `workflows`, `projects.

.board`, `projects.

.workflows` and + `packs`; any other refuses. + - A board write or a workflow write is an `applyPack` call whose plan + holds only that key and no `packs` entry. The generic `write()` cannot + carry it, because it refuses any change under `projects` + (`projectsProtected`, `organization.mjs:383-391`). +- Acceptance: valid shapes load and each invalid one refuses; the loaded + object and each `projects[id]` keep `board` and `workflows`; `applyPack` + writes everything or nothing (a fault injected after the definitions + leaves the file unchanged); an agent actor with a definition in the plan + refuses `human-required`; a plan holding `plugins`, `roles`, `limits` or + `credentials` refuses; a board-only plan changes only `board`; a workflow + step naming an unknown role refuses `invalid-request` at `applyPack`, + while an org file whose step names a definition removed since loads, and + removing that definition succeeds. +- Red before: a file with a `board` key refuses today as an unknown top key + (`organization-config.mjs:259`); the test expects it to load. + +### SP4: preview, apply, board and workflow writes for people + +- Owns: `packages/bus/src/broker.mjs` (the five SP4 `.console` verbs, and + the unsuffixed `packs`, `pack.preview`, `pack.apply`, `board.set` and + `workflow.set` for the organization's human only, gated as the definition + writes are at `broker.mjs:6305-6308`), + `packages/bus/src/runtime.mjs` (the new `packs`, `pack.preview`, + `pack.apply`, `board.set` and `workflow.set` ops only), + `packages/bus/src/refusals.mjs` (`pack-plan-stale`), + `packages/organization/src/org-rights.mjs`, `packages/cli/src/org.mjs` + (the `pack`, `board` and `workflow` words), `packages/webui/src/packs-http.mjs` + (new: the routes), `packages/webui/src/serve.mjs` (route hookup only), + `packages/webui/src/cli.mjs` (caller wiring only), their tests. +- Schema change: `VERB_RIGHTS` gains `'packs.console': 'board.read'`, + and `null` (owner only) for + `pack.preview.console`, `pack.apply.console`, `board.set.console` and + `workflow.set.console`. `CONSOLE_VERBS` gains `packs.console` and those + four. The unsuffixed verbs are not in `VERB_RIGHTS` (section 3). Owner + only matches the definition writes today (`broker.mjs:6221,6303-6308`); + when row 193 opens those to `org.hire`, the four `.console` writes and + the human unsuffixed verbs move with them. +- Builds: `mosaic org pack list|preview|apply`, `mosaic org board set`, + `mosaic org workflow set|remove`, calling the unsuffixed verbs; routes + `GET /api/packs`, `POST /api/orgs//packs/preview`, + `POST /api/orgs//packs/apply`, `POST /api/orgs//board`, + `POST /api/orgs//workflows/` and + `DELETE /api/orgs//workflows/`, calling the `.console` verbs; + `workflow.set` with `workflow: null` removes the workflow; + the merge table in section 3, including `kept`, `--as` and the per-pack + `removed` tombstone; the held-credential check at preview and at apply; + `saved:` through `readSavedTemplate` with the caller's own account. + Board and workflow writes call `applyPack` with a one-key plan (SP3). +- Acceptance: preview writes nothing and lists flagged lines, credential + lines and `noHire` marks; a pack string holding a held credential value + refuses at preview and at apply; apply writes only `add` items and the + `packs` entry with `added` and pinned `proposedHires`; a stale digest + refuses `pack-plan-stale`; a second apply of the same pack is all `same`; + an edited definition is `kept`; an item an earlier apply added and the + owner then removed is `removed`, while a different pack naming it adds + it; apply raises no decision; a non-owner member's `pack.apply.console`, + `board.set.console` and `workflow.set.console` refuse `human-required`; + owner B naming `saved:x` that only owner A saved gets + `starter-pack-not-found`; `packs.console` lists no saved template; + `board.set.console` changes only `board`; a workflow step naming an + unknown role refuses `invalid-request`; the owner's CLI + `mosaic org pack apply` writes the `packs` entry; a member session's + unsuffixed `pack.apply` refuses `human-required`; `mosaic org workflow + remove` and `DELETE /api/orgs//workflows/` remove only that + workflow. +- Red before: the owner calls `pack.apply.console` for `coding` and the + test expects a `packs` entry in the org file; today the verb is unknown. + +### SP5: the same writes for agents, and the hire pin check + +- Owns: `packages/bus/src/broker.mjs` (the five agent verbs and the hire + pin check only), `packages/bus/src/runtime.mjs` (the agent actor on the + SP4 ops only), `packages/bus/src/refusals.mjs` + (`starter-pack-proposal-stale`), their tests. +- Schema change: none to files. `hire.raise` gains the optional + `proposal` argument, which the broker strips before `prepare`. Authority: + agent verbs `packs`, + `pack.preview`, `pack.apply`, `board.set` and `workflow.set` for a role + whose file lists `organization.change`, the same test `organization.set` + uses (`broker.mjs:6247-6255`). A role without it gets + `decision-required`, as `organization.set` gives today + (`#classification`, `broker.mjs:4743`). +- Builds: SP5 extends the same unsuffixed verbs SP4 adds for the human to + a role whose file lists `organization.change`. The `packs` read is + allowed to an `organization.change` holder without spending the grant, + as `organization.revision` is (`broker.mjs:6253`); any other agent gets + `human-required`. The agent apply in section 3: no definitions + (`human-required` if the plan has one), origin `saved` refused with + `human-required`, workflow steps naming an unwritten definition left out + and marked in preview. The `proposal` argument on `hire.raise` and its + pin check, exactly as section 3 states: the broker fills `op`, `name`, + `level` and `role` from the recorded pin, refuses a caller-sent `role`, + `name` or `level`, checks the pin (`decision-stale`, + `definition-rev-conflict`, `starter-pack-proposal-stale`, + `definition-not-hireable`), passes `{inline: compositionOf(entry)}` for an + inline pin, and strips `proposal` before `prepare`. +- Acceptance: a coder without `organization.change` gets + `decision-required` for `pack.apply`, `board.set` and `workflow.set`, the + same as `packages/bus/tests/chief-of-staff.test.mjs:255-261` asserts for + `organization.set`; the chief's `board.set` and `workflow.set` write; an + agent apply with a definition in the plan refuses `human-required`; an + agent naming `saved:x` refuses `human-required`; a hire the chief raises + from a proposal carries the broker-computed lines; a non-chief raise + still refuses `role-not-chief`; a proposal whose pinned template moved + refuses `decision-stale`, one whose definition `rev` moved refuses + `definition-rev-conflict`, an inline one whose hash differs refuses + `starter-pack-proposal-stale`, and an inline one whose `from` is in + `launch.noHire` refuses `definition-not-hireable`; a raise with + `proposal` that also sends `role` refuses; a raise without `proposal` + behaves as today; a coder's `packs` refuses `human-required` and the + chief's `packs` lists shipped and installed packs. +- Red before: the chief calls `pack.apply` for `coding` and the test + expects the org file to gain its `board`; today the verb is unknown. + +### SP6: Console pack picker, board labels and workflow editor + +- Owns: `packages/webui/src/public/tasks-board.js`, a new + `packages/webui/src/public/packs.js`, a new + `packages/webui/src/public/workflows.js`, `packages/webui/src/public/workspace.js` + (the page hooks only), `packages/webui/src/workspace-read.mjs` (the + `board` field only), `packages/webui/src/packs-http.mjs` (the workflow + read route only), their tests. +- Schema change: none. The workspace read gains a `board` field per org. +- Builds: the server side first. `readOrg` already holds the org document + (`member.doc`, `workspace-read.mjs:140`), so the per-org head gains + `board` and each project row its project `board`, and a new + `GET /api/orgs//workflows` returns the org and project workflows from + the same document. It is owner only until row 193, as `packs.console` + is, and then `board.read`. Then the pages: pack picker, preview and apply through + SP4; labels and order from `board` when the view is filtered to one org or + project; `layout` as a default under the viewer's saved `localStorage` + choice; a workflow list and an editor that saves through + `workflow.set.console`. +- Acceptance: a view filtered to an org with a `board` shows its labels; the + all-orgs view keeps today's labels; a saved viewer layout wins over the + pack default; a label or workflow title holding `` + renders as text (`esc()`, `tasks-board.js:17`); a step naming a role that + no longer resolves shows "names no current role". +- Red before: a view filtered to an org with a `board` shows the fixed labels + (`tasks-board.js:21`). + +### SP7: workflow guidance in the prompt + +- Owns: `packages/organization/src/resolve.mjs` (`resolveInstance`, the + `workflows` field only), `packages/harness/src/bundle.mjs` (`prompt`, + line 271), their tests. +- Schema change: none to files. New prompt text, which is why it needs + sign-off. +- Builds: `resolveInstance` puts `workflows` on `resolved`: the org's, plus + the project's when it is given a project, the project's winning by name. + The launcher calls it with no project today (`cli/src/launcher.mjs:401`), + so this go delivers org workflows to prompts, and project workflows reach + a prompt only once a launch names its project; this slice does not change + the launcher. `prompt` renders the read-only workflows section in section + 2 for the chief, roles holding `task.create` or `task.assign`, and roles a + step names (their own steps only). Titles and descriptions appear as + quoted data under a fixed heading, never as headings of their own. The + section says workflows grant nothing and the contract and rails win. +- Acceptance: a chief bundle for an org with a workflow contains the + section; a role no step names and without those actions gets none; a + named role sees only its own steps; a description holding + `## Contract` stays inside the quoted block; the contract text is + byte-identical to before. +- Red before: a chief bundle for an org with a `change` workflow has no + workflows section. + +### SP8: saving a template + +- Owns: `packages/organization/src/template-save.mjs` (new), + `packages/bus/src/broker.mjs` (the two template `.console` verbs, and + the unsuffixed `template.save` and `template.list` for the + organization's human only), + `packages/bus/src/runtime.mjs` (the save and list ops only), + `packages/bus/src/refusals.mjs` (`template-secret-suspected`), + `packages/organization/src/org-rights.mjs`, `packages/cli/src/org.mjs` + (the `template save|list` words), `packages/webui/src/template-http.mjs` + (new), `packages/webui/src/serve.mjs` (route hookup only), + `packages/webui/src/cli.mjs` (caller wiring only), their tests. +- Schema change: `VERB_RIGHTS` gains `'template.save.console': null` and + `'template.list.console': null` (owner only); `CONSOLE_VERBS` gains both. + The saved file is a pack document read with origin `saved`; it adds no + new schema. +- Builds: capture, strip, placeholder and preview rules in section 5; the + file at `/templates//.json`, directory 0700, + file 0600, written with `wx`, the account (the caller's `s.human`) + checked against `ID_PATTERN` and the name against `NAME` first; the CLI + words call the unsuffixed verbs, the routes the `.console` ones; `packs` written as at most 32 `{id, version}` entries; save + refuses more than 8 hires and any role with a `from` that + `narrowsTemplate` refuses; `list` shows only the caller's own. +- Acceptance: the file has none of the stripped keys and keeps role + credential request lines; instance names, org and project ids, the owner's + account and project roots become placeholders; the preview lists every + kept free-text field; a seeded held credential value refuses + `template-secret-suspected` naming the field path; a second save to the + same name refuses; a name of `..` refuses; a ninth hire refuses; each + saved `packs` entry holds only `id` and `version`; a saved template + applies through SP4 for its saver; the owner's CLI + `mosaic org template save` writes the file, and a member session's + unsuffixed `template.save` refuses `human-required`. +- Red before: save an org whose instance has `credentials` and + `copyBindings` and assert neither appears in the output; the verb does not + exist today. + +### SP9: remove system types + +- Owns, each scoped to its system-type lines: + - `packages/plugins/src/kinds.mjs`; `packages/plugins/src/system-types.mjs` + and `packages/plugins/system-type/` (deleted); + - `packages/organization/src/organization-config.mjs` (lines 360-365) and + `packages/organization/src/organization.mjs` (`systemTypesFence` and + its calls); + - `packages/organization/src/org-rights.mjs` (`system-types.console`, + line 90); + - `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs` (the + import at line 30 and the reads at 903-904) and + `packages/bus/src/refusals.mjs` (`system-type-not-enabled` and + `organization-system-types-human-only` out); + - the bus schema step: `packages/bus/src/migrations/3v-3w.sql` (new), + `packages/bus/src/store.mjs` (`SCHEMA_VERSION`, `SCHEMA_SHA256`, the + step entry and `timedTables`), `packages/bus/schema.sql`, and + `scripts/stack-release.sh` (`COUNTED`, line 443); + - `packages/cli/src/org.mjs`, `packages/cli/src/project.mjs` and + `packages/cli/src/cli.mjs` (the dispatch list at line 781); + - `packages/plugins/channel/cli/index.mjs`; + - the Console files section 6 lists; + - `docs/prd/mosaic-stack.md` (REQ-SYS-1/2 only), + `packages/tasks/requirements.json` (lines 73-74), and the three + package READMEs section 6 lists; + - their tests, and every test file section 6 names for the schema pins. +- Schema change: `system-type` leaves `KINDS`. Bus schema step `3v-3w` + drops `project_system_types`, its index and its four triggers, and + `SCHEMA_VERSION` becomes `'3w'`. The pins it moves are section 6's: + `schema.test.mjs:533-534` (tables 33 to 32, triggers 130 to 126), + `COUNTED` and `LISTED` (`stack-release.sh:443`, + `stack-release.test.mjs:888`, with the samples at `:906,928`), the 20 + test files that pin `'3v'`, and the migration-chain cuts and table lists. + The PRD text is section 6's. +- Builds: the removals in section 6, and the step. +- Acceptance: an org file with `plugins: {"system-type": []}` refuses + `unknown-kind`; a `3v` store holding `project_system_types` rows opens, + migrates to `3w` and matches the fresh `schema.sql` digest, with no table, + index, trigger or `sqlite_sequence` row named `project_system_types` + left; the schema counts are 32 tables and 126 triggers; T45 and T46 + pass; no system-type verb, route, CLI word, channel verb or Console page + is left, and + `grep -rlnE "system-type|systemType|system_type|system-types" packages scripts` + finds only `packages/bus/src/migrations/3t-3u.sql`, `3v-3w.sql`, and the + tests that replay or check them. +- Red before: an org file with `plugins: {"system-type": ["coding"]}` + refuses `unknown-kind` (today it loads, since `coding` is a shipped + module); a store opened at `SCHEMA_VERSION` `'3w'` has no + `project_system_types` table (today the version is `'3v'` and the table + exists). + +## 9. Answered + +Jason answered on 2026-11-12, about 21:05Z, with "full send". The slices +above follow his answers. + +1. **Retire system types as a separate kind?** Remove it outright: + + > The only deployed system is the dev environment on dragon-lin. It can + > be wiped and reconfigured. No reason to hold on to legacy for this + > change. + + Section 6 and SP9 remove the kind, its modules, verbs, routes, refusals, + reads, CLI words, channel verbs and Console surfaces, and drop + `project_system_types` with bus schema step `3v-3w`. An org file naming + the kind refuses as an unknown kind. +2. **May the chief change board styles and workflows directly, as it changes + limits today?** Yes, as written. `board.set`, `workflow.set` and the + non-definition half of `pack.apply` sit under the existing + `organization.change` grant, with no decision. Any role listing + `organization.change` gets the same writes; a role without it gets + `decision-required`, as `organization.set` gives today. Role and hire + changes stay decisions, and only the chief raises hires. +3. **Board styles as labels and order over the five fixed states, or custom + states?** Yes, as written: labels, order and layout over the five fixed + states. Custom states would change the task tracker contract, every + tracker module and the Vikunja bucket setup, and wait until a pack needs + one.