docs(plans): starter packs fix round 3, system types removed outright
This commit was merged in pull request #1949.
This commit is contained in:
1 parent
dd878cefcf
commit
c0a60ed3ae
1 file changed
+292
-164
@@ -105,7 +105,8 @@ returns the document.
|
||||
|
||||
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 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`
|
||||
(`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
|
||||
checks, so `readSavedTemplate` does them first:
|
||||
|
||||
- the account and the template name each match `NAME`
|
||||
(`packages/plugins/src/kinds.mjs:20`) before any path is built, so `..`
|
||||
and `/` never reach the file system;
|
||||
- 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 `<configDir>/templates/<caller account>/`;
|
||||
`saved:<name>` never names another account's file;
|
||||
- `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.
|
||||
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 role
|
||||
`{definition, rev}` and a route it chooses; the broker computes the
|
||||
- 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
|
||||
@@ -371,9 +379,11 @@ exactly as if a person had made them by hand. The steps:
|
||||
| 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/<id>/packs/preview`, `POST /api/orgs/<id>/packs/apply`, `POST /api/orgs/<id>/board`, `POST /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` |
|
||||
| `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) |
|
||||
| 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, 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
|
||||
@@ -420,16 +430,25 @@ What it does:
|
||||
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-<decision>` as it does now.
|
||||
- the pin recorded in `proposedHires` is checked again when the chief raises
|
||||
the hire, so a proposal never resolves to text nobody reviewed. A template
|
||||
pin that no longer matches refuses `decision-stale` and a definition whose
|
||||
`rev` moved refuses `definition-rev-conflict`, both existing checks
|
||||
(`hire-decisions.mjs:225-226`). An inline entry whose hash differs from the
|
||||
recorded one refuses the new `starter-pack-proposal-stale`. For an inline
|
||||
entry the broker also refuses at raise when the entry's `from` template is
|
||||
in `launch.noHire`, with `definition-not-hireable`, the code
|
||||
`hire-decisions.mjs:219` gives for a template, since the `{inline}` path
|
||||
itself skips that check.
|
||||
- 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
|
||||
|
||||
@@ -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. |
|
||||
| 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` | 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. |
|
||||
| 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. |
|
||||
@@ -614,9 +633,14 @@ There is no create-time shortcut that skips the hire decisions.
|
||||
|
||||
## 6. System types
|
||||
|
||||
**Decision: the kind is replaced.** The `system-type` kind becomes
|
||||
`starter-pack`. A pack's `label` is what the UI shows as the system type. The
|
||||
reasons:
|
||||
**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
|
||||
@@ -624,39 +648,102 @@ reasons:
|
||||
- Keeping both would leave a second, empty configuration surface next to the
|
||||
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 file that names it: `checkPlugins` refuses an unknown kind, even with an
|
||||
empty list (`unknown-kind`, `kinds.mjs:146-148`, run at
|
||||
`organization-config.mjs:358`). An organization would stop loading until
|
||||
its owner converted the file by hand, which is an outage and a forced
|
||||
human step. So:
|
||||
1. For one release `system-type` stays in `KINDS` as a retired kind. The
|
||||
reader accepts the key and ignores it. A write that adds a name refuses
|
||||
`verb-retired` (a new code). The next writer commit for that organization, by anyone,
|
||||
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
|
||||
Console offers the matching packs once.
|
||||
2. A print-only conversion in the style of `migrateLaunch`
|
||||
(`packages/organization/src/migrate-launch.mjs`, decision 145) covers a
|
||||
file that is never written in between; it is built in this go (SP10).
|
||||
Removing the kind from `KINDS` is not in this go. It is a follow-up row
|
||||
with its own go, after one release, once each instance's owner has run
|
||||
the conversion or every org file has been written. A build session
|
||||
cannot see other instances' org files, so it cannot make that check.
|
||||
- **`project_system_types`.** The table stays, rows and triggers unchanged
|
||||
(write-once, R2). `project.system-types.set` refuses with a new code,
|
||||
`verb-retired` (not found today). The read verb keeps working for history
|
||||
until a later PRD removes it. No DDL change.
|
||||
- **Code removed.** `systemTypesFence`, `system-types.console`, the system
|
||||
types Console route and page, the CLI words (`mosaic org system-types`,
|
||||
`mosaic project system-types`), the channel verbs,
|
||||
`system-type-not-enabled`, `installedSystemTypes` and its bus reads, each
|
||||
with its tests (section 1's three system-type rows list every file).
|
||||
`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
|
||||
`packages/plugins/starter-pack/`.
|
||||
### 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
|
||||
|
||||
@@ -777,10 +864,10 @@ proposals. The workflow's "relevant officer" step lists all four in its
|
||||
|
||||
## 8. Build slices
|
||||
|
||||
The slices run in order, SP1 to SP10, each landing with its own tests and
|
||||
the full suites green. One go from Jason covers all ten. Nothing in a slice
|
||||
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.
|
||||
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/`.
|
||||
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
|
||||
`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`,
|
||||
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, 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
|
||||
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);
|
||||
`verb-retired` (SP9).
|
||||
`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
|
||||
@@ -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
|
||||
shipped and installed packs through `loadInstalled` and sets the origin;
|
||||
`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
|
||||
`parseJsonFileText` and `checkPack` with origin `saved`.
|
||||
- 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
|
||||
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
|
||||
`/`, a symlink, a 0644 file and a 300 KiB file, and a missing file
|
||||
refuses `starter-pack-not-found`.
|
||||
`/`, 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`
|
||||
@@ -878,10 +975,18 @@ does not widen itself. Tests for a slice live beside the files it owns.
|
||||
`list`;
|
||||
- `workflows`: `{<name>: {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 that are an
|
||||
org definition, a V2 template or `human`;
|
||||
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`.
|
||||
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]`.
|
||||
@@ -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
|
||||
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`.
|
||||
`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 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`,
|
||||
`pack.apply`, `board.set` and `workflow.set` ops only),
|
||||
`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`
|
||||
(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: 'board.read'`,
|
||||
`'packs.console': 'board.read'`, and `null` (owner only) for
|
||||
- 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. Owner only matches the definition writes today
|
||||
(`broker.mjs:6221,6303-6308`); when row 193 opens those to `org.hire`,
|
||||
the four move with them.
|
||||
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`; routes `GET /api/packs`,
|
||||
`POST /api/orgs/<id>/packs/preview`, `POST /api/orgs/<id>/packs/apply`,
|
||||
`POST /api/orgs/<id>/board` and `POST /api/orgs/<id>/workflows/<name>`;
|
||||
`mosaic org workflow set|remove`, calling the unsuffixed verbs; routes
|
||||
`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>` 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
|
||||
`removed` tombstone; the held-credential check at preview and at apply;
|
||||
`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
|
||||
`starter-pack-not-found`; `packs.console` lists no saved template;
|
||||
`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
|
||||
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
|
||||
SP4 ops only), `packages/bus/src/refusals.mjs`
|
||||
(`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
|
||||
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: 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
|
||||
pin check when the chief raises a proposal (`decision-stale`,
|
||||
`definition-rev-conflict`, `starter-pack-proposal-stale` when an inline
|
||||
entry's canonical hash differs, and `definition-not-hireable` when an
|
||||
inline entry's `from` is in `launch.noHire`; the broker checks the hash,
|
||||
then passes `{inline}` on).
|
||||
- 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
|
||||
@@ -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
|
||||
`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`.
|
||||
`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.
|
||||
|
||||
@@ -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
|
||||
`board` and each project row its project `board`, and a new
|
||||
`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
|
||||
project; `layout` as a default under the viewer's saved `localStorage`
|
||||
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
|
||||
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=...>`
|
||||
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
|
||||
(`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
|
||||
|
||||
- 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/refusals.mjs` (`template-secret-suspected`),
|
||||
`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.
|
||||
- Builds: capture, strip, placeholder and preview rules in section 5; the
|
||||
file at `<configDir>/templates/<account>/<name>.json`, directory 0700,
|
||||
file 0600, written with `wx`, name and account checked against `NAME`
|
||||
first; `packs` written as at most 32 `{id, version}` entries; save
|
||||
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
|
||||
@@ -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
|
||||
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.
|
||||
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: retire system types, step 1
|
||||
### 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` and
|
||||
`packages/organization/src/organization.mjs` (the retired key and
|
||||
`systemTypesFence`);
|
||||
- `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` out,
|
||||
`verb-retired` in);
|
||||
`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 in section 1's system-type rows;
|
||||
- `docs/prd/mosaic-stack.md` (REQ-SYS-1/2 only);
|
||||
- their tests.
|
||||
- Schema change: `system-type` stays in `KINDS` with `retired: true`.
|
||||
`checkPlugins` accepts any list for it without loading modules, the reader
|
||||
ignores it, and `systemTypesFence` becomes a fence that lets any writer
|
||||
drop the key and refuses a write that adds or changes a name
|
||||
(`verb-retired`). The next writer commit drops the key and its trail event
|
||||
keeps the old list. `project_system_types` is unchanged (no DDL). The PRD
|
||||
text is section 6's.
|
||||
- Builds: the removals in section 6; `project.system-types.set`,
|
||||
`system-types.installed` and `system-types.current` refuse
|
||||
`verb-retired`; the `project.system-types` read stays.
|
||||
- Acceptance: an org file with `plugins['system-type']` loads with the key
|
||||
ignored; the next commit by an agent or a person drops it and its trail
|
||||
event holds the old list; `project_system_types` rows are unchanged; no
|
||||
system-type route, CLI word or channel verb is left, and
|
||||
`grep -rln "installedSystemTypes\|system-type-not-enabled" packages`
|
||||
finds nothing outside tests.
|
||||
- Red before: an org file with `plugins['system-type']: ["no-such-type"]`
|
||||
loads with the key ignored (today it refuses "names unknown module",
|
||||
`organization-config.mjs:365`); a human write that adds `coding` refuses
|
||||
`verb-retired` (today it is accepted).
|
||||
- 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).
|
||||
|
||||
### SP10: the print-only conversion
|
||||
## 9. Answered
|
||||
|
||||
- Owns: `packages/organization/src/migrate-system-type.mjs` (new, beside
|
||||
`migrate-launch.mjs`), `packages/cli/src/org.mjs` (the
|
||||
`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.
|
||||
Jason answered on 2026-11-12, about 21:05Z, with "full send". The slices
|
||||
above follow his answers.
|
||||
|
||||
Follow-up row, not in this go: remove `system-type` from `KINDS` after one
|
||||
release, with its own go, once each instance's owner has run the conversion
|
||||
or every org file has been written since SP9.
|
||||
1. **Retire system types as a separate kind?** Remove it outright:
|
||||
|
||||
## 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
|
||||
recommendation; a different answer changes the slice named.
|
||||
|
||||
1. **Retire system types as a separate kind?** This design replaces the
|
||||
`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.
|
||||
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?** This design puts `board.set`, `workflow.set` and the
|
||||
non-definition half of `pack.apply` under the chief's existing
|
||||
`organization.change` grant, with no decision. Recommendation: yes. They
|
||||
carry no capability, the trail records each change, and a decision here
|
||||
would be a new gate. Any role listing `organization.change` gets the same
|
||||
writes, so the rule is the grant, not the chief's name. A role without it
|
||||
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.
|
||||
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?** Custom states would change the task tracker contract, every
|
||||
tracker module and the Vikunja bucket setup. Recommendation: labels, order
|
||||
and layout only for now; revisit custom states when a pack needs one. A
|
||||
no adds a tracker contract change to SP3 and every tracker module.
|
||||
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.
|
||||
Reference in new issue
Block a user