BUILD-LOG.md: append-only union, refactor entries first, then the branch's.
docs/SESSIONS.md: append-only union, refactor entries first, then the branch's.
packages/bus/src/runtime.mjs: keep the organization usage timer and refactor's halted/invalid startup-recovery gate.
packages/harness/src/first-output.mjs: keep the branch's harness-neutral markers; refactor's empty-length rejection moves into the Pi monitor (packages/plugins/harness/pi/model-stream.mjs).
packages/harness/tests/runner.test.mjs: keep refactor's length-continuation cases and the branch's output-policy cases.
packages/plugins/harness/pi/index.mjs: keep the documented GLM limits and the branch's transport-based bundle.
Suites: 6356 pass, 32 fail; all 32 fail identically on both parents
(node_modules symlink EISDIR in pinned-copy and stack copy/release tests,
missing Python jsonschema in two conversation tests).
Rename design r2, "Selector scopes and parser matrix", case2-install row. --org/--business
and --org-file/--business-file are two families, each its own scope: each spelling once, and
a second spelling must carry the same text (file paths compared as given). Missing values
are usage; a flag-shaped or invalid id keeps the business-id refusal; a flag-shaped file, a
repeated spelling and a conflict say why. All exit 4 before anything is read or written.
The persona-import call it spawns still passes --business.
Rename design r2, "Selector scopes and parser matrix". case2-persona-import.mjs takes --org
through selectorScope and case2-precheck.sh through scripts/selector.sh, one scope each. Both
keep their exit codes and their old business-id wording; a repeated or conflicting selector
says why. persona-import now checks the id while parsing, so stage refuses an invalid
selector before it reads anything.
The case2-install fake stack copy carries packages/business/src/selector.mjs, which the real
copy already does (COPY_PATHS takes packages/business/src).
cli.mjs serve and webui-service.sh each take --org as --business in their own
scope through the shared selector rules; the service unit still carries the
canonical --business. A flag-shaped or invalid value keeps each script's old
wording and exit code; a repeated or conflicting selector is a usage error.
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.
mosaic console start --dev-local-owner --dev-insecure-bind ADDR
--dev-allow-host NAME[,NAME...] binds 0.0.0.0 or an IPv4 address and
answers only NAME:<bound port> plus the loopback names. Both flags are
refused without the dev owner, the bind without an allowed name, and a
specific ADDR that is not itself a name. Start and console status print
the no-sign-in warning; the state file records bind and allowHosts.
serve.mjs takes devAllowHosts only with the dev owner; SSO, preview and
tester listeners stay loopback. Writes need Origin http://<same Host>.
Chromium sends no Fetch Metadata from a LAN http origin, so for an
allowed name a write with that exact Origin and no Sec-Fetch-Site counts
as same-origin; an explicit header is still judged by the routes.
chat.js builds its operation keys without crypto.randomUUID, which an
insecure context lacks.
Once organizations existed, Quick Setup was reachable only through
Settings > Organizations > type an ID > New organization. This tree
has no header organization switcher, so the owner of a Console with
organization setup now gets "New organization" in the header avatar
menu, under the Organization picker on Settings > Organization, and
next to the Settings link in the no-organization empty state. Each
opens #/settings/organizations?new=1, which focuses the ID field and
then drops the flag. The form itself is unchanged.
The new browser test covers the menu at 1400 and 390px, the picker
and the empty state. It fails on 6ca7f3b65 and passes here.
"Projects / work / / Conversations / init" had no missing label.
Every project crumb link had min-width:44px, so a short name such as
"work" was padded with blank space after its text, and the separators
around it read as an empty crumb. The 44px target width now comes
from an ::after overlay centred on the link, so the layout box fits
the text on every project route.
The new browser test measures text-to-separator gaps and hit width
on the overview, conversations and conversation routes at 1440 and
390px. It fails on 6ca7f3b65 and passes here.
The organization options in cli.mjs carried no requestRead, so
/api/orgs/<id>/config never offered requests and the project and
conversation pages counted "requests not offered" as a failed read:
"Request status unavailable." on a conversation whose message went
through. The owner now reads request status over the organization's
own bus socket, as the root Console does. Request writes stay off,
per the README Console actions inventory.
The new browser test runs the real cli.mjs serve --dev-local-owner
against a real organization record with only the bus transports
shimmed. It fails on 6ca7f3b65 and passes here.
The endpoint ended after MOSAIC_FIXTURE_TTL_S, but init passed it only PATH, so
the lifetime was always the 14400 s default. Its record stayed after it ended,
and init refused fixture-exists until `dev fixture stop`.
init now takes --ttl SECONDS (0 = no expiry; otherwise 60 to 2147483, the
longest delay a Node timer holds; the default stays 14400), passes it to the
endpoint, and records ttlSeconds and expiresAt. /health and status report them.
A record whose pid is no longer the endpoint is stale: init removes it and says
which pid it replaced. A live endpoint still refuses fixture-exists.
A running bus keeps the endpoint it read at start, so after a replacement
the bus needs a restart; --ttl 0 avoids the expiry in the first place.
The tester-cli test makes about 20 Console reads and chat calls in a
row. Each one spawns TESTER_CLI, which re-executes itself so the nonce
is in its initial environment; both processes statically imported the
client and the broker module graph behind it, though the parent only
spawns the child. The child now imports Client and readProcess after
the re-exec branch, so the parent is a bare node start. Interleaved
over 20 calls at load 19: 258 ms per call before, 192 ms after. The
proof, nonce and argv are unchanged.
What remains is legitimate serial work: about 20 round trips of two
node processes each, about 3 s idle and 10 s at load 20 on 16 cores
(before this change). 30 s left under 3x room on a busy machine, so the
test's hang guard is now 60 s.
The Console-and-bus test in packages/webui/tests/dev-local-owner.test.mjs
registered rmSync(root) as its first t.after hook. Hooks run in
registration order, so root went first, both `bus stop` calls then failed,
and the SIGTERM fallback reached only the outer `bus start`. Each run left
the beta and acme --internal-host processes and their brokers running.
One hook now kills the Console, stops both buses while root exists, waits
for every recorded process (outer CLIs, hosts from their receipts, and the
hosts' children, each matched by start time) and fails if any survives,
SIGKILLing the survivors first so the failure does not leak. Root is
removed last. A mutant that skips the stops fails with six survivors.
packages/bus/tests/dev-local-owner.test.mjs already stopped its bus and
leaves nothing behind.
On a deploy tree node_modules is a symlink. .gitignore's node_modules/
matches only a directory, so git ls-files -co --exclude-standard lists
the link as an untracked file and the fixtures' cpSync throws EISDIR
on it (8 failures in pinned-copy.test.mjs). stack-copy.sh never takes
node_modules from git (it archives COPY_PATHS and runs npm ci), so the
fixtures now exclude **/node_modules from the listing. The copy code is
unchanged.
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.
serve.mjs keeps the bound address as well as the port, so the settings,
queue and business routes answer a request that arrives after close().
browser.mjs keeps an exited leader's group tracked until it is killed at
exit, and close() signals the group only while the leader is alive. The
late-request test is event-based with one owned timeout. New test: the
chromium group dies when the main process dies and close() never runs.
Webui browser and server files (92, pinned-copy excluded): 741 pass, 0 fail.
- serve.mjs read server.address().port in the Host checks. Once close() starts that is
null, so a request on a kept-alive connection threw and was never answered, and the test
file waited forever. The bound port is now stored on 'listening'.
- Killing only chromium's main process left its renderer and GPU children writing into the
profile, so the profile rm failed with ENOTEMPTY. Chromium now runs in its own process
group; close() and the exit hook kill the group, and the rm retries.
- teardown.test.mjs: a request sent after close() starts gets a 200.
- 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.
Combined node --test runs hung in the org browser files. Two causes:
- org-quick: a page's GET /api/config reached the server on a
kept-alive connection after close(server) had stopped the listener.
The handler threw on server.address().port, the request was never
answered, and server.close() waited for it forever. close() in
fixture.mjs now cuts every connection and fails the hook after 5 s
with a message.
- org-delete: t.after hooks run in registration order, so the data
directory was removed while the server still wrote there. The rm
failed with ENOTEMPTY, node:test skipped the later hooks, and the
open server and chromium kept the file running. The three org files
and project-archive-style now register the removal last.
browser.mjs: close() kills a browser that does not answer
Browser.close within 5 s, a call to an exited browser fails at once,
and an unclosed chromium no longer holds the event loop and is killed
when the test process exits.
teardown.test.mjs reproduces each case: 0/4 pass on the old helpers,
4/4 on the new ones.
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.
Quick Setup made organization code import the plug-in loader, which loads
packages/plugins/<kind>/<name>/index.mjs by a computed path. The scanner now
allows exactly that form in the loader file and still refuses it anywhere else.
A 200 or 201 with a body that does not name the expected step as done now
stops at that step as an unknown outcome, with no further writes. Starting a
new draft re-enables the preset, thinking and run controls and clears the
risk checkbox. Browser regression covers both (Sol review P1, P2).
The dev fixture guard was instance-wide: once a fixture record existed,
every organization's bus start refused when a provider credential was in
the environment. One instance could not hold a fixture organization and a
real-model organization side by side.
Bus start now reads the organization file. An organization that does not
route a launch to the fixture launches as configured. A fixture
organization's launch environment drops provider credentials before the
guard runs.
Settings marked every page except appearance and business read only when
send was off. Organization mode always turns send off, so the read-only
style hid New organization and Save organization. Organizations now stays
writable when the server allows org setup. The browser test asserts both
buttons are visible, not only clickable.