CB0: registry incarnation minted at reserve (legacy entries get one at
the first binding proposal, under the registry lock); copyId, source
and definition pin, binding generation; a pure resolver where
effective = role intersect approved and only a bound, matching,
unshadowed record resolves.
CB1: copyBindings in the organization file (ids, scope names, project
ids and integers only). applyCopyBindingDecision is its only writer,
behind a module-private capability every other commit is fenced
against. A binding is an owner-only subtype of the hire decision:
only the chief raises it, no autonomy level, resolver agent, member
right, tester or reader resolves it. Digest, expiry, single
consumption, revision, generation, incarnation and definition pin are
rechecked before any effect; a bind writes pending, an unbind the
unbound tombstone, receipts are paths only. Nothing writes bound, so
nothing launches on a binding yet (CB2/CB3). The org service and the
Console update path keep copyBindings server-held. Eight new refusal
codes with next steps.
Roles round 2, slice T1 (roles-and-rails design sections 4, 5 and 13).
- The org file's definitions key: each entry {rev, from?, role, instructions,
personality?} passes checkComposition; a bad entry refuses the whole file,
naming the definition and key, never a value.
- org:<name> instance references resolve to the file's definition; a missing
one refuses definition-missing, naming the instance. Template names are
unchanged.
- Template identity is the name plus a sha256 over the template JSON and its
contract; templateHashes reads the current hash for each template.
- The writer's setDefinition is the only path that changes definitions: it
checks the composition, bumps rev, refuses agent actors, and refuses
definition-in-use for a definition an instance names (A4 adds the change
decision). write() refuses organization-definitions-protected.
- Definition verbs import, edit, invent and remove need org.hire
(definition.*.console in VERB_RIGHTS) and refuse every agent caller.
- The diff read lists the primitives that changed and a line diff of the
instructions. It applies nothing.
- CLI: mosaic org definitions templates|list|show|diff|import|invent|edit|remove.
Each root agent an organization does not exclude, and each agent a project
defines, becomes a copy when the organization's bus boots. The root file is
read once, through loadRootAgents, at the cli boot step; the snapshot is fixed
until the next boot. A bound copy joins the organization document's roles and
goes through validateBusinessDocument and checkRoles with the organization's
own credential map. A copy whose role needs a credential service is unbound
and refuses at launch by name and service (credential-unbound); a copy the
organization refuses is copy-refused; every agent in a project whose union
refuses is project-refused, naming the project. A project agent launches
only with its own project's mounts and takes a thread only in its project
(project-agent-elsewhere). Refusal events carry the code alone. The bus's
instances read carries the copies with their running runs, and the Agents
grid shows each copy's real status and reason.
channel-proof re-pins webui bus.mjs for four STATUS entries; no proof or
transport change. packages/business gets the agent-copies.mjs re-export.
Root writer for <configDir>/agents.json (owner only, revision-checked, atomic, A1-validated,
receipts name agents and keys only), the project.agents writer verb on the organization
writer, `mosaic agents root` and `mosaic project agents` verbs, and owner-only Settings >
Agents edit forms on GET/POST hooks gated like /api/agents/levels.
Roles round 2, slice P1. checkComposition(role, registry, chief, { assertClean })
runs the design's seven steps in order against a registry from buildRegistry
(core vocabulary plus a caller-passed plugin set, empty by default) and returns
the flagged lines: every decision.resolve.* action and everything above the
chief's own resolved role. Refusals name the field and position, quote only
registered names, and never quote instructions or personality text.
COMPOSITION_BOUNDS holds the length bounds the design leaves open. Nothing
calls checkComposition yet; HIRE_ALLOWED and unhireableAction stay until A4.
Phase A slice A1, no authority change. Agents are defined at three levels:
the user's root (<configDir>/agents.json), the organization (roles) and the
project (projects.<p>.agents in the organization file). A project's agents
are their union, the most specific level winning.
- organization: agent-levels.mjs loads and checks the root file (no link,
owner uid, no group or other write) and project entries (define, narrow
intersect keys only, exclude), and resolves the union. The organization
file checks project agents for every reader and leaves them out of the
loaded business, so buses see what they saw before. Old-path re-export
in packages/business.
- webui: GET /api/agents/levels, owner only, with no host path, credential
reference or var other than the model in its body. display.js leaves it
unscoped. SERVER_PROJECT_KEYS holds agents.
- Agents grid: the level band, a project picker, and per agent its level,
the file that defines it, memory, status and model, with narrowed keys,
overrides and what the project does not get.
- Design: docs/plans/2026-11-10_agent-levels-design.md. The roles/ change is
a proposal outside this branch.
mosaic org config validate|resolve runs runConfigCommand
(packages/organization/src/config-command.mjs), the callable service the
old mosaic business validate|resolve executable now calls too. The service
returns the exit code, never exits the process and does nothing on import.
The spelling sets only the stderr prefix and the usage text; status and
stdout are byte-identical, and the v1 output keeps the business key.
mosaic org instances|instance|veto takes the same arguments as
mosaic business instances|instance|veto and runs the same proof-bearing
instances command. It is dispatched before orgCommand, so it never builds
the dev-local-owner context. mosaic org validate FILE is unchanged.
Help, the UI labels for agent actions and instance reasons, and the
current docs now use the org spelling and the packages/organization paths.
Two webui test regexes follow the label text. packages/cli/tests/
rename-b-commands.test.mjs compares the two spellings golden for golden
on status, stdout, stderr and authorization outcomes, and covers a draft
file named like an installed ID.
git mv packages/business -> packages/organization (@mosaic/organization). The
old package stays as one-line re-export shims of each moved module, with
src/business.mjs re-exporting src/organization-config.mjs and src/cli.mjs
forwarding to the moved executable. BusinessError is OrganizationConfigError
(one constructor, name kept BusinessError); OrganizationError stays distinct.
The bus adapter gains organization-context.mjs (busOrganization), with
business.mjs (busBusiness) as the same function.
Import consumers, manifests, the lockfile, scripts/mosaic, stack-copy.sh
COPY_PATHS (both roots), seat sandbox RUNNER_FILES and the copy closure
walker (organization-imports.mjs, both roots required) move to the new path.
v1 serialization, v1 writers and the stage A fixtures are unchanged; the stage
A contract tests stay where they were and pass as before.
Rename design r2, "Selector scopes and parser matrix". packages/business/src/selector.mjs
and scripts/selector.sh carry one rule set: in a parser's scope each spelling appears once,
and a second spelling must carry exactly the same value; a repeated spelling, a missing or
flag-shaped value, an invalid id and unequal values refuse. The file family
(--business-file/--org-file) compares paths as given.
Every CLI parser that took --business now takes --org through orgSelector, and the old
global --org rewrite in main() is gone, so a value another option consumes is never read as
a selector. The id is checked while parsing (OrgError org-invalid, exit 3), so grants admin
set no longer reads the token and proxy init no longer prompts for the key before an
invalid id refuses. access gains the same local id check.
F1: the org list shows a broken org as invalid only when the failure
came after the owner check passed (ownerVerified). A broken root or
marker on someone else's org stays hidden.
F2: /api/orgs answers a registry read failure with a fixed message.
The parser text could quote private registry bytes.
F3: selected-org pages keep the list error, paint one replaceable
header banner, and Retry always re-reads the list. The Board shows the
registry message once.
New tests org-list-privacy and org-list-refresh-browser fail on
0e9b2c505 for F1, F2 and F3 and pass here. Serial batch over all 134
webui files plus business org-* and cli org: only failures were the
known pinned-copy EISDIR (8) and an org-switch assertion on the old
pass-through text, updated here.
#867: organization reads no longer take the registry writer lock; they
retry while the lock exists or the registry changed during the read.
#868: a broken organization is listed as invalid with a path-masked
reason instead of disappearing; the Board, header and picker show it,
and /api/orgs 503 carries the reason. Orgs the principal may not see and
archived orgs still give an empty list.
#869: a Console with no board says "No board", not "Board: null".
#866 (save-path validation at bus admission) is left out: it refused
documents the bus accepted before and needs a migration story first.
Every bus, business, cli and webui file run serially; failures are only
the known EISDIR node_modules-link files.
- A Console is stale only when its pid is gone or its start time differs. A live Console
started from another entry path is never quarantined; stop refuses to signal it and
names the path it was started from.
- The lock-clear fence records the clearer's pid and start time. A fence left by a dead
clearer is moved aside and recovery continues; a live or unreadable fence refuses.
lock show reports a fence.
- Console state is moved aside with one atomic rename after an inode re-check.
console start and stop move a console.json whose recorded process is gone
aside instead of refusing (P19 #857). mosaic org lock show|clear reports a
lifecycle lock holder and clears it only when the holder is dead.
mosaic bus recover marks a bus host stopped when its process is gone and its
socket does not answer, and writes a recovery receipt (P19 #862).
mosaic org delete ID --confirm ID --if-revision N, and POST /api/orgs/:id/delete,
mark the organization as deleting, stop its bus, move its files into
<dataRoot>/deleted/<ID>-<stamp>/ with a receipt, then drop it from the registry
and append deleted.log. A failure stops at its step and a rerun resumes. Bus
start refuses an organization that is being deleted. The settings page asks
twice: a panel that lists the effects, then typing the ID before Delete
permanently is enabled. Purge and remote revocation are follow-ups.
Add a reviewed `theme` plugin kind (interfaceVersion 1, export `theme`, switch
"list"). A module returns data only: a displayName and a flat map of colour
custom properties for light, dim and dark. checkTheme validates names and
#rrggbb colours strictly and checks each text/background pair for contrast; a
bad module is refused and logged, never served.
The ten shipped palettes become the first ten modules under
packages/plugins/theme/<name>/. Modules are found by directory scan
(loader.discoverModules, loader.loadInstalled), not by a list in the Console.
loadModule and createLoader are unchanged.
The organization file takes `appearance: { theme, mode }`, validated in
packages/business/src/appearance.mjs; a missing appearance is valid.
GET /api/themes (themes-http.mjs) serves the installed, valid themes.
Browser: the Settings picker lists /api/themes. Precedence is a per-organization
browser override, then the organization's saved theme, then the built-in default
(Harbor, dark). Clearing the override restores the organization's choice. An
uninstalled or invalid saved theme renders the default with a visible warning
and keeps the saved value. A legacy global browser preference is not copied to
every organization. The organization is named in text, so colour is never the
only cue. A plain load writes no appearance record. The default look is
unchanged: computed styles match the base commit element for element.
Enable CLI and WebUI channels in the Case 1 starter. Explain channel
refusals, preserve in-memory drafts, and distinguish failed reads from
successful empty data. Keep room refusal state through failed refreshes
and restore session controls after catalog recovery.
Integrates reviewed source commits 5558fe020 and a1ba9cd56. Independent
review R2 approved both recovery fixes; append-only records preserve
controller and implementation evidence.