4750 Commits
Author SHA1 Message Date
jason.woltje 89205a6163 feat(authority): resolve hire, fire and role changes through pinned human decisions (#1873) 2026-11-11 04:19:33 -06:00
jason.woltje 6342940634 test(cli): clarify cleanup timing limits and namespace failures (#1873) 2026-11-11 03:49:22 -06:00
jason.woltje cfd7e612d3 test(cli): bound supervisor cleanup with a fixed process inventory (#1873) 2026-11-11 03:20:27 -06:00
jason.woltje afe131444c test(cli): retain default service template coverage (#1873) 2026-11-11 02:34:50 -06:00
jason.woltje 0b52249cc3 test(cli): measure legacy and query inner timers at the supervisor (#1873) 2026-11-11 02:11:48 -06:00
jason.woltje b7c9eee603 fix(organization): definition writes go through the bus with the held-secret check (#1873)
Review P1: the CLI wrote definitions with a no-op secret check. setDefinition
now refuses definition-secret-check-required when a write that adds or changes
an entry has no assertClean. New bus verbs definition.import|invent|edit|remove
run the writes for the business's human at the cli with the credential
store's assertClean; the tester, agents and readers refuse. The CLI's
definition writes call the bus and map its codes to messages that never
quote the definition.
2026-11-11 01:15:12 -06:00
jason.woltje 1d07df0d61 feat(organization): org definitions, template hash and diff reads, definition verbs (#1873)
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.
2026-11-11 01:15:12 -06:00
jason.woltje be698e602b test(cli): json-redaction pins follow the label refusals
The review round changed the config-command relay and the org document
refusal to name the file by label. cli/tests/json-redaction.test.mjs still
required the host path in those two CLI refusals. They now require the
label and the path's absence. The direct scripts/mosaic-config.mjs checks
and the loadSystem check (PL1, deferred) still require the path.
2026-11-11 01:04:15 -06:00
jason.woltje d085b16136 fix(organization,cli): review round: raw fs and git errors, console entry, config, document and --vars refusals name labels
Sol review (org-redact5, changes 6):
- org-service: Node filesystem errors become "cannot <syscall> <label> (<CODE>)";
  code, syscall, deleteStep and deleteReceipt stay. git failures name the verb
  and exit code, never git's stderr.
- org-console: consoleStop names "another checkout", not the entry path. Exit 3.
- config-command: the system config is named by where it came from. Exit 3.
- cli org: documents and raw fs errors are named by kind and code. Exit 2.
- instances: --vars file named by flag and kind. Exit 4.
- hosts: a refused command is named by the basename refusedCommand matched.
- role.mjs (T1): the change is back to the readJsonFile label only.
Sentinel tests for each; pins in last-values, rename-b-commands, org-recovery.
2026-11-11 01:04:15 -06:00
jason.woltje 4c8bf9ca4e fix(organization,cli): refusals name the file by label, never by host path
PR #1916 rule: a refusal names the field or file, never a config value
or a host path. readJsonFile's three refusals and parseJsonFileText now
name the file by its `what` label only. Callers whose label alone is
ambiguous add a name that already passed a strict pattern (business id,
role name, org id, turn id, project key) or a fixed path under the data
root. Exit codes, refusal codes and check order are unchanged.

organization: util, access, project, config-command, library-seats,
org-service deleteOrg receipt, agent-levels regex; T1 files role.mjs and
organization-config.mjs change only the readJsonFile label.
cli: access lock, accounts owner, host state/lock, notifier journal,
notify-config, run-evidence, dev-fixture, git-socket, org-bus, org-console.
discord: turn record label names the turn id.

Sentinel tests (sk-test-NOT-A-SECRET-1234 as a path component) cover each
changed refusal and fail on BASE; old path pins updated.
2026-11-11 01:04:15 -06:00
jason.woltje e588fb32cb test(cli): band the P10 inner timer at the supervisor, not around cleanup (#1873)
The row299 root-side pair (mos-1409) spans supervisor start-up and
clean_session, which scans every /proc entry on the host. With about
1,850 processes that adds 0.6 to 1.6 s, so the unchanged inner probe read
2.5 to 3.9 s and failed the [1.25, 2.75) band, while the 1 s inner-timer
mutant read 2.06 to 2.38 s and passed. Widening the bound alone would
have kept that mutant alive.

A second observer pair, also confined to the private test copy, brackets
only the inner timer: begin just before the supervisor starts its sleep,
end at the start of finish(). It writes only for the SO_PEERCRED peer and
at most one pair. The unchanged band now applies to that interval
(2.01 s on base, 1.00 s and 3.00 s for the 1 s and 3 s mutants, so both
fail). The root pair remains required and must contain the timer pair;
probe-inner-timeout already places it before the outer bound.
2026-11-11 00:30:10 -06:00
jason.woltje e3577e9d31 fix(case2): give the case2 fixture host the request-host ops (#1873)
test-case2.sh failed 8 of 21 steps on base from S3b on. Bisect names
f23a998ee (#797, "close project archive admission races") as the first
bad commit: it made every launch call request.admission and updated each
in-process launcher fixture except scripts/case2/launch.mjs. The fixture
host refused the op with invalid-host-request, so no launch started; the
S6/S7 "unauthenticated" and S8/S10 reds followed from the missing launch
capability, not from a separate session change.

The fixture now spreads requestHostOps from packages/cli/tests, the shared
map every other fixture uses, so it tracks the production protocol.
2026-11-11 00:30:10 -06:00
jason.woltje 99ce579e3c fix(plugins): keep imports on the verified package pin (#1873) 2026-11-11 00:11:13 -06:00
jason.woltje b44b14c7df feat(plugins): install pinned instance packages and declared tools (#1873) 2026-11-11 00:11:13 -06:00
jason.woltje 85858938fc docs: design credential bindings for agent copies 2026-11-10 23:52:29 -06:00
jason.woltje 6415e81ee7 docs(run-sheet): check config directory ancestors before accounts init (#1873)
mosaic accounts init refuses accounts-unsafe since #1919 when a folder above the
config directory is writable by another user. Step 3 now gives the read-only check.
2026-11-10 23:43:55 -06:00
jason.woltje 15225c5e1c test(webui): verify applied light overrides and the U5 shot recipe (#1873) 2026-11-10 23:40:06 -06:00
jason.woltje cb9580f6fb fix(webui): explain conflicts that reopen a read-only task (#1873) 2026-11-10 23:40:06 -06:00
jason.woltje c0e08f81ea fix(webui): reopen Archive conflicts on the current task (#1873) 2026-11-10 23:40:06 -06:00
jason.woltje f7813339b5 test(refusals): detect escaped malformed identifiers (#1873) 2026-11-10 23:16:26 -06:00
jason.woltje 3e82bc16c3 fix(refusals): restore validated identifiers already shown by the system (#1873) 2026-11-10 23:16:26 -06:00
jason.woltje 57052c53dd fix(organization): old-path re-export for config-dir-trust.mjs (#1873)
rename-b-identity requires one packages/business/src shim per
packages/organization/src module.
2026-11-10 22:26:46 -06:00
jason.woltje 930182b54a fix(organization): accounts config directory checks every ancestor (#1873)
assertAccountsOwner refused a symlink or non-directory on the config path,
but checked owner and mode only on the leaf. An ancestor owned by another
user, or writable by others without the sticky bit, let that user rename the
directory away and put their own accounts.json in its place. Since M3 that
file decides root authority on a deployed Console.

One shared helper, config-dir-trust.mjs, now holds A2's rule: every ancestor
belongs to root or this uid and is group or other writable only with the
sticky bit; configDir itself is this uid's and not group or other writable.
The accounts store (load, init, lock, locked write, mosaic accounts) and the
root agent file's writer both call it, each with its own refusal code. A2's
messages are unchanged.

Accounts refusals no longer quote a path: the config directory and accounts
file uid messages name the rule and the uid only (redaction rule, #1916).
A missing config directory now refuses accounts-unsafe instead of a raw
ENOENT.
2026-11-10 22:21:06 -06:00
jason.woltje d0cfebe286 fix(cli): M3 re-check: provider denials answer 403; A2 test owner carries rootOwner (#1873)
The rehearsal provider matches routes on the parsed pathname, so a
query string no longer turns authorize or token into a 404. Authorize
and token answer 403 to any method before the GET check, and only
discovery and the JWKS answer 405 to another method. R20 now sends a
POST to token and a GET to authorize with a query, and expects 403 for
both.

The upstream A2 agent-levels write test stubbed an owner session
without rootOwner. M3 sets the hook's owner flag from rootOwner, as
sso.mjs grants it, so the stub now carries rootOwner: true, as
system-types-http does. No check changes.
2026-11-10 21:38:17 -06:00
jason.woltje 9acc378555 fix(cli): M3 review round: re-pin migration, rehearsal provider (#1873)
An installed unit must equal the render but for the copy id, so a
re-pin that adds --sso refused render-refused. case2-unit gains --check
(every render check, nothing written) and --check-new (a first install's
checks). stack-upgrade runs both checks before anything stops, and
--next-args reads root's <instance>.args.next: equal before -- but for
--copy, different after it. After the stage it moves the installed unit
files into a new <instance>.replaced.XXXXXX directory and renders a
first install. A failure moves them back unchanged and restarts the old
copy, and confirm swaps the args file. The run sheet gives the commands,
the rollback past the migration included.

A stamped rehearsal now starts an isolated provider on 127.0.0.1 port 0
with a certificate and keys made for the run. It serves discovery and a
JWKS for the live provider ids and answers 403 to authorize and token.
Only the scratch Console trusts its certificate. The gate requires each
provider ready (sso-ready); provider-discovery requires discovery and
JWKS fetches and no code or token request. The provider stops with the
run, and by itself once the run's shell is gone.

The system-path test helper asserts the log's from copy again. The
pinned-copy sign-in requests are bounded, and cleanup waits for each
Console to exit.
2026-11-10 21:38:17 -06:00
jason.woltje db822c5b9d feat(cli): M3 reconciliation: accounts units, stamped renders, rehearsal (#1873)
stack-release refuses a user-unit activate, rollback or pm-on when the
instance shows any accounts sign (stamped target, accounts.json,
console.sso, .deployed), before anything stops. Root renders the system
units with case2-unit.sh, and the op runs with --units external.

case2-unit renders a stamped copy's units only with --sso, console.sso,
an accounts.json that loads and a valid .deployed, and names each
missing step in run-sheet order before any write. Per Jason's ruling of
2026-11-10, a stamped library unit takes --sso and refuses proxy-preview
admission and its proof. The unstamped and dev paths are unchanged.

rehearse runs a stamped copy against rehearsal.invalid with scratch
slot placeholders, a copy of the live accounts.json and the copy's own
.deployed. It refuses sso-not-prepared and accounts-not-initialized
before any rehearsal directory exists, and sso-isolated before the host
starts.

The tests that turned SSO on through the user unit now pin the refusal
and reach the same end state through the system path. The copy tests
start the Console on real scratch prerequisites and pin each refusal.
The run sheet, TOOLS.md and the accounts design record the ruling and
the migration order.
2026-11-10 21:38:17 -06:00
jason.woltje 554bc5116d feat(cli): require dedicated host uid for accounts system units (#1873) 2026-11-10 21:38:17 -06:00
jason.woltje 6db8fb2dce feat(cli): enforce stamped-copy and deployed-instance boundaries (#1873) 2026-11-10 21:38:17 -06:00
jason.woltje 85122dd2bc feat(webui): separate account sessions from root authority (#1873) 2026-11-10 21:38:17 -06:00
jason.woltje 82dcba2d45 fix(agents): A3 hidden checkouts cover every left-out project and refuse links, final admission binds the organization revision (#1873) 2026-11-10 20:27:33 -06:00
jason.woltje 29f7c3af85 fix(agents): A3 excluded checkouts hidden, launch boundary rechecks unions, reload collisions and changed defines refuse (#1873)
- A checkout an earlier launch left for a project the instance may no longer
  work in is covered by an empty tmpfs inside the sandbox; the host copy stays.
- The launcher rechecks every prepared project against the reloaded document
  after preparation, and the bus's final admission takes the instance and
  checks the broker's current unions.
- A project define whose vars changed since boot refuses its copy until the
  next start.
- Instance-name collisions are recomputed at each reload from the frozen root
  claims and the current project defines; every claimant refuses.
2026-11-10 20:27:33 -06:00
jason.woltje 17a48217f6 fix(agents): A3 project unions reach every agent, copies never collide or shadow (#1873)
Review fix round for slice A3.

- Every project's union is recomputed from the current organization file
  at each load. A refused union refuses the project's own copies and every
  inherited root or org agent there: no mount at launch (checkout-skipped,
  project-refused), no thread and no room, each naming the project and why.
- A project exclude removes that project's mount from the excluded agent;
  a narrow reaches the instance's resolved limits, so its real bundle is
  narrowed. A narrow in any project narrows the instance for every launch,
  since one launch mounts every project it works in.
- Two copies whose instance names collide both refuse, naming each
  claimant; the view lists the claimants. An organization instance of the
  same name replaces a root or project copy, at boot and after it, and the
  copy is gone rather than refused.
- Rooms hold only the thread instances that work in the room's project.
- The grid finds an agent's copy by name and project from the bus view,
  never by rebuilding the instance name.
- Real-boot regressions: inherited agents in a refused union, exclude and
  narrow in the bundle, an org override after boot, two organizations each
  running a copy, a root file change while running, and a grid fed by a
  booted bus.
2026-11-10 20:27:33 -06:00
jason.woltje 9d00f9ad54 feat(agents): A3 root and project agent copies at bus boot (#1873)
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.
2026-11-10 20:27:33 -06:00
jason.woltje a3e82efe23 fix(redaction): close independent review findings (#1873) 2026-11-10 19:56:05 -06:00
jason.woltje c8ff8ea128 fix(redaction): omit remaining configuration values from refusals (#1873) 2026-11-10 19:56:05 -06:00
jason.woltje ddf4f895cd fix(rails): close review guidance and coverage gaps (#1873) 2026-11-10 19:42:36 -06:00
jason.woltje 7035ce61b3 feat(harness): expose caller rails and refusal guidance (#1873) 2026-11-10 18:51:15 -06:00
jason.woltje 54bf239e32 test(webui): v1 business golden carries RR's resolve rights (#1873) 2026-11-10 18:31:23 -06:00
jason.woltje a4eed1e219 fix(agents): A2 review round: config check, name rule, drafts and test gaps (#1873)
The CLI agent verbs validate the instance config through loadSystem before
any read or write, and print a fixed line for errors they do not own. The
root writer refuses a config directory with an ancestor another user can
write without the sticky bit. The project HTTP route checks agent names
with the organization ID_PATTERN and answers 400 naming the key.

The Settings level forms take their own class, so the role-instance
handlers no longer catch them, and keep both drafts in module state with
the revision each began at. The organization writer tests now reach the
file's own uid check and test the assistant protection with an assistant
in the fixture.
2026-11-10 18:25:54 -06:00
jason.woltje 1e1bbaa6dd fix(webui): A2 agent level grids scroll inside their card on phones (#1873) 2026-11-10 18:25:54 -06:00
jason.woltje 78bda9d6a4 test(organization): A2 refusal patterns follow var-redact's key-only text (#1873) 2026-11-10 18:25:54 -06:00
jason.woltje 6b39af7888 feat(agents): A2 root and project agent level writers, CLI and Settings forms (#1873)
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.
2026-11-10 18:25:54 -06:00
jason.woltje 96c900d210 fix: preserve read error codes and launcher fixtures (#1873) 2026-11-10 17:54:33 -06:00
jason.woltje 307fe928ac fix: redact remaining file and config refusal echoes (#1873) 2026-11-10 17:06:54 -06:00
jason.woltje e9a079fcc4 fix(organization): validate narrowed arbiter resolve authority (#1873) 2026-11-10 16:51:03 -06:00
jason.woltje 7de9cfccc3 feat(organization): enforce arbiter domain resolve rights (#1873) 2026-11-10 16:33:21 -06:00
jason.woltje c3c192677d test(webui): the layout reload test loads the page through navigate (#1873)
The mark from the previous commit kept the wait off the old page, but
the wait could still start on it and be killed by the reload
("Inspected target navigated or closed", once in ten runs). navigate()
waits for the load; a query makes the same URL load again.
2026-11-10 16:01:01 -06:00
jason.woltje 95ee19cc71 fix(webui): a task dialog closed during the conflict read stays closed (#1873)
Review fix. dialog.close() clears `open` at once but queues the close
event that empties the dialog. A conflict read that resumed in between
still found the old form connected and reopened the dialog, and the
queued event then emptied it: an open modal with nothing in it. The
check is now the dialog's own `open` state and that it still holds the
form. A browser test closes the dialog and releases the read's body in
one task; it fails on the previous commit.
2026-11-10 16:01:01 -06:00
jason.woltje 07dba7f2f5 test(webui): the layout reload test waits for the new page (#1873)
The wait after location.reload() could pass on the board that was
unloading, and the next read then found no board (seen once in ten
runs under load). A mark set before the reload goes with the old page,
so the wait now passes only on the reloaded one.
2026-11-10 16:01:01 -06:00
jason.woltje ceee80d5b7 fix(webui): task board conflict shows the refusal only with the current task (#1873)
A product race, not only a test race. On task-conflict the dialog wrote
the refusal into the old form, turned its buttons back on and only then
read the current task. For the length of that read the form showed the
refusal beside the old task and a Save went out against the old digest.
The conflict test read the title in that window and failed under load.

The old form now stays disabled with "Reading the current task…" until
the read answers; the refusal and the reopened form appear together. A
read a poll overtook no longer reopens on the board's old read; the
dialog reads the task itself. If the task cannot be read the form keeps
only Cancel and takes no save. A dialog closed during the read stays
closed, and a form whose Save is disabled refuses a submit sent past it.
2026-11-10 16:01:01 -06:00