docs(plans): starter packs fix round 3, system types removed outright

This commit was merged in pull request #1949.
This commit is contained in:
jason.woltje committed 2026-11-12 15:18:16 -06:00
1 parent dd878cefcf
commit c0a60ed3ae
1 file changed
+292 -164
+292 -164
View File
@@ -105,7 +105,8 @@ returns the document.
Shipped and installed packs follow the loader exactly: the same component and Shipped and installed packs follow the loader exactly: the same component and
symlink checks, the same refusal codes, a refused installed pack omitted and symlink checks, the same refusal codes, a refused installed pack omitted and
logged, a refused core pack failing the listing as system types do today. 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` An installed pack is executable PL1 code. The loader imports its `index.mjs`
(`loadPinnedModule`, `packages/plugins/src/loader.mjs:244-256`), and a loaded (`loadPinnedModule`, `packages/plugins/src/loader.mjs:244-256`), and a loaded
@@ -123,9 +124,13 @@ A saved template is read, never imported or run. `parseJsonFileText`
(`packages/organization/src/util.mjs:66`) is a bare `JSON.parse` with no file (`packages/organization/src/util.mjs:66`) is a bare `JSON.parse` with no file
checks, so `readSavedTemplate` does them first: checks, so `readSavedTemplate` does them first:
- the account and the template name each match `NAME` - the account is the calling session's `s.human`, which the owner-only
(`packages/plugins/src/kinds.mjs:20`) before any path is built, so `..` verbs already require to equal the organization's `human`. It is checked
and `/` never reach the file system; 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 `<configDir>/templates/<caller account>/`; - the path resolves only under `<configDir>/templates/<caller account>/`;
`saved:<name>` never names another account's file; `saved:<name>` never names another account's file;
- `lstat` refuses a symlink, a file not owned by the instance's uid, and a - `lstat` refuses a symlink, a file not owned by the instance's uid, and a
@@ -348,8 +353,11 @@ exactly as if a person had made them by hand. The steps:
re-apply table never meets half-applied items without a `packs` record. 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 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 `packs` entry. Each becomes a hire through the existing paths:
- the chief raises `hire.raise` (`broker.mjs:6237`) with role - the chief raises `hire.raise` (`broker.mjs:6237`) with the proposal
`{definition, rev}` and a route it chooses; the broker computes the 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 flagged and credential lines, and the decision routes by the authority
autonomy level; autonomy level;
- or a person with `org.hire` starts it with `hire.start.console`, which - or a person with `org.hire` starts it with `hire.start.console`, which
@@ -371,9 +379,11 @@ exactly as if a person had made them by hand. The steps:
| Layer | Name | | 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]` | | 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/<id>/packs/preview`, `POST /api/orgs/<id>/packs/apply`, `POST /api/orgs/<id>/board`, `POST /api/orgs/<id>/workflows/<name>` | | Console route | `GET /api/packs`, `POST /api/orgs/<id>/packs/preview`, `POST /api/orgs/<id>/packs/apply`, `POST /api/orgs/<id>/board`, `POST /api/orgs/<id>/workflows/<name>`, `DELETE /api/orgs/<id>/workflows/<name>` |
| Bus ops | `packs.console` and, for agents, `packs` (reads), `pack.preview.console`, `pack.apply.console`, `board.set.console`, `workflow.set.console`; and for agents, `pack.preview`, `pack.apply`, `board.set` and `workflow.set` for a role whose file lists `organization.change` | | Bus ops, Console | `packs.console` (read), `pack.preview.console`, `pack.apply.console`, `board.set.console`, `workflow.set.console` |
| `VERB_RIGHTS` | `packs` and `packs.console` to `board.read`. `pack.preview.console`, `pack.apply.console`, `board.set.console` and `workflow.set.console` to `null` (owner only) | | 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 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 writes. `VERB_RIGHTS` maps the T1 definition verbs to `org.hire`, but the
@@ -420,16 +430,25 @@ What it does:
marks such a proposal when its `from` template is in `launch.noHire`. The 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 broker computes the lines against the chief either way, and the approved
hire writes `hire-<decision>` as it does now. hire writes `hire-<decision>` as it does now.
- the pin recorded in `proposedHires` is checked again when the chief raises - the chief raises a proposal with `hire.raise {proposal: {pack, project?,
the hire, so a proposal never resolves to text nobody reviewed. A template hire}, route, reason}`. The broker reads the matching
pin that no longer matches refuses `decision-stale` and a definition whose `packs[].proposedHires` entry from the loaded organization (SP3 keeps
`rev` moved refuses `definition-rev-conflict`, both existing checks `packs`). It fills `op: "hire"`, `name`, `level` and `role` from the
(`hire-decisions.mjs:225-226`). An inline entry whose hash differs from the recorded pin and refuses any `role`, `name` or `level` the caller also
recorded one refuses the new `starter-pack-proposal-stale`. For an inline sends. Then it checks the pin: a `{template, sha256}` whose `templateHash`
entry the broker also refuses at raise when the entry's `from` template is moved refuses `decision-stale`; a `{definition, rev}` whose entry moved
in `launch.noHire`, with `definition-not-hireable`, the code refuses `definition-rev-conflict`; an inline proposal reads the full entry
`hire-decisions.mjs:219` gives for a template, since the `{inline}` path stored in the `packs` entry, refuses `starter-pack-proposal-stale` if its
itself skips that check. 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 ### A second pack, and re-applying
@@ -485,7 +504,7 @@ and change decisions stay chief-only.
| 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. | | 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. | | 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. | | 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` | Steps name existing definitions, templates or `human`, and the five states. A workflow grants nothing; agents read it as guidance below the contract. | | 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. | | 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`). | | 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. | | Enable a skill on the machine | `scripts/skill.sh activate` (instance owner) | Not available | Machine-scoped. |
@@ -614,9 +633,14 @@ There is no create-time shortcut that skips the hire decisions.
## 6. System types ## 6. System types
**Decision: the kind is replaced.** The `system-type` kind becomes **Decision: removed outright.** Jason, 2026-11-12, about 21:05Z, answering
`starter-pack`. A pack's `label` is what the UI shows as the system type. The section 9 question 1:
reasons:
> 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 - 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 every module's `toolsets` is empty. A list switch that grants nothing has no
@@ -624,39 +648,102 @@ reasons:
- Keeping both would leave a second, empty configuration surface next to the - Keeping both would leave a second, empty configuration surface next to the
real one. real one.
### Migration There is no retired kind, no fence, no conversion step and no new refusal
code.
- **Org files, in two steps.** Removing the kind outright would brick every ### Org files
org file that names it: `checkPlugins` refuses an unknown kind, even with an
empty list (`unknown-kind`, `kinds.mjs:146-148`, run at `system-type` leaves `KINDS`. An org file that still names it, even with an
`organization-config.mjs:358`). An organization would stop loading until empty list, refuses to load as an unknown kind: `checkPlugins` refuses
its owner converted the file by hand, which is an outage and a forced `unknown-kind` (`packages/plugins/src/kinds.mjs:146-148`, run at
human step. So: `packages/organization/src/organization-config.mjs:358`), as it does for any
1. For one release `system-type` stays in `KINDS` as a retired kind. The other name not in `KINDS`. Nothing is carried over, since the old modules
reader accepts the key and ignores it. A write that adds a name refuses carried nothing. The one deployed system, the dragon-lin dev environment, is
`verb-retired` (a new code). The next writer commit for that organization, by anyone, wiped and reconfigured (the ruling above).
drops the key, and its trail event records the old list as evidence.
Nothing is applied for it, since the old modules carried nothing. The ### `project_system_types` and the bus schema step
Console offers the matching packs once.
2. A print-only conversion in the style of `migrateLaunch` A new bus schema step `3v-3w` drops the table:
(`packages/organization/src/migrate-launch.mjs`, decision 145) covers a
file that is never written in between; it is built in this go (SP10). - `packages/bus/src/migrations/3v-3w.sql` (new) drops the four triggers
Removing the kind from `KINDS` is not in this go. It is a follow-up row `project_system_types_no_update`, `project_system_types_no_delete`,
with its own go, after one release, once each instance's owner has run `project_system_types_no_replace` and `project_system_types_business`,
the conversion or every org file has been written. A build session the index `project_system_types_project`, and the table, all of which
cannot see other instances' org files, so it cannot make that check. `3t-3u.sql` created. Its rows go with it. This is the one deletion of
- **`project_system_types`.** The table stays, rows and triggers unchanged append-only rows in this go, and the ruling above is the authority for
(write-once, R2). `project.system-types.set` refuses with a new code, it. SQLite removes the table's `sqlite_sequence` row when it drops the
`verb-retired` (not found today). The read verb keeps working for history table.
until a later PRD removes it. No DDL change. - `packages/bus/src/store.mjs`: `SCHEMA_VERSION` `'3v'` to `'3w'` (line
- **Code removed.** `systemTypesFence`, `system-types.console`, the system 17); a `{from: '3v', to: '3w'}` entry after the `3u-3v` step
types Console route and page, the CLI words (`mosaic org system-types`, (lines 121-122); `project_system_types` out of `timedTables`
`mosaic project system-types`), the channel verbs, (lines 124-150); `SCHEMA_SHA256` (line 16) recomputed, because
`system-type-not-enabled`, `installedSystemTypes` and its bus reads, each `packages/bus/schema.sql` loses the same DDL and line 153 refuses a
with its tests (section 1's three system-type rows list every file). mismatch (`schema-source-mismatch`).
`systemTypesFence` is replaced by a fence that lets any writer drop the
key and refuses any write that adds or changes a name (`verb-retired`). The five modules move to The pins the step moves:
`packages/plugins/starter-pack/`.
- `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 ### REQ-SYS-1 and REQ-SYS-2
@@ -777,10 +864,10 @@ proposals. The workflow's "relevant officer" step lists all four in its
## 8. Build slices ## 8. Build slices
The slices run in order, SP1 to SP10, each landing with its own tests and 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 ten. Nothing in a slice 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. waits on a further design choice, a release, or data on a live instance.
Removing `system-type` from `KINDS` is not in this go (section 6, step 2).
**What the go covers.** No slice edits a file under `roles/` or `contracts/`. **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 The repository has no separate schema files; the schemas are the validators
@@ -791,22 +878,29 @@ that need sign-off are these:
- new org file keys `packs`, `board` and `workflows`, and project keys - new org file keys `packs`, `board` and `workflows`, and project keys
`board` and `workflows` (SP3); `board` and `workflows` (SP3);
- new verbs in `VERB_RIGHTS` (`packages/organization/src/org-rights.mjs:34`) - new verbs in `VERB_RIGHTS` (`packages/organization/src/org-rights.mjs:34`)
and `CONSOLE_VERBS` (`packages/bus/src/broker.mjs:181`): `packs`, and `CONSOLE_VERBS` (`packages/bus/src/broker.mjs:181`):
`packs.console`, `pack.preview.console`, `pack.apply.console`, `packs.console`, `pack.preview.console`, `pack.apply.console`,
`board.set.console`, `workflow.set.console` (SP4), and `board.set.console`, `workflow.set.console` (SP4), and
`template.save.console`, `template.list.console` (SP8). Every write verb `template.save.console`, `template.list.console` (SP8). Every write verb
is owner only (`null`) until row 193; 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 - agent verbs `packs`, `pack.preview`, `pack.apply`, `board.set` and
`workflow.set` under `organization.change` (SP5); `workflow.set` under `organization.change` (SP5);
- `hire.raise` gains an optional `proposal` argument (SP5);
- new text in agents' generated prompts (SP7); - new text in agents' generated prompts (SP7);
- data leaving an organization into a file (SP8); - data leaving an organization into a file (SP8);
- a PRD wording change, a retired kind and a changed fence (SP9). - 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 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`, that first uses them: `starter-pack-invalid`, `starter-pack-not-found`,
`starter-pack-template-changed` (SP1); `pack-plan-stale` (SP4); `starter-pack-template-changed` (SP1); `pack-plan-stale` (SP4);
`starter-pack-proposal-stale` (SP5); `template-secret-suspected` (SP8); `starter-pack-proposal-stale` (SP5); `template-secret-suspected` (SP8).
`verb-retired` (SP9). SP9 removes `system-type-not-enabled` and
`organization-system-types-human-only`.
**Owner boundary.** "Owns" lists the files a slice may change. Where a **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 shared file is listed with a scope in brackets, the slice changes only those
@@ -830,7 +924,8 @@ does not widen itself. Tests for a slice live beside the files it owns.
as section 2 states; `installedStarterPacks({dataRoot})`, which lists as section 2 states; `installedStarterPacks({dataRoot})`, which lists
shipped and installed packs through `loadInstalled` and sets the origin; shipped and installed packs through `loadInstalled` and sets the origin;
`readSavedTemplate({configDir, account, name})` with every file check in `readSavedTemplate({configDir, account, name})` with every file check in
section 2 (names against `NAME` first, `lstat`, no symlink, owner uid, mode 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 0600 or narrower, 256 KiB checked before the read), then
`parseJsonFileText` and `checkPack` with origin `saved`. `parseJsonFileText` and `checkPack` with origin `saved`.
- Acceptance: a valid pack loads. Each of these refuses with its code: a - Acceptance: a valid pack loads. Each of these refuses with its code: a
@@ -841,8 +936,10 @@ does not widen itself. Tests for a slice live beside the files it owns.
saved one with either passes. A saved `packs` list with a 33rd entry, an 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 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 semver refuses. `readSavedTemplate` refuses a name of `..`, a name holding
`/`, a symlink, a 0644 file and a 300 KiB file, and a missing file `/`, an account holding `/` or starting with `.`, a symlink, a 0644 file
refuses `starter-pack-not-found`. 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 - Red before: `checkPack` refuses a pack whose definition carries
`copyBindings` with `starter-pack-invalid` (today the module does not `copyBindings` with `starter-pack-invalid` (today the module does not
exist); `plugins: {"starter-pack": ["coding"]}` refuses `bad-setting` exist); `plugins: {"starter-pack": ["coding"]}` refuses `bad-setting`
@@ -878,10 +975,18 @@ does not widen itself. Tests for a slice live beside the files it owns.
`list`; `list`;
- `workflows`: `{<name>: {title, description, steps: [{title, state, roles}]}}`, - `workflows`: `{<name>: {title, description, steps: [{title, state, roles}]}}`,
names matching `NAME`, 1 to 20 steps, titles up to 120 characters, 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 that are an description up to 1,000, each step's `roles` 1 to 8 names. The reader
org definition, a V2 template or `human`; 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}`, - `packs`: a list of `{id, version, sha256, origin, scope, project, at, by, added, proposedHires}`,
origin one of `shipped`, `installed`, `saved`. 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: - Builds:
- the reader keeps `packs`, `board` and `workflows` on the loaded - the reader keeps `packs`, `board` and `workflows` on the loaded
organization object, and `board` and `workflows` on each `projects[id]`. organization object, and `board` and `workflows` on each `projects[id]`.
@@ -904,13 +1009,19 @@ does not widen itself. Tests for a slice live beside the files it owns.
writes everything or nothing (a fault injected after the definitions writes everything or nothing (a fault injected after the definitions
leaves the file unchanged); an agent actor with a definition in the plan leaves the file unchanged); an agent actor with a definition in the plan
refuses `human-required`; a plan holding `plugins`, `roles`, `limits` or refuses `human-required`; a plan holding `plugins`, `roles`, `limits` or
`credentials` refuses; a board-only plan changes only `board`. `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 - 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. (`organization-config.mjs:259`); the test expects it to load.
### SP4: preview, apply, board and workflow writes for people ### SP4: preview, apply, board and workflow writes for people
- Owns: `packages/bus/src/broker.mjs` (the six SP4 verbs only), - 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`, `packages/bus/src/runtime.mjs` (the new `packs`, `pack.preview`,
`pack.apply`, `board.set` and `workflow.set` ops only), `pack.apply`, `board.set` and `workflow.set` ops only),
`packages/bus/src/refusals.mjs` (`pack-plan-stale`), `packages/bus/src/refusals.mjs` (`pack-plan-stale`),
@@ -918,17 +1029,21 @@ does not widen itself. Tests for a slice live beside the files it owns.
(the `pack`, `board` and `workflow` words), `packages/webui/src/packs-http.mjs` (the `pack`, `board` and `workflow` words), `packages/webui/src/packs-http.mjs`
(new: the routes), `packages/webui/src/serve.mjs` (route hookup only), (new: the routes), `packages/webui/src/serve.mjs` (route hookup only),
`packages/webui/src/cli.mjs` (caller wiring only), their tests. `packages/webui/src/cli.mjs` (caller wiring only), their tests.
- Schema change: `VERB_RIGHTS` gains `packs: 'board.read'`, - Schema change: `VERB_RIGHTS` gains `'packs.console': 'board.read'`,
`'packs.console': 'board.read'`, and `null` (owner only) for and `null` (owner only) for
`pack.preview.console`, `pack.apply.console`, `board.set.console` and `pack.preview.console`, `pack.apply.console`, `board.set.console` and
`workflow.set.console`. `CONSOLE_VERBS` gains `packs.console` and those `workflow.set.console`. `CONSOLE_VERBS` gains `packs.console` and those
four. Owner only matches the definition writes today four. The unsuffixed verbs are not in `VERB_RIGHTS` (section 3). Owner
(`broker.mjs:6221,6303-6308`); when row 193 opens those to `org.hire`, only matches the definition writes today (`broker.mjs:6221,6303-6308`);
the four move with them. 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`, - Builds: `mosaic org pack list|preview|apply`, `mosaic org board set`,
`mosaic org workflow set|remove`; routes `GET /api/packs`, `mosaic org workflow set|remove`, calling the unsuffixed verbs; routes
`POST /api/orgs/<id>/packs/preview`, `POST /api/orgs/<id>/packs/apply`, `GET /api/packs`, `POST /api/orgs/<id>/packs/preview`,
`POST /api/orgs/<id>/board` and `POST /api/orgs/<id>/workflows/<name>`; `POST /api/orgs/<id>/packs/apply`, `POST /api/orgs/<id>/board`,
`POST /api/orgs/<id>/workflows/<name>` and
`DELETE /api/orgs/<id>/workflows/<name>`, 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 the merge table in section 3, including `kept`, `--as` and the per-pack
`removed` tombstone; the held-credential check at preview and at apply; `removed` tombstone; the held-credential check at preview and at apply;
`saved:<name>` through `readSavedTemplate` with the caller's own account. `saved:<name>` through `readSavedTemplate` with the caller's own account.
@@ -945,7 +1060,11 @@ does not widen itself. Tests for a slice live beside the files it owns.
owner B naming `saved:x` that only owner A saved gets owner B naming `saved:x` that only owner A saved gets
`starter-pack-not-found`; `packs.console` lists no saved template; `starter-pack-not-found`; `packs.console` lists no saved template;
`board.set.console` changes only `board`; a workflow step naming an `board.set.console` changes only `board`; a workflow step naming an
unknown role refuses. 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/<id>/workflows/<name>` remove only that
workflow.
- Red before: the owner calls `pack.apply.console` for `coding` and the - 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. test expects a `packs` entry in the org file; today the verb is unknown.
@@ -955,20 +1074,28 @@ does not widen itself. Tests for a slice live beside the files it owns.
pin check only), `packages/bus/src/runtime.mjs` (the agent actor on the pin check only), `packages/bus/src/runtime.mjs` (the agent actor on the
SP4 ops only), `packages/bus/src/refusals.mjs` SP4 ops only), `packages/bus/src/refusals.mjs`
(`starter-pack-proposal-stale`), their tests. (`starter-pack-proposal-stale`), their tests.
- Schema change: none to files. Authority: agent verbs `packs`, - 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 `pack.preview`, `pack.apply`, `board.set` and `workflow.set` for a role
whose file lists `organization.change`, the same test `organization.set` whose file lists `organization.change`, the same test `organization.set`
uses (`broker.mjs:6247-6255`). A role without it gets uses (`broker.mjs:6247-6255`). A role without it gets
`decision-required`, as `organization.set` gives today `decision-required`, as `organization.set` gives today
(`#classification`, `broker.mjs:4743`). (`#classification`, `broker.mjs:4743`).
- Builds: the agent apply in section 3: no definitions (`human-required` if - Builds: SP5 extends the same unsuffixed verbs SP4 adds for the human to
the plan has one), origin `saved` refused with `human-required`, workflow a role whose file lists `organization.change`. The `packs` read is
steps naming an unwritten definition left out and marked in preview; the allowed to an `organization.change` holder without spending the grant,
pin check when the chief raises a proposal (`decision-stale`, as `organization.revision` is (`broker.mjs:6253`); any other agent gets
`definition-rev-conflict`, `starter-pack-proposal-stale` when an inline `human-required`. The agent apply in section 3: no definitions
entry's canonical hash differs, and `definition-not-hireable` when an (`human-required` if the plan has one), origin `saved` refused with
inline entry's `from` is in `launch.noHire`; the broker checks the hash, `human-required`, workflow steps naming an unwritten definition left out
then passes `{inline}` on). 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 - Acceptance: a coder without `organization.change` gets
`decision-required` for `pack.apply`, `board.set` and `workflow.set`, the `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 same as `packages/bus/tests/chief-of-staff.test.mjs:255-261` asserts for
@@ -980,7 +1107,10 @@ does not widen itself. Tests for a slice live beside the files it owns.
refuses `decision-stale`, one whose definition `rev` moved refuses refuses `decision-stale`, one whose definition `rev` moved refuses
`definition-rev-conflict`, an inline one whose hash differs refuses `definition-rev-conflict`, an inline one whose hash differs refuses
`starter-pack-proposal-stale`, and an inline one whose `from` is in `starter-pack-proposal-stale`, and an inline one whose `from` is in
`launch.noHire` refuses `definition-not-hireable`. `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 - 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. expects the org file to gain its `board`; today the verb is unknown.
@@ -997,7 +1127,8 @@ does not widen itself. Tests for a slice live beside the files it owns.
(`member.doc`, `workspace-read.mjs:140`), so the per-org head gains (`member.doc`, `workspace-read.mjs:140`), so the per-org head gains
`board` and each project row its project `board`, and a new `board` and each project row its project `board`, and a new
`GET /api/orgs/<id>/workflows` returns the org and project workflows from `GET /api/orgs/<id>/workflows` returns the org and project workflows from
the same document. Then the pages: pack picker, preview and apply through 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 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` project; `layout` as a default under the viewer's saved `localStorage`
choice; a workflow list and an editor that saves through choice; a workflow list and an editor that saves through
@@ -1005,7 +1136,8 @@ does not widen itself. Tests for a slice live beside the files it owns.
- Acceptance: a view filtered to an org with a `board` shows its labels; the - 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 all-orgs view keeps today's labels; a saved viewer layout wins over the
pack default; a label or workflow title holding `<img src=x onerror=...>` pack default; a label or workflow title holding `<img src=x onerror=...>`
renders as text (`esc()`, `tasks-board.js:108-113`). 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 - Red before: a view filtered to an org with a `board` shows the fixed labels
(`tasks-board.js:21`). (`tasks-board.js:21`).
@@ -1037,7 +1169,9 @@ does not widen itself. Tests for a slice live beside the files it owns.
### SP8: saving a template ### SP8: saving a template
- Owns: `packages/organization/src/template-save.mjs` (new), - Owns: `packages/organization/src/template-save.mjs` (new),
`packages/bus/src/broker.mjs` (the two template verbs only), `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/runtime.mjs` (the save and list ops only),
`packages/bus/src/refusals.mjs` (`template-secret-suspected`), `packages/bus/src/refusals.mjs` (`template-secret-suspected`),
`packages/organization/src/org-rights.mjs`, `packages/cli/src/org.mjs` `packages/organization/src/org-rights.mjs`, `packages/cli/src/org.mjs`
@@ -1050,8 +1184,9 @@ does not widen itself. Tests for a slice live beside the files it owns.
new schema. new schema.
- Builds: capture, strip, placeholder and preview rules in section 5; the - Builds: capture, strip, placeholder and preview rules in section 5; the
file at `<configDir>/templates/<account>/<name>.json`, directory 0700, file at `<configDir>/templates/<account>/<name>.json`, directory 0700,
file 0600, written with `wx`, name and account checked against `NAME` file 0600, written with `wx`, the account (the caller's `s.human`)
first; `packs` written as at most 32 `{id, version}` entries; save 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 refuses more than 8 hires and any role with a `from` that
`narrowsTemplate` refuses; `list` shows only the caller's own. `narrowsTemplate` refuses; `list` shows only the caller's own.
- Acceptance: the file has none of the stripped keys and keeps role - Acceptance: the file has none of the stripped keys and keeps role
@@ -1061,95 +1196,88 @@ does not widen itself. Tests for a slice live beside the files it owns.
`template-secret-suspected` naming the field path; a second save to the `template-secret-suspected` naming the field path; a second save to the
same name refuses; a name of `..` refuses; a ninth hire refuses; each same name refuses; a name of `..` refuses; a ninth hire refuses; each
saved `packs` entry holds only `id` and `version`; a saved template saved `packs` entry holds only `id` and `version`; a saved template
applies through SP4 for its saver. 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 - Red before: save an org whose instance has `credentials` and
`copyBindings` and assert neither appears in the output; the verb does not `copyBindings` and assert neither appears in the output; the verb does not
exist today. exist today.
### SP9: retire system types, step 1 ### SP9: remove system types
- Owns, each scoped to its system-type lines: - Owns, each scoped to its system-type lines:
- `packages/plugins/src/kinds.mjs`; `packages/plugins/src/system-types.mjs` - `packages/plugins/src/kinds.mjs`; `packages/plugins/src/system-types.mjs`
and `packages/plugins/system-type/` (deleted); and `packages/plugins/system-type/` (deleted);
- `packages/organization/src/organization-config.mjs` and - `packages/organization/src/organization-config.mjs` (lines 360-365) and
`packages/organization/src/organization.mjs` (the retired key and `packages/organization/src/organization.mjs` (`systemTypesFence` and
`systemTypesFence`); its calls);
- `packages/organization/src/org-rights.mjs` (`system-types.console`, - `packages/organization/src/org-rights.mjs` (`system-types.console`,
line 90); line 90);
- `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs` (the - `packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs` (the
import at line 30 and the reads at 903-904) and import at line 30 and the reads at 903-904) and
`packages/bus/src/refusals.mjs` (`system-type-not-enabled` out, `packages/bus/src/refusals.mjs` (`system-type-not-enabled` and
`verb-retired` in); `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/org.mjs`, `packages/cli/src/project.mjs` and
`packages/cli/src/cli.mjs` (the dispatch list at line 781); `packages/cli/src/cli.mjs` (the dispatch list at line 781);
- `packages/plugins/channel/cli/index.mjs`; - `packages/plugins/channel/cli/index.mjs`;
- the Console files in section 1's system-type rows; - the Console files section 6 lists;
- `docs/prd/mosaic-stack.md` (REQ-SYS-1/2 only); - `docs/prd/mosaic-stack.md` (REQ-SYS-1/2 only),
- their tests. `packages/tasks/requirements.json` (lines 73-74), and the three
- Schema change: `system-type` stays in `KINDS` with `retired: true`. package READMEs section 6 lists;
`checkPlugins` accepts any list for it without loading modules, the reader - their tests, and every test file section 6 names for the schema pins.
ignores it, and `systemTypesFence` becomes a fence that lets any writer - Schema change: `system-type` leaves `KINDS`. Bus schema step `3v-3w`
drop the key and refuses a write that adds or changes a name drops `project_system_types`, its index and its four triggers, and
(`verb-retired`). The next writer commit drops the key and its trail event `SCHEMA_VERSION` becomes `'3w'`. The pins it moves are section 6's:
keeps the old list. `project_system_types` is unchanged (no DDL). The PRD `schema.test.mjs:533-534` (tables 33 to 32, triggers 130 to 126),
text is section 6's. `COUNTED` and `LISTED` (`stack-release.sh:443`,
- Builds: the removals in section 6; `project.system-types.set`, `stack-release.test.mjs:888`, with the samples at `:906,928`), the 20
`system-types.installed` and `system-types.current` refuse test files that pin `'3v'`, and the migration-chain cuts and table lists.
`verb-retired`; the `project.system-types` read stays. The PRD text is section 6's.
- Acceptance: an org file with `plugins['system-type']` loads with the key - Builds: the removals in section 6, and the step.
ignored; the next commit by an agent or a person drops it and its trail - Acceptance: an org file with `plugins: {"system-type": []}` refuses
event holds the old list; `project_system_types` rows are unchanged; no `unknown-kind`; a `3v` store holding `project_system_types` rows opens,
system-type route, CLI word or channel verb is left, and migrates to `3w` and matches the fresh `schema.sql` digest, with no table,
`grep -rln "installedSystemTypes\|system-type-not-enabled" packages` index, trigger or `sqlite_sequence` row named `project_system_types`
finds nothing outside tests. left; the schema counts are 32 tables and 126 triggers; T45 and T46
- Red before: an org file with `plugins['system-type']: ["no-such-type"]` pass; no system-type verb, route, CLI word, channel verb or Console page
loads with the key ignored (today it refuses "names unknown module", is left, and
`organization-config.mjs:365`); a human write that adds `coding` refuses `grep -rlnE "system-type|systemType|system_type|system-types" packages scripts`
`verb-retired` (today it is accepted). 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).
### SP10: the print-only conversion ## 9. Answered
- Owns: `packages/organization/src/migrate-system-type.mjs` (new, beside Jason answered on 2026-11-12, about 21:05Z, with "full send". The slices
`migrate-launch.mjs`), `packages/cli/src/org.mjs` (the above follow his answers.
`migrate-system-type --file ORGANIZATION` word, which prints and writes
nothing, as `mosaic limits migrate` does at `packages/cli/src/limits.mjs:62-72`),
their tests.
- Schema change: none. `system-type` stays in `KINDS` as retired.
- Builds: a conversion that returns the org file without
`plugins['system-type']` and names the old list on stderr.
- Acceptance: the conversion prints the SP9 fixture without the key and
leaves the input file byte-identical; a file without the key prints
unchanged.
- Red before: the conversion does not exist.
Follow-up row, not in this go: remove `system-type` from `KINDS` after one 1. **Retire system types as a separate kind?** Remove it outright:
release, with its own go, once each instance's owner has run the conversion
or every org file has been written since SP9.
## 9. Open questions for Jason > 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.
Each answer changes what gets built. The slices above follow the Section 6 and SP9 remove the kind, its modules, verbs, routes, refusals,
recommendation; a different answer changes the slice named. reads, CLI words, channel verbs and Console surfaces, and drop
`project_system_types` with bus schema step `3v-3w`. An org file naming
1. **Retire system types as a separate kind?** This design replaces the the kind refuses as an unknown kind.
`system-type` kind with `starter-pack`. Org files that name the old kind
keep loading for one release and lose the key on their next write;
`project_system_types` stays as history. Recommendation: yes. The modules carry
nothing today, and two surfaces for one idea would confuse users. A no
drops SP9 and SP10 and keeps the system-type label beside packs.
2. **May the chief change board styles and workflows directly, as it changes 2. **May the chief change board styles and workflows directly, as it changes
limits today?** This design puts `board.set`, `workflow.set` and the limits today?** Yes, as written. `board.set`, `workflow.set` and the
non-definition half of `pack.apply` under the chief's existing non-definition half of `pack.apply` sit under the existing
`organization.change` grant, with no decision. Recommendation: yes. They `organization.change` grant, with no decision. Any role listing
carry no capability, the trail records each change, and a decision here `organization.change` gets the same writes; a role without it gets
would be a new gate. Any role listing `organization.change` gets the same `decision-required`, as `organization.set` gives today. Role and hire
writes, so the rule is the grant, not the chief's name. A role without it changes stay decisions, and only the chief raises hires.
gets `decision-required`, as `organization.set` gives today. Role and hire
changes stay decisions, and only the chief raises hires. A no drops the
agent writes from SP5 and leaves agents to propose board and workflow
changes in a message to the owner.
3. **Board styles as labels and order over the five fixed states, or custom 3. **Board styles as labels and order over the five fixed states, or custom
states?** Custom states would change the task tracker contract, every states?** Yes, as written: labels, order and layout over the five fixed
tracker module and the Vikunja bucket setup. Recommendation: labels, order states. Custom states would change the task tracker contract, every
and layout only for now; revisit custom states when a pack needs one. A tracker module and the Vikunja bucket setup, and wait until a pack needs
no adds a tracker contract change to SP3 and every tracker module. one.