Commit Graph
4818 Commits
Author SHA1 Message Date
jason.woltje 32be3a01bc feat(webui): one task board for every organization, with organization and project filters in the address (#1873, lane sp4)
#/tasks is a system route that shows every organization's tasks on one board,
with the per-organization columns, organization and project chips on each card,
and the organization and project filters kept in the address. An organization
that did not answer is named first with its reason. A move or edit goes to the
row's own organization and is offered only where that organization's own config
takes task changes. #/orgs/<id>/tasks is the same board scoped to one
organization, and a card opened from the system board links to its task there.
A render replaced by a newer one now draws from that newer read instead of
throwing on an empty board.
2026-11-15 03:54:28 -06:00
jason.woltje f97e0d3aeb fix(webui): sp5 re-check notes, a late named Fire leaves a newer Fire's busy state alone, the picked agent row gets the left bar, tests for the picked agent with the state filter and a refused scope (#1873, lane sp5) 2026-11-15 03:32:32 -06:00
jason.woltje eebdb2f0ce fix(webui): sp5 review round, Open addresses the agent and the scoped page marks it, Fire words name the organization, a named Fire keeps its outcome, Cancel restores focus (#1873, lane sp5)
- Open on #/agents goes to #/orgs/<id>/agents?agent=<key>; the scoped table puts that agent first,
  marks it (aria-current) and draws no Open or Actions column (review F1, F2).
- Fire's aria-label, confirm group and receipt use the organization's label (F5).
- A Fire sent to a named organization keeps its receipt after the opened organization changes (F7).
- Cancel moves focus back to the row's Fire (F10).
- agentsPage refuses a scope that is not an organization id instead of listing every organization (F11).
2026-11-15 03:26:18 -06:00
jason.woltje 5373b110ac feat(webui): one Agents page for the whole system, with organization and state filters, Fire sent to the row's organization (#1873, lane sp5)
#/agents lists every organization's agents in one table: name, role, state,
what each works on, organization and project chips. Org and state filters
live in the hash query. A stopped organization is named at the top. A row
opens the agent in its organization; Fire is drawn only where that
organization's own config lets this Console decide, and posts to that
organization's hire route. #/orgs/<id>/agents leads with the same table
scoped to one organization, above the levels grid and held roles. A hired
agent reads as Hired (or Hired from pack X) with its org:hire id as
secondary text. The sidebar and dashboard Agents links open the system page.
2026-11-15 03:13:54 -06:00
jason.woltje 3885f4628b fix(webui): sp3 second review round, #/inbox keeps the system theme, hire links follow each organization's own decide (#1873, lane sp3) 2026-11-15 02:09:04 -06:00
jason.woltje ba9933309b fix(webui): sp3 review round, answers from the system Inbox read route_to and show who raised them, the hire form is linked, the sidebar count stays system-wide (#1873, lane sp3) 2026-11-15 01:10:13 -06:00
jason.woltje 0785af8129 feat(webui): one Inbox for the whole system, with organization filters and answers pinned to the row's organization (#1873, lane sp3)
#/inbox lists every answering organization's decisions from the one GET /api/workspace read, newest
first, each row with its organization chip and a project chip when it has one. Organization and
type filters live in the hash query. A stopped organization is named at the top with its reason.
An answer from the list reads and posts to the row's own organization (/api/orgs/<org>/...), never
the one opened last, and each item can be opened in its own organization. #/orgs/<id>/inbox is the
same list filtered to that organization. No server route was added.
2026-11-15 01:10:13 -06:00
jason.woltje d1b01329bc fix(webui): sp2 polish, an organization's bare address opens its page and the dashboard keeps the system theme (#1873, lane sp2)
- #/orgs/<id> with no page and no trailing slash now opens #/orgs/<id>/organization instead of the
  system dashboard.
- The system dashboard resolves its own appearance (this browser's choice made
  there, else the default) under mosaic-console-appearance-system; it no
  longer shows the theme of the organization opened last.
- A stopped organization's page shows one bus banner: the page's own down
  banner now carries the restart action, and the shell's "Bus unavailable"
  banner hides on that page (display.js paintBus and app.js busDown).
- The dashboard row for a stopped organization no longer repeats "the bus is
  not running" under "bus stopped"; the last start time still shows.
- Organizations table figures keep to one line (white-space, no font change).
- The Active agents status cell was class "st", which picked up the global
  .st::before "?" glyph; it is now ws-st.
- Tests: red-first browser tests for each item, plus the test sp1 lacked: a
  read still in flight at an organization switch keeps nothing.
- org-switch-browser: a stopped organization's page asserts one banner; the
  shell's "Bus unavailable" is checked on its tasks page instead.
2026-11-15 00:44:18 -06:00
jason.woltje c7aecbffa9 fix(webui): sp1 second review round, a read or file save in flight at a switch keeps nothing (#1873, lane sp1)
The second review found two paths the first fix round left open.

- The project-file save captures orgEpoch before its POST. When a switch
  happened meanwhile, it neither paints nor announces the old file's
  outcome on the new organization's page. An opaqueredirect still signs out.
- A live read (reader, get.api), the palette's inbox and tasks reads and
  the board poll keep their answer in `last` only while the organization
  they were sent for is still open. A later cached render on the new
  organization no longer draws the old one's rows.

single-pane test 11 holds acme's file save across a switch to beta and
asserts nothing is announced: red on the previous bus.js ("Saved as commit
cccccccccccc."), green now. The kept-read guard has no browser test.
2026-11-14 23:51:59 -06:00
jason.woltje bb2f79f3d9 fix(webui): sp1 review round, a switch carries nothing across and first run keeps its board (#1873, lane sp1)
- First run (no organization yet) keeps #/ as its board; the router no longer rewrites it to
  #/dashboard, which first run cannot draw.
- An organization switch stops the agents section's request poll and clears what it held. A
  route-change restart chain finishes on the organization it started in.
- A write in flight at a switch drops its answer: an org epoch in bus.js, checked after each
  await. The switch clears every busy flag, receipt and reply, and the doc drafts.
- The bus banner reads org state again only when the route crosses /dashboard, not on every
  hashchange inside one organization.
- The down banner does not repeat its heading as the reason.
- Tests: first run wordmark, poll stop on switch, restart pinned to its organization, settings
  save in flight at a switch, banner without a repeated reason. The org-switch test comment and
  wait match the in-place switch.
2026-11-14 23:19:05 -06:00
jason.woltje e24e7e93a2 feat(webui): one shell, a system dashboard, organization pages as drill-ins (#1873, lane sp1)
#/dashboard is the system landing route: one row per organization with its bus
state, agent count (organization files), project count and needs-you count,
each only when a read gave it, and active agents. Opening an organization from
the sidebar or a dashboard row is a route inside the same page: no reload, a
Dashboard / <organization> breadcrumb, and later /api reads scoped to it through
the existing fetch wrapper, whose organization is now a variable the router sets
(mosaic-org-switch event). #/orgs/<id>/... stays the address. An organization
whose bus is down says so on the dashboard and on its page, with the reason the
workspace read gives.
2026-11-14 22:35:00 -06:00
jason.woltje 387f73a7a4 fix(harness): r127 review round, only a shell's own not-found line names a program (#1873, lane r127)
Review P3-1: a "No such file or directory" line from another program
(cat, ls) no longer counts. Only bash's "line N:", dash's "N:" and env's
quoted form do.
P3-2: a name with a run of 16 letters and digits looks like a key, so it
stays in the run's private stderr instead of the reply.
P3-3: a 16 KiB stderr window that starts mid-file drops its partial first
line, so a cut name ("aude") isn't reported.
P3-4: CRLF lines read the same as LF lines.
P3-5: tests for the name check (tokens without spaces, key-shaped names),
for a cat line, and for the window boundary through the runner.
2026-11-14 21:19:02 -06:00
jason.woltje 61371adb10 fix(harness): an adapter exit 127 names the program that wasn't found (#1873, lane r127)
A claude-code turn on a Console started without MOSAIC_CLAUDE_BIN failed
with a bare "adapter exited 127". The sandbox binds /usr, so the wrapper
/usr/bin/claude is visible, but its exec target /opt/claude-code/bin/claude
isn't bound, and the shell exits 127.

On exit 127 the runner now reads the tail of the turn's stderr, finds the
shell's "not found" line (bash, dash, env) and adds the program's basename:
"adapter exited 127 (command not found: claude)". It never shows a host
path or anything that isn't a plain program name; a 127 without such a
line keeps the bare reason.

The deploy side (MOSAIC_CLAUDE_BIN on the Console unit) is a Lead-owned
script change, written up in the lane report.
2026-11-14 21:07:14 -06:00
jason.woltje b913f0f4c8 fix(bus,webui): pr2 review round, WebUI receipt carries routeFrom, design note, member and project tests (#1873, lane pr2)
The WebUI packs receipt passes routeFrom through on each hire line; the WebUI
README and TOOLS.md name it. The starter packs design doc gets a dated note
for the 2026-11-14 route ruling. New bus tests: a member pack hire on the
assistant route marks hired and awaiting-owner lines routeFrom assistant, and
a project pack with no chief route hires into the project on the assistant's
harness and model.
2026-11-14 18:37:28 -06:00
jason.woltje bfd2cc5e79 fix(bus,cli,webui): a pack hire falls back to the org assistant's harness and model (#1873, lane pr2)
pack.state gave a pack hire only the chief of staff's resolved route, so an
org whose chief has no model refused every hire pack-hire-route-missing. The
route is now the chief's (launch.by), else the org assistant's (the instance
whose definition is assistant; the one named assistant when there are several,
else the first in key order), else null. Both resolve through resolveInstance,
need harness and model, and carry thinking when set. pack.state adds routeFrom,
and each hired or awaiting-owner receipt line carries it. Refusal next step,
CLI hint, WebUI hire reason and the bus, CLI and TOOLS docs say the refusal now
means neither has a route.
2026-11-14 18:32:01 -06:00
jason.woltje 3e53af3941 fix(bus,webui): pd review round, supersede after apply, one pack hire loop per org, hireError, fromPack (#1873, lane pd)
Review pd-review.md (changes 6). Fixed:
- P2#2: the owner's pack hire withdraws a pack-raised inbox decision only
  after its replacement hire applied; a replacement that refuses (route
  missing, prepare refusal) leaves the old decision open in the inbox.
- P3#3: an apply whose hire loop throws after the apply committed still
  answers the apply receipt, with no hires and the code as hireError; the
  route passes a code through and the card says the hires did not run.
- P3#5: the packs read marks an awaiting-owner line this pack's own hire
  raised fromPack: true; the owner's card counts it in "Hire the N".
- P3#6: one pack hire loop per business; a second refuses
  organization-busy and raises nothing.
- P2#1 docs: TOOLS.md and the bus README say no member surface calls
  pack.hire.console yet (the member surface is a recorded follow-up).

Deferred: P3#4 project agents naming their pack (projectUnion is shared
with the runtime).
2026-11-14 16:09:46 -06:00
jason.woltje 359b3299b4 feat(bus,cli,organization,webui): applying a starter pack is the hire decision (#1873, lane pd)
Ruling 2026-11-14: a person's pack apply records one hire decision per
proposed hire and applies it in the same act, through the hire-decisions
path, with the evidence an inbox approval leaves.

- bus: pack.apply.console runs the pack hire loop; pack.hire.console hires
  an applied record's open proposals. The owner's apply approves flagged and
  credential lines; a member with org.hire gets the unflagged hires and the
  rest open as awaiting-owner decisions; an agent apply still refuses
  human-required. Project packs hire into projects.<p>.agents. Nothing is
  hired twice. Credential lines are approved, not bound. New refusal codes
  pack-not-applied and pack-hire-route-missing. The instances read names the
  pack each hire came from; the packs read lists hired agents and open
  proposals with reasons.
- organization: pack.hire.console maps to org.hire; instances carry
  hireDecision.
- cli: mosaic org pack hire ID PACK [--project P]; apply prints the hires.
- webui: pack card shows N agents hired, open proposals with reasons and
  "Hire the N proposed agents"; Agents and organization pages redraw on
  mosaic-org-changed; agent cards say "from pack X"; Fire (not on the chief
  or assistant) removes the card at once with a receipt.
- docs: design doc section 3 dated note, TOOLS.md, package READMEs.
2026-11-14 15:40:10 -06:00
jason.woltje 65f7288cea fix(webui): template save maps the new template-exists refusal to 409, not outcome-unknown (#1873)
f6c added template-exists for a save under a taken name. The template
route had no entry for it, so a duplicate save answered 503
outcome-unknown. Map it to 409 and move the SP8 pin.
2026-11-13 20:14:49 -06:00
jason.woltje 211b32ef1a fix(cli): f6c re-check, a failed released-marker write still removes the start lock; docs name the new warning and code (#1873) 2026-11-13 20:14:21 -06:00
jason.woltje 163972e08e fix(cli): f6c review round, released start locks, ENOENT and path cut (#1873)
P3-1: releasing a console start lock marks it released through its own
descriptor and removes the path only while it is still that file; a
released lock put back by a racing clear is cleared by the next start or
stop.
P3-2: org definitions reports unknown-organization only when the
organization file itself is missing; another missing file names the
operation.
P3-3: the path cut also covers a path after ':' or inside backticks.
2026-11-13 20:14:21 -06:00
jason.woltje 23e504f331 fix(cli,bus,organization): E2E6 lane f6c, refusal lines name the next step (#1873)
1. Stale bus host lock refusals from bus status, start, stop and restart
   name mosaic bus recover ORG.
2. org definitions: unknown org, unknown or malformed name and bad id give
   one refusal or usage line, no stack trace, no host path.
3. Pack, board and workflow refusals print their hint as template save does;
   tested with the real CliError transport shape.
4. console start and stop clear a console/start.lock whose named supervisor
   (pid and start time) is gone; a live or unnamed holder refuses.
5. Template save keeps free text verbatim and writes no placeholders; the
   preview lists each field that mentions an org-specific name.
6. A request the shim never sent (no socket, connect refused) exits 5 and
   the CLI reports bus-not-running with the start and recover line.
7. autonomy-precondition carries the check that failed, including on the
   hire paths.
8. Saving under an existing template name refuses template-exists with a
   next step; refusal table entry added, channel-proof pins moved.
2026-11-13 20:14:21 -06:00
jason.woltje 23b5e24ebc fix(webui): E2E6 re-check round, consumed-decision wording, autonomy focus and read errors (#1873)
Re-check finding 1 (P2): the wording for refusals that can come after the
decision is consumed told the user to look for the proposal "as answered"
in the inbox and otherwise read "nothing was applied". The bus inbox lists
no closed decision, so a consumed one drops out of it and the user would
read the false claim. Those codes now say only that part of it may already
be applied and to check the Agents page. decision-http tests assert no
"nothing was applied" in any case and no inbox pointer for those codes.

Re-check P3-b: the autonomy controls (Save, Discard, notices, review
summary and buttons) carry data-focus-key, so a focused button in a clean
form gets its focus back after the poll repaints the section.

Re-check P3-c: a failed autonomy read keeps the last read only while the
page holds the form (dirty, or focus inside it). A clean form shows the
read error. org-live-edit-browser test 4 covers both.

Re-check P3-a (harness-off after the consume through a corrupt prior
settings file) is deferred: wording it exactly needs the bus to mark the
phase on the refusal.
2026-11-13 20:05:10 -06:00
jason.woltje 266f3a45ff fix(webui): E2E6 review round, apply-phase refusals, held sections, tab fade, header pins (#1873)
Review finding 1: a hire start refusal the apply step can also raise after the decision is
consumed (decision-stale, organization-instance-*, organization-refused, definition-missing,
invalid-request and the rest) no longer says "Nothing was applied". It says that part may be
applied when the inbox shows the proposal as answered, and to check Agents first. Only codes
that prepare() or the route checks alone raise keep "Nothing was applied".
Review P3: a failed workflows or autonomy read keeps the last good read, so an open editor or
a dirty form stays through it. Focus holds a section only in a field being typed in; a focused
button is rebuilt and gets focus back by data-focus-key. Autonomy has Discard changes. The
project tab fade is 0px when every tab fits.
s8 and s9 header pins at 1440 now measure from the content edge: 1418 was 100vw-based, ran
under the page scrollbar and is the P2-11 clipping itself.

Correction to the previous commit's message: P1-2 is the unknown project, agent or harness
wording; P2-4 names the refused field; P2-6 is the Queue refusal reason; P2-8 is "bus stopped".
2026-11-13 19:52:26 -06:00
jason.woltje 0c35eeed9d fix(webui): E2E6 round, live edits survive refresh, org page layout, decision refusals (#1873)
P1-1 an open workflow editor, a dirty autonomy form and a pack's picked project keep their
node, draft and focus across the 10-second poll and an org-changed repaint.
P1-2 decision-http names the refused field and keeps the bus code and detail.
P1-3 long editors span the full width below the org page split.
P2-1 workflow roles resolve as definitions, templates and human; P2-2 a pack apply re-reads
workflows, autonomy and board and keeps its receipt; P2-3 pack preview fits its column;
P2-4 unknown project, agent or harness say so instead of outcome-unknown; P2-6 a stopped bus
reads "bus stopped"; P2-8 Queue says why it is unavailable; P2-9 pane settings values are not
clipped; P2-10 spend wording; P2-11 project header stays in its column; P2-12 project tabs
fade at the edge until scrolled to the end; P2-13 Restart bus and Stop bus line up in panes;
P2-15 backticks in chat text render as code.
Minor: Same-as commands wrap between words; harness checkboxes share a row.
Tests: org-live-edit-browser, e2e6-layout-browser, chat-replies and decision-http additions.
2026-11-13 19:24:48 -06:00
jason.woltje e6ab8bc1fd feat(bus,plugins,organization,cli,webui): SP9 remove system types outright, bus schema 3v-3w drops project_system_types (#1873)
Jason's 2026-11-12 ruling: the only deployed system is the dev environment on
dragon-lin, so the system-type plug-in kind goes with no retired kind, fence,
conversion or new refusal code.

- plugins: system-type leaves KINDS; system-types.mjs and the five modules are
  deleted. An org file naming the kind refuses unknown-kind.
- organization: the plugins.system-type check, systemTypesFence and the
  system-types.console right are removed.
- bus: system-types.console, project.system-types[.set], the system-type half
  of the tester check, the runtime reads and system-type-not-enabled are
  removed. Step 3v-3w drops project_system_types, its index, its four guards
  and its rows; SCHEMA_VERSION is 3w (32 tables, 126 triggers, 5 views).
- cli: mosaic org system-types and mosaic project system-types[-set] are
  removed; stack-release COUNTED and the budget supervisor's closed list move
  to 3w.
- channel: the project.system-types verbs are removed.
- webui: the System types page, route, dashboard row, chips and project
  selection are removed.
- docs: REQ-SYS-1/2 now describe starter packs; README system-type text and
  the requirements titles follow.
2026-11-12 21:02:46 -06:00
jason.woltje 0251acdad9 fix(packs): SP8 re-check, close before unlink cannot skip it; check the whole reason before the cut (#1873) 2026-11-12 20:05:32 -06:00
jason.woltje 13560660ae fix(packs): SP8 review round, single-pass placeholders, CLI hints, pins (#1873)
- template-save: one alternation pass replaces every literal, so an
  instance named root, org or agent never rewrites a placeholder already
  written in. checkPack's refusal message is passed on as the reason. A
  write or fsync failure removes the partial file it created.
- broker #templateFail drops a reason the credential check refuses.
- cli template words: humanTransport throws a CliError named by the bus
  code; a known code now gains its hint (the test transport throws the
  same CliError).
- template-http: the header notes serve.mjs strips /api/orgs/<id>.
- pins: the two .console verbs in console-decide and request-console;
  the packages/business/src re-export for template-save.mjs.
- tests: placeholder collision, a held-value key masked as [key], and a
  save after a real pack.apply.console keeping {id, version}.
2026-11-12 20:05:32 -06:00
jason.woltje 08e50e513f feat(packs): the owner saves an organization's shape as a template (#1873 SP8)
template-save.mjs captures the org (or one project) as a starter-pack JSON under
<config>/templates/<owner>/<name>.json: copy material and instance credentials
stripped, role credential request lines kept, owner/org/project/agent/root names
replaced by placeholders in free text, at most 8 hires, packs entries {id, version}.
A held credential value in kept text refuses template-secret-suspected naming the
field path. Preview then save with the preview's digest; O_EXCL 0600 write, a
second save refuses.

Bus: template.save/template.list for the owner's CLI cap, .console names for the
owner's Console cap, null in VERB_RIGHTS. CLI: mosaic org template save|list.
Console: GET /api/templates, POST /api/templates/preview, POST /api/templates.
2026-11-12 20:05:32 -06:00
jason.woltje a9d72b3cd1 fix(business): CB5 old-path re-export for copy-binding-status.mjs, the rename r2 B guard (#1873)
Every packages/organization/src module has a one-line re-export at its old packages/business/src
path; the CB5 status table had none, so rename-b-identity refused it.
2026-11-12 19:19:23 -06:00
jason.woltje 4b35e3c1d2 fix(bus): CB5 re-check, a running or failed revoke outranks copy-service-unbindable; unbind summary tested (#1873)
- copy-launch status(): a fenced or closed copy falls through to the gate check, so the owner
  sees credential-revoking or credential-revoke-failed for a binding issued to a copy whose role
  also needs a service a copy cannot bind.
- copy-bindings: an unbind raise reads change 'unbind' in the raise answer, every inbox and trail.
2026-11-12 19:19:22 -06:00
jason.woltje 56aeefb370 fix(bus,webui,cli): CB5 review fixes, binding summary on the inbox, raise and trail reads, copy-service-unbindable status, Console allowlist (#1873)
- broker: the hire.raise answer, the inbox console preview and the trail read carry a
  binding as {instance, change} only; the raise event keeps the full proposal the resolve reads.
- copy-launch status(): a copy whose role needs a service a copy cannot bind reads
  copy-service-unbindable (new fixed status text) instead of a gitea binding step.
- cli instances: an unbound copy's line names its missing services.
- webui serve: the instances verb rebuilds each copy binding from the fixed status table.
- bus.js reads the binding change field only.
- browser tests: system Chromium starts with --disable-extensions; the A3 booted-bus test passes it.
2026-11-12 19:19:22 -06:00
jason.woltje fdbc34413b feat(webui,bus): CB5 Console operator flow, binding confirmation and next steps, copy binding status on the Agents grid (#1873)
The bus instances read adds a copy's binding status only when the copy was
bound at boot or is unbound for want of a bindable service, never to a
tester. decision-http returns an allowlisted binding (instance and change)
on a binding confirmation and fixed next-step text on an approved resolve
and on a binding hire start. The decision page names the copy and the
change; the receipt shows the next step; the Agents grid shows can't launch
with the binding's next step. Every new string is fixed text.
2026-11-12 19:19:22 -06:00
jason.woltje c5bf71ab08 feat(bus,cli): CB5 operator status, each copy's binding status and next step in the owner's instances read and mosaic org instances (#1873)
The launch gate's status() runs the same checks a launch runs, without the loss report, and
answers a fixed { status, next } from organization copy-binding-status.mjs (design section 6
texts plus issue-required and ready). A tester's read never carries it.
2026-11-12 19:19:22 -06:00
jason.woltje dbb69c8a41 test(bus,cli): CB5 two-organization acceptance and the CB4 deferred P3 tests (#1873)
The two-org test boots real buses for two organizations with a shipped
Gitea-requiring thread role, binds each copy through its own owner
decision and verified issue, and checks identity separation, record
isolation, token isolation, B through A's revoke, archive and delete,
and refusal of a replayed raw A binding or A token in B.

P3-1 shadowed cleanup, P3-2 runtime revoke wiring, P3-3 ordinary-run
stop. Each passes on BASE; guard-removal mutants recorded.
2026-11-12 19:19:22 -06:00
jason.woltje 34dd331016 fix(packs): SP5 re-check, the inline pin digest is canonicalJson's for integer-like keys (#1873)
digestPrd rebuilds an object, so integer-like keys come first and the JSON
differs from canonicalJson, the digest the pack apply records. An inline
entry with such a key (a credential scope map group) refused as stale on
every raise. The broker now builds the canonical string itself, a local copy
of util.mjs canonicalJson, so the sandbox import closure is unchanged.
2026-11-12 19:05:33 -06:00
jason.woltje 808b2b7cbf fix(packs): SP5 review round, broker import closure, no-op spend, role.hire before the read, template-named steps (#1873)
The broker no longer imports organization util or definitions; the proposal
pin digest is digestPrd and the inline composition is built in place, so the
sandbox client import closure project-runtime.test.mjs pins is unchanged.
An agent apply the runtime skips (a repeat that adds nothing) never reaches
beforeWrite and spends no grant. A proposal raise checks role.hire before it
reads the packs list. An agent apply keeps a workflow step only for a pack
role the organization defines or one the pack pins to the template of that
name; an inline definition sharing a template's name is left out. Tests: a
repeat apply spends nothing, apply then raise end to end with the broker
booted from the file, a project pack, the template-name shadow, and a chief
without role.hire; the revision refusal is matched exactly.
2026-11-12 19:05:33 -06:00
jason.woltje 5ffe38e3b2 feat(packs): the chief applies a pack's non-definition half and raises hires from its proposals (#1873 SP5)
pack.preview, pack.apply, board.set and workflow.set accept an agent session whose role
lists organization.change (Jason 11-12 Q2): the check runs before the read, and the grant
is spent once after a write commits. A role without it gets decision-required. The packs
read lists packs for a within-role holder and spends nothing.

An agent apply never writes a definition. A definition a human apply would add is marked
not-written and its hire is pinned to the template or the inline entry. A workflow step
naming such a definition is left out and listed, and a workflow left with no steps is not
written. A definitions rename or a saved template refuses human-required, with the writer's
own guard behind both.

hire.raise takes {proposal: {pack, project?, hire}, route, reason?} from the chief. The broker
reads the recorded proposal, checks an inline pin's hash (starter-pack-proposal-stale) and
launch.noHire, and hands the pin to the hire service, which refuses a moved template or
definition as decision-stale or definition-rev-conflict.
2026-11-12 19:05:33 -06:00
jason.woltje 991eb8c332 fix(webui): SP6 review round, the writer's workflow name rule, no silent replace, preview redraw (#1873)
The workflow editor checks the writer's PLAN_NAME (lowercase letters, digits
and dashes, starting with a letter), so a name the bus would refuse no longer
reaches it with step guidance. A new workflow under a name its scope already
holds is refused on the page instead of replacing that workflow. A pack
preview the page cannot draw says so rather than leaving Preview disabled.
The browser test launches Chromium with --disable-extensions.
2026-11-12 18:40:34 -06:00
jason.woltje 504fcd9220 fix(webui): pack preview reads credential lines as {service, scope} objects (#1873 SP6)
The bus sends each hire's credential lines as {service, scope} (scope absent
when a definition names a service without scopes). The picker destructured
them as tuples, so a real preview threw and never rendered. The browser test
fixture now uses the producer's shape.
2026-11-12 18:31:21 -06:00
jason.woltje 154c2216f3 feat(webui): board labels, pack picker and workflow editor on the organization page (#1873 SP6)
The Tasks board takes the selected organization's board: its labels and column order over the five
fixed states, and its default layout unless the viewer saved one. The all-organizations view keeps
the fixed labels. The organization page gains the owner's starter pack picker (list, preview with
marks, flagged lines, credential lines and noHire marks, apply by digest) and a workflow list and
editor that marks a step role naming no current role. Every organization- or pack-supplied string
goes through esc or textContent.

The workspace read carries each organization's and project's board. GET /api/orgs/<id>/workflows
is the owner's read of the workflows, the role names a step may resolve to and the file revision.
packsServes pins HEAD and GET /api/board to the Board read, never the pack route.
2026-11-12 18:17:26 -06:00
jason.woltje 9a649abf85 fix(packs): SP4 re-check, the board route method split, human role reserved, distinct step roles (#1873)
- serve.mjs: only GET and HEAD /api/board stay the Board read; every
  other method goes to the pack route, so DELETE and PUT answer its 405
  again. The owner-only check moves inside packsHTTP after the method
  check, so OPTIONS on a root Console stays 405 as before SP4.
- runtime.mjs: a --as rename cannot target the definition name human,
  and a human step role never follows a rename. A step whose mapped
  roles collide refuses invalid-request at preview, not only at apply.
- The no-op apply skip also needs the same pack sha256, so a changed
  pack with nothing to add still records its entry.
- Tests: the --as refusals for human, a step role collision, a pack
  defining human, a changed saved pack recording its entry, board by
  PUT, a granted session refused owner-only on every pack route, and a
  Console without sign-in refused owner-only.
2026-11-12 17:17:19 -06:00
jason.woltje 1ba3288bec fix(packs): SP4 review round, --as collisions, step roles, the board route and repeat applies (#1873)
- An --as target that lands on another pack item's name refuses invalid-request
  (definitions and workflows); the preview no longer shows two adds for one path.
- A workflow step role naming one of the pack's definitions follows its --as rename,
  so an inline definition renamed at preview applies.
- GET /api/board stays the Board read; only POST /api/board is the pack board write.
- Tombstones key on pack id, origin and project: a saved template reusing a shipped
  id is another pack.
- An apply that adds nothing and proposes what the last apply proposed writes
  nothing, so retries do not fill the 256 packs entries.
- mosaic org pack list refuses inside an agent run like the other words.
- Tests pin coder-1's credential lines, skills.notEnabled and the member's
  unsuffixed pack.apply and board.set refusals; the CONSOLE_VERBS pins in two bus
  tests name the five pack verbs.
2026-11-12 17:03:56 -06:00
jason.woltje 079ced93f3 feat(packs): pack, board and workflow verbs, routes and CLI words (#1873 SP4)
Design section 8. The bus answers packs, pack.preview, pack.apply,
board.set and workflow.set to the organization's human, and their
.console forms to the owner's Console cap. The broker runs the section 3
merge, the plan digest (pack-plan-stale on a mismatch) and the held
credential check. The organization writer keeps packs, board and
workflows out of the generic write and refuses definition writes to
every actor name an agent carries, project agents included.

mosaic org pack list|preview|apply, board set and workflow set|remove
go over the organization's bus as its human and refuse inside an agent
shell before any file is read. The Console serves GET /api/orgs/<id>/packs,
packs/preview, packs/apply, board and workflows/<name> (POST and
DELETE) to the root owner's session only, writes under --send only.
2026-11-12 16:51:46 -06:00
jason.woltje 95c2ce1435 fix(organization): CB4 re-check, project.remote.set refuses under a copy binding that still grants the project (#1873) 2026-11-12 16:16:24 -06:00
jason.woltje 2196d37a47 test(bus): CB4 tests for the slot parent check, the resume failure code and the CLI copy remote wiring (#1873) 2026-11-12 16:14:06 -06:00
jason.woltje ef1d3a4583 fix(bus): CB4 review round, fire revokes after the write, archive and delete revoke at Gitea (#1873)
Build review (changes 8) and security review (changes 4):
- P1-1: an owner-approved fire no longer writes the revoke before the
  apply (decision-stale). The fire stops the copy's runs, the fire is
  written, then the retire revoke starts for the fired instance.
- P2 (both): archive and delete from the CLI and the Console pass the
  organization's managed Gitea remote step (copyRemoteFor). A remote
  step that was not attempted or failed keeps the record revoking with
  failed remote, so the bus finishes it after restore; no done tombstone
  over a live token.
- Security P2-1: a bound server that cannot be read is a failed remote
  step, not not-managed. updateOrg refuses project-repo-bound when a
  project a copy binding grants loses or changes its remote or git.
- P3-1 (security): a delete whose organization file cannot be read says
  copiesUnreadable, never an empty copy list.
- P3-2 (security): the remote step requires the record's incarnation to
  equal the registry's live one.
- P3-5 (build): the archive's slot discard checks each parent directory
  (no symlink, own uid, private mode).
- P3-6 (build): resume keeps credential-revoke-failed as the gate code
  while a failed op runs again.
- P3-4 (build): a test pins the refused-begin reopen guard.
2026-11-12 16:08:08 -06:00
jason.woltje 90245765f2 feat(bus): copy revoke CB4, lifecycle events fence and revoke bound copies (#1873)
Every lifecycle event that ends a copy's authority (unbind, emergency
revoke, slot removal or expiry, role change, fire, collision, shadowing,
archive, delete and recreate) now closes admissions, drains in-flight
uses, stops runs with a confirmed ack, removes the slot and revokes the
Gitea token through the org admin slot. The binding moves through a
durable revoking state, so a restart resumes the same op and never
resurrects authority; an uncertain outcome stays fenced.
2026-11-12 16:08:07 -06:00
jason.woltje dfef78834a test(plugins): review round for the shipped starter packs (#1873 SP2)
The SP1 listing test also requires a nonempty shipped listing, and a
new SP2 case pins that installing a plug-in named after any shipped
pack refuses core-name, so an installed pack cannot replace one.
2026-11-12 16:06:08 -06:00
jason.woltje 1f2a726d19 feat(plugins): the five shipped starter packs (#1873 SP2)
coding, creative-writing, marketing, montage and business-management,
each a packVersion 1 document pinning whole V2 templates at today's
templateHash. expected-lines.json beside each pack records the flagged
and credential lines every proposed hire carries against
roles/chief-of-staff.json, as the broker computes them.

starter-pack-shipped.test.mjs checks each pack loads through SP1 with
origin shipped, matches section 7, and that its lines equal
expected-lines.json; a template edit refuses the pack until re-pinned.
The SP1 test no longer expects an empty shipped listing.
2026-11-12 16:06:08 -06:00
jason.woltje 1ced0e3924 fix(harness): escape every control and format character in workflow data (starter packs SP7 review P3)
The workflows data lines now escape every \p{Cc}, \p{Cf}, \p{Zl} and
\p{Zp} character by UTF-16 unit, so zero-width, soft hyphen, BOM and
tag characters never reach the prompt invisible. The section says the
rails come later in the prompt, not at its end, since skills can follow
them. A role holding task.assign or task.create only cross-role sees
every step; now tested.
2026-11-12 16:00:42 -06:00