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.
Lifecycle Task 2 (#797). A confirmed new-project selection now becomes a
usable product: the code checkout, the organization mapping, the records
repository with the original intent as the owner's spec commit, the chat
association and the working repository, each journaled as its own receipt
(bootstrap:<plan_digest>:<step>) and recoverable after a kill.
- business: addProject is a private exact-add writer under the existing
organization lock and receipt discipline; hold runs a synchronous check
under that lock; findAddition recovers a prepared receipt. Ordinary
writers still refuse every project change.
- bus: prepareBootstrap and executeBootstrap; activateManagedProject
reloads only a receipt-backed additive root and admits it to the broker
after readiness; request.admission host IPC op for the launcher.
- #800: the bound organization revision and association are re-verified
inside the organization lock at the start of execution; a mismatch
records request-plan-stale and performs no effect.
- cli: trackerFor skips reserved products; the launcher refuses to clone a
product the bus has not admitted.
- seat: initializeWorking seeds the working repository, host-only, with no
work ref or push authority.
- schema: append-only migration 3o to 3p widens the request_events kind
list with bootstrap.stale and bootstrap.completed.
The factory profile of scripts/case2-unit.sh now takes the bare pair
--sso --send, ported from the lane's serve_flags rule (b00019548). Either
flag alone refuses, and the proxy preview set and the proof pair still
refuse. Before any unit write, a bounded query run as the stack user,
through the existing config-query supervisor, checks that the stack
user's system config has console.sso; the Console refuses --sso without
it at start (packages/webui/src/cli.mjs:95). The server needs no change.
The library profile is unchanged: still read-only, still refusing --send
and --sso. Taking library --send with forward-auth and proof would need
the #385 refusal reverted in cli.mjs:63, serve.mjs:681 and the service,
release and upgrade scripts, so it is not done here.
docs/guides/case1-business.mosaic-stack.json now launches
claude-opus-5-5 with provider proxy, route anthropic, under account
factory with credential proxy.
Ruling 1: the bus runtime passes launchSettings a key lineage lookup read
from its own credential records (ids only). A new daily key quota takes the
earliest stored since among the old document's daily key quotas in its key's
lineage, and now only when there is none. A reset still sets now, a
caller-supplied since still refuses, and a quota removed and added back in a
later write is new. A failed or malformed lookup refuses proxy-key-refused; a
write that adds no new daily key quota never calls it.
Ruling 2: every limits refresh updates the remembered orphan set, an empty
set when no daily key quota is left, so a quota removed and added back logs
again. In memory only: a restart is a fresh observation.
Ruling 3: pollSettledAfterTick yields on the real setImmediate, captured at
load, inside its deadline.
Tests: p5-key-lineage (writer move counts 160, reset restarts, lookup
failure, orphan remove and re-add), p5-runtime (exhausted on A, moved to B
through the real runtime, refuses), p5-background (barrier with every timer
mocked), token-limit-settings (earliest, no match, re-add, caller since,
malformed answers, default refusal). READMEs: business, plugins, daily-quota,
cliproxy-queue.
Joins row 333 R2 head 0ef269da9 (base 09a847eda: R1 503b91e56, overlay
2627321f6, docs c012b2e0a and ddb3e961f, R2 fixtures 12eba4b59 and
0ef269da9) onto refactor with row 332 landed at 5df0e9d6f. Run with the
union attributes for BUILD-LOG.md and docs/SESSIONS.md. Two files
conflicted; no other path did.
packages/business/src/launch.mjs, import block: 332 changed the errors
import to `{ BusinessError, refuse }` for the proxy route refusals; 333
added `isDailyModelQuota` from plugins/src/model-quota.mjs for the daily
model leaves under limits.tokens.keys. Kept both: the model-quota import
and the `{ BusinessError, refuse }` import. The rest of launch.mjs merged
without conflict (332's checkModels route handling and checkAccount
credential "proxy"; 333's checkTokenLimits keys block).
packages/plugins/README.md, end of file: 332 appended "Per-session proxy
context (P3)" and 333 appended "Proxy usage telemetry (row333 / P5)" at
the same spot. Kept both sections, P3 first, one blank line between.
The resolved files match the earlier 332 R1 join resolution
(join-resolution.patch) line for line apart from hunk offsets.
Commits the 18 product paths of the row 333 R1 repair exactly as the reviewed overlay. Packet hashes: source-tests.patch sha256 a78bc385801b64be270fb9d6e84672c443738a5fce8eb68f7d2a01359a57a770 (40623 bytes), INDEX.json sha256 3eb9b8d10445aa328be3b854385e4792dda2c5712b19d443d485d1b00a18d0f8, changed-source-tests.json sha256 42f770fc3e2384362efd1e416091355c064b6cca22dd40f3b5ef8f7b67e43a52. The committed bytes were checked by applying source-tests.patch to 503b91e56 in a scratch tree and comparing the 18 paths. The approvals are source only: R2 (APPROVE source-only, 2026-10-26 23:32:39Z) and Boyd's first review (APPROVE, source only, 2026-10-27T00:27:55Z), both on HEAD 503b91e56 plus this overlay. No test has run on these bytes yet.