Commit Graph
4716 Commits
Author SHA1 Message Date
jason.woltje ce4fe048bb fix(discord): the connector's STOP refusal names the journal by label (#1873)
Re-check 3 found connector.start() still quoted the journal directory in
its STOP refusal, and the CLI passes a DiscordError through unchanged.
It now uses journalLabel like the CLI and journal refusals. The connector
test asserts the path is absent: 22/1 on the old source, 23/0 after.
2026-11-11 10:22:14 -06:00
jason.woltje 63e2a574af fix(discord): a native fs error never prints its path: CLI choke point and journal labels (#1873)
The discord CLI's top-level handler prints a native Node error that is not
already a labelled refusal as 'cannot <syscall> <label> (<CODE>)'. The journal
and approvals readers and writers name the file by label at the source, so
conversation, control-board and webui callers get the label too. The session
directory check moves to engine-pi.mjs as sessionContinues(). Exit codes and
refusal codes are unchanged.
2026-11-11 10:11:11 -06:00
jason.woltje d9023e28db fix(discord): check-then-read gaps name the file by label, never the path (#1873)
A token file, context file, tool root, data root or setspark key file
that changes between its check and its read failed with a native error
whose message carried the host path. Each read now names the file by
label and code (quietRead): same error object, type and code, so exit
codes, refusal codes and check order are unchanged.
2026-11-11 09:50:14 -06:00
jason.woltje c3fe6b77ad fix(discord): journal.mjs takes BINDING_NAME from errors.mjs, not binding.mjs (#1873)
The journal label import pulled binding.mjs and its config readers into
every journal.mjs importer. The queue test repo copies only journal.mjs
and errors.mjs, so its genesis failed with ERR_MODULE_NOT_FOUND (webui
reads.test.mjs red, 4 tests). BINDING_NAME now lives in errors.mjs and
binding.mjs re-exports it, so its export is unchanged.
2026-11-11 08:53:31 -06:00
jason.woltje 809b1b0100 test(control-board): board API body carries the seen marks label, not the path (#1873)
The board API returns a scan refusal in its JSON error body. The test
starts a board on port 0 over a seen.json that is not an object and
checks the body names seen.json by label, with no sentinel or scratch
path. It fails on BASE, where the body quotes the host path.
2026-11-11 08:53:31 -06:00
jason.woltje 4a9d9d4b89 fix(business): old-path re-export for system-config-problem.mjs (#1873)
rename-b-identity pins one packages/business/src module per moved
organization module; the review round's new module needs its shim.
2026-11-11 08:53:31 -06:00
jason.woltje 87db40e0cd fix(cli,seat,discord,organization,control-board,harness,plugins,webui): review round: launcher reasons, session, discord reads, mosaic-config and keys by label (#1873)
Review "changes 8" fix round for org-redact6.

- launcher: a harness module's throw names its system code or class,
  never its message; preflight, loader, bundle, checkout, sandbox and
  route text is relayed with host paths replaced (relay()).
- claude-code and pi preflights name MOSAIC_CLAUDE_BIN and the
  repository's node_modules, never a path; persona and skill bundle
  errors and sandboxProblem name their file or the sandbox by label.
- seat session: thread store, legacy store, overlap, busy lock and
  newRun refusals name the place under the data root.
- discord: unknown keys, binding names and turn ids are described when
  not plain; approvals, journal and repository read errors name the file
  and code; git drops stderr lines that carry a host path and a spawn
  failure names its code only.
- seat, control-board: registration fields and seen keys are described
  when not plain; invalid seat name and layout state the constraint.
- organization: the registry names the config directory and registry
  file by label; --project is quoted only when it is a plain name;
  systemConfigProblem moves to its own module.
- scripts/mosaic-config.mjs: the config by where it came from, the data
  root and its marker by role, keys and arguments only when plain, and
  error codes in place of raw messages. Exit codes unchanged.
- webui sso-config: an unknown console key is named only when plain.

Sentinel tests in each package's path-redaction-labels file fail on
BASE; pins that quoted a path now assert the label and its absence.
2026-11-11 08:53:31 -06:00
jason.woltje f3f33a6171 fix(organization,cli,discord,seat,control-board): refusals name the field or file by label, never a host path (#1873)
redact6. A refusal names the field, the file or the instance. It never quotes a
config value, a host path, or a raw e.message or stderr that carries one.

- organization: checkCredentialRef names the field and each forbidden root by
  label; OrgError keeps .path only for a document field ($...); the registry,
  fence and lock refusals use the quiet helpers (call and code).
- cli: loadSystem never relays mosaic-config stderr: instance refusals keep
  their code and names, everything else reads as a field and its constraint
  against a labelled config. Project-key, access warning, halt gate, sessions
  file, launch socket, harness relays and the workspace rename name labels.
  Launch-log reasons read a Node system error as its call and code.
- scripts/mosaic-config: configuration not found names where the config came from.
- discord, seat, control-board: config, token, context, tool-root, journal,
  binding, registration and seen-marks refusals name labels.

Exit codes, refusal codes and check order are unchanged. Each new or changed
test fails on BASE be698e602.
2026-11-11 08:53:31 -06:00
jason.woltje 5320e3d2c0 test(cli): the lifecycle fixture host answers definitionScan (#1873)
A4's hire writes an org:hire-<uuid> definition, so the chief's in-session
launch of its hire now reaches the launcher's held-secret check. The fixture's
op table lacked definitionScan and refused invalid-host-request; process.mjs
answers it.
2026-11-11 08:49:15 -06:00
jason.woltje 16cdf5cdc1 fix(organization): an approved hire and the boot copy definitions run the definition check (#1873)
The hire service takes the bus's definition check as scanDefinition and runs
it on the stored proposal entry before preview, check or apply goes further,
and on the approved entry again just before the writers. An approval whose
definition holds a credential the store came to hold after the raise refuses
definition-holds-credential before the decision is consumed; nothing is
written. Without the hook the entry still goes through assertClean whole.

Bus start and credentials.issue also check the boot copy snapshot's
definitions (A4 root agents file definitions, re-applied on every reload),
which no organization file holds.
2026-11-11 08:49:15 -06:00
jason.woltje 1a45a7274b fix(organization): credentials issue and rotate refuse a value already in an organization definition (#1873)
credentials.issue runs a check after it normalizes the value and before the store, the
organization file or memory changes: the value must not appear in the definitions of any
organization the bus serves (instructions, personality and every other field or key). A hit
refuses definition-holds-credential naming the organization, definition and key, never the
value or the credential. An organization file the check cannot read refuses
definition-scan-unreadable (new TI entry). The check runs on the bus loop inside issue, so a
launch admission can no longer pass and then see its definition text become held before spawn.
The final admission re-scan stays as the backstop for values held outside the verb.
2026-11-11 08:49:15 -06:00
jason.woltje 17eecb2d65 fix(organization): final launch admission re-checks the org definition against held credentials (#1873)
Review round (P2): a credential issued between the launcher's definition scan and
spawn could make text already in the definition a held value; a same-date rotate
leaves the organization file's revision unchanged, so the revision fence missed it.
The bus's final request.admission for an instance, the last bus op before spawn,
now runs the held-secret check on the org: definition the instance names, on the
bus loop that issue runs on, and refuses definition-holds-credential.

Self-found: check() takes the scanned entry from the same read as resolveInstance,
before any await can let another check replace the shared organization.
2026-11-11 08:49:15 -06:00
jason.woltje 8fec494694 fix(organization): bus start and launch check org definitions against the held credentials (#1873)
T1 follow-up. A definition's text becomes an agent's contract, but a load ran the
held-secret check with an empty store. Bus start now checks every definitions entry
(the boot copy's org: entries and the file's own) with the live store, and the
launch check asks the bus the same question for the org: definition it just read,
before a contract exists. A hit refuses as definition-holds-credential, a new code
in the refusal table; the refusal names the definition and key, never the text or
the credential. bus start records the refusal in the boot status.
2026-11-11 08:49:15 -06:00
jason.woltje 4a122699a0 fix: preserve tester session identity through cleanup (#1873) 2026-11-11 08:22:31 -06:00
jason.woltje f382699d47 fix: bound tester supervisor cleanup and verify fast sessions (#1873) 2026-11-11 05:13:47 -06:00
jason.woltje 5e93356cfa fix(authority): close chief grant and root fire review gaps (#1873) 2026-11-11 04:19:33 -06:00
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