What the assistant can do, against what a person can do¶
Every route in this MES that changes something, and whether the floor assistant can perform it on somebody's behalf. Either a tool sends it, or the row says why not - and the reason is a rule, not a to-do.
Generated by fsmes assist coverage --markdown on 2026-10-11.
Do not edit it by hand: tests/test_every_write_route_has_a_tool_or_a_reason.py
regenerates it and fails if this file differs.
There are 77 write routes; 73 of them a screen calls; 55 write tools send one.
- operator: 17 of 73 screen actions the assistant can propose; 14 with a walk; 16 by written reason.
- supervisor: 33 of 73 screen actions the assistant can propose; 25 with a walk; 16 by written reason.
- admin: 57 of 73 screen actions the assistant can propose; 42 with a walk; 16 by written reason.
- agent: 24 of 73 screen actions the assistant can propose; 21 with a walk; 16 by written reason.
A screen action is a write route some page script calls: the things a
person can do by pointing at them. The route's own capability gate says
whether a role may perform it, and a walk is a "Show me" surface in
services/assistant.py - a tool without one can be proposed but not
shown, and is listed in assistant.WITHOUT_A_WALK with where its
control is.
Every write route¶
| Method | Path | Screen | Tool, or why not | Capability | Walk |
|---|---|---|---|---|---|
| DELETE | /admin/roles/{code} |
admin.js |
no tool — a judgement call, decided against. create_role and update_role exist because defining what a role grants is work an admin can be helped with and is reversible from the screen that did it. Deleting one is not: every account holding it loses every capability at once, and what it granted is not recoverable from the row that is gone. The screen keeps it; the assistant walks an admin to it. | users.manage |
— |
| DELETE | /maintenance/rules/{code} |
maintenance.js |
remove_dispatch_rule |
maintenance.plan |
no |
| PATCH | /dashboard/config/{domain}/settings/{key} |
ai.js, config.js |
write_plant_setting |
per key: the define of the ConfigSection the key is listed under |
yes |
| PATCH | /maintenance/rules/{code} |
maintenance.js |
change_dispatch_rule |
maintenance.plan |
no |
| POST | /adjustments |
adjustments.js |
propose_adjustment |
adjustments.propose |
yes |
| POST | /adjustments/{code}/approve |
adjustments.js |
no tool — the human in the loop before a PLC write - the whole point of the adjustment queue. propose_adjustment proposes; approving is the person the queue exists to ask. | adjustments.approve |
— |
| POST | /adjustments/{code}/reject |
adjustments.js |
no tool — rejecting is that same judgement, and an agent that could reject could quietly clear the queue it filled. | adjustments.approve |
— |
| POST | /admin/roles |
admin.js |
create_role |
users.manage |
yes |
| POST | /assist/agent |
ai.js, assist.js |
no tool — the same: the conversation endpoint both agents answer on - the panel's floor assistant, and the AI tab's exploration. | none | — |
| POST | /assist/agent/confirm |
assist.js |
no tool — "Do it" - the person's confirmation of a proposal. The loop reads it; a tool that pressed it would be the assistant confirming itself. | none | — |
| POST | /assist/agent/decline |
assist.js |
no tool — the other half of that confirmation, for the same reason. | none | — |
| POST | /assist/ask |
assist.js |
no tool — this is how the assistant is asked. A tool for it would be the assistant calling itself. | none | — |
| POST | /auth/login |
app.js |
no tool — signing in is how a person becomes the person this all acts for. The tools sign in as AGENT themselves, in code, with no model in the loop. | none | — |
| POST | /auth/logout |
app.js, common.js |
no tool — ending a person's session is not an action on the plant, and an assistant that could sign somebody out could sign them out mid-shift. | none | — |
| POST | /auth/users |
admin.js |
create_user |
users.manage |
yes |
| POST | /auth/users/{code}/password |
admin.js |
no tool — an agent never types a password. Setting somebody's is a person at a keyboard with the account holder in front of them, and a password that passed through a model's context is a password to rotate. | users.manage |
— |
| POST | /coa/pallet/{serial} |
coa.js |
issue_pallet_certificate |
quality.close_nc |
no |
| POST | /coa/{order} |
coa.js |
issue_certificate |
quality.close_nc |
no |
| POST | /design/chat |
design.js |
no tool — the design partner, which is how this product is built rather than how a plant is run. Not a floor action. | audit.read |
— |
| POST | /documents |
assist-record.js, instructions.js |
draft_instruction, create_document |
documents.write |
yes |
| POST | /documents/{code}/approve/{revision} |
instructions.js |
no tool — putting a revision in force is the approver's signature, recorded against them by name. Decision 0035: the agent role holds documents.write and never documents.approve, and a confirmed "Do it" runs as AGENT on the person's behalf - so a tool here would sign for them. The assistant drafts (create_document, revise_document) and walks them to Approve. | documents.approve |
— |
| POST | /documents/{code}/revise |
instructions.js |
revise_document |
documents.write |
yes |
| POST | /documents/{code}/withdraw |
instructions.js |
no tool — taking an instruction out of force is the same signature in reverse, gated on the same documents.approve, and withdrawn procedure is what the floor is working to until somebody notices. A person does it. | documents.approve |
— |
| POST | /equipment/downtime-reasons |
machine.js, reasons.js, station.js |
draft_downtime_reason |
process.define |
yes |
| POST | /equipment/downtime-reasons/{code}/approve/{revision} |
— | no tool — the vocabulary every stop from now on is named with. draft_downtime_reason drafts it; process.approve is a person's, and the walk to the pending-approvals panel is the honest answer. | process.approve |
— |
| POST | /equipment/{code}/state |
station.js |
set_machine_state |
equipment.state |
yes |
| POST | /equipment/{code}/stops/label |
machine.js |
no tool — naming a stop is somebody's account of why a machine stopped, and nothing in the record says why - the MES watched the interval and the vocabulary is a list of candidates. A tool here would let a model choose between six reasons from the shape of the data and write the answer into the pareto as though a person on the floor had said it, which is how a plant's downtime analysis becomes fiction with a confidence interval. House rule 3: unlabelled data is reported as unlabelled. The honest assistant answer is to show which stops are unnamed and let whoever was there say. Another system's label has its own front door (fsmes inbound), and it records who claimed it. |
equipment.state |
— |
| POST | /erp/outbox/{message_id}/retry |
ops.js |
erp_retry |
orders.close |
no |
| POST | /execution/consume |
app.js, station.js |
issue_material |
production.consume |
yes |
| POST | /execution/lots |
app.js, station.js |
create_lot |
production.consume |
yes |
| POST | /execution/report |
app.js, station.js |
book_output |
production.book |
yes |
| POST | /maintenance/corrective |
machine.js, maintenance.js |
raise_corrective_maintenance |
maintenance.perform |
yes |
| POST | /maintenance/dispatch |
maintenance.js |
dispatch_maintenance |
maintenance.perform |
no |
| POST | /maintenance/orders/{code}/assign |
machine.js, maintenance.js |
assign_maintenance_order |
maintenance.plan |
no |
| POST | /maintenance/orders/{code}/complete |
machine.js, maintenance.js, station.js |
perform_maintenance |
maintenance.perform |
yes |
| POST | /maintenance/orders/{code}/start |
machine.js, maintenance.js, station.js |
perform_maintenance |
maintenance.perform |
yes |
| POST | /maintenance/plans |
machine.js, maintenance.js |
create_maintenance_plan |
maintenance.plan |
yes |
| POST | /maintenance/raise |
maintenance.js |
raise_due_maintenance |
maintenance.perform |
yes |
| POST | /maintenance/rules |
maintenance.js |
write_dispatch_rule |
maintenance.plan |
no |
| POST | /masterdata/equipment |
admin.js, app.js, masterdata.js |
create_equipment |
masterdata.write |
yes |
| POST | /masterdata/materials |
admin.js, coa.js, masterdata.js, orders.js |
create_material |
masterdata.write |
yes |
| POST | /masterdata/materials/{code}/bom |
masterdata.js |
add_bom_component |
masterdata.write |
yes |
| POST | /masterdata/personnel |
masterdata.js |
add_person |
users.manage |
no |
| POST | /masterdata/routings |
admin.js |
create_routing |
masterdata.write |
yes |
| POST | /quality/checks |
app.js, quality.js, station.js |
record_check |
quality.record |
yes |
| POST | /quality/counts |
— | record_count |
quality.record |
no |
| POST | /quality/gauges |
gauges.js |
register_gauge |
masterdata.write |
no |
| POST | /quality/gauges/{code}/calibrate |
gauges.js |
calibrate_gauge |
quality.close_nc |
no |
| POST | /quality/nonconformances/{code}/close |
quality.js |
close_nonconformance |
quality.close_nc |
yes |
| POST | /quality/nonconformances/{code}/disposition |
quality.js |
disposition_nonconformance |
quality.close_nc |
yes |
| POST | /quality/nonconformances/{code}/review |
quality.js |
review_nonconformance |
quality.close_nc |
yes |
| POST | /quality/samples |
— | record_sample |
quality.record |
no |
| POST | /quality/severities |
severities.js |
draft_nc_severity |
quality.define |
yes |
| POST | /quality/severities/{code}/approve/{revision} |
— | no tool — the same again for the severities a non-conformance is graded at - the second vocabulary, and deliberately the same reason word for word: quality.approve is a person's signature. | quality.approve |
— |
| POST | /quality/specs |
app.js, masterdata.js, quality.js, spc.js, station.js |
create_spec |
masterdata.write |
yes |
| POST | /scheduling/calendar/exceptions |
schedule.js |
add_calendar_exception |
scheduling.plan |
yes |
| POST | /scheduling/calendar/shifts |
schedule.js |
add_shift |
scheduling.plan |
yes |
| POST | /scheduling/plan |
schedule.js |
plan_all_orders |
scheduling.plan |
yes |
| POST | /scheduling/plan/{code} |
schedule.js |
plan_order |
scheduling.plan |
yes |
| POST | /trace/units |
trace.js |
produce_units |
production.book |
yes |
| POST | /trace/units/batch |
trace.js |
produce_batch |
production.book |
no |
| POST | /trace/units/pack |
trace.js |
pack_unit |
production.book |
no |
| POST | /trace/units/{serial}/status |
trace.js |
set_unit_status |
quality.close_nc |
no |
| POST | /triggers |
triggers.js |
draft_trigger |
triggers.write |
yes |
| POST | /triggers/{code}/approve |
triggers.js |
no tool — a trigger in force is logic acting on a live plant with nobody in the loop after it. Decision 0035 gives the agent triggers.write and never triggers.approve; draft_trigger drafts, a person signs. | triggers.approve |
— |
| POST | /triggers/{code}/withdraw |
triggers.js |
no tool — withdrawing a trigger stops a machine being protected by it. Gated on triggers.approve for that reason, and not a tool for the same one. | triggers.approve |
— |
| POST | /workorders |
app.js, orders.js, schedule.js |
create_order |
orders.create |
yes |
| POST | /workorders/{code}/cancel |
orders.js |
order_action |
orders.close |
yes |
| POST | /workorders/{code}/close |
app.js, orders.js |
order_action |
orders.close |
yes |
| POST | /workorders/{code}/hold |
orders.js |
order_action |
orders.close |
yes |
| POST | /workorders/{code}/operations/{seq}/complete |
orders.js, station.js |
complete_operation |
production.book |
yes |
| POST | /workorders/{code}/operations/{seq}/start |
station.js |
start_operation |
production.book |
yes |
| POST | /workorders/{code}/release |
app.js, orders.js |
create_order, order_action |
orders.release |
yes |
| POST | /workorders/{code}/resume |
orders.js |
order_action |
orders.close |
yes |
| PUT | /admin/roles/{code} |
admin.js |
update_role |
users.manage |
yes |
| PUT | /admin/users/{code}/role |
admin.js |
assign_role |
users.manage |
yes |
| PUT | /masterdata/personnel/{code}/home-equipment |
masterdata.js |
set_home_equipment |
users.manage |
no |
Who holds what¶
Whether each role may perform the route at all, from the route's own
capability gate. A role that may not perform it is never offered the
tool either: what the assistant offers comes from agent.NEEDS, which
is a second copy of the same answer, and
test_every_write_tool_names_the_capability_its_route_demands fails if
the two ever disagree about any role in this table. Until 2026-09-27
they disagreed about nine tools, and this column was the half that was
right.
| Action | Capability | operator | supervisor | admin | agent |
|---|---|---|---|---|---|
DELETE /admin/roles/{code} |
users.manage |
no | no | yes | no |
DELETE /maintenance/rules/{code} |
maintenance.plan |
no | no | yes | no |
PATCH /dashboard/config/{domain}/settings/{key} |
per key: the define of the ConfigSection the key is listed under |
no | no | yes | yes |
PATCH /maintenance/rules/{code} |
maintenance.plan |
no | no | yes | no |
POST /adjustments |
adjustments.propose |
no | yes | yes | yes |
POST /adjustments/{code}/approve |
adjustments.approve |
no | no | yes | no |
POST /adjustments/{code}/reject |
adjustments.approve |
no | no | yes | no |
POST /admin/roles |
users.manage |
no | no | yes | no |
POST /assist/agent |
none | yes | yes | yes | yes |
POST /assist/agent/confirm |
none | yes | yes | yes | yes |
POST /assist/agent/decline |
none | yes | yes | yes | yes |
POST /assist/ask |
none | yes | yes | yes | yes |
POST /auth/login |
none | yes | yes | yes | yes |
POST /auth/logout |
none | yes | yes | yes | yes |
POST /auth/users |
users.manage |
no | no | yes | no |
POST /auth/users/{code}/password |
users.manage |
no | no | yes | no |
POST /coa/pallet/{serial} |
quality.close_nc |
no | yes | yes | no |
POST /coa/{order} |
quality.close_nc |
no | yes | yes | no |
POST /design/chat |
audit.read |
no | yes | yes | yes |
POST /documents |
documents.write |
no | yes | yes | yes |
POST /documents/{code}/approve/{revision} |
documents.approve |
no | no | yes | no |
POST /documents/{code}/revise |
documents.write |
no | yes | yes | yes |
POST /documents/{code}/withdraw |
documents.approve |
no | no | yes | no |
POST /equipment/downtime-reasons |
process.define |
no | no | yes | yes |
POST /equipment/downtime-reasons/{code}/approve/{revision} |
process.approve |
no | no | yes | no |
POST /equipment/{code}/state |
equipment.state |
yes | yes | yes | yes |
POST /equipment/{code}/stops/label |
equipment.state |
yes | yes | yes | yes |
POST /erp/outbox/{message_id}/retry |
orders.close |
no | yes | yes | no |
POST /execution/consume |
production.consume |
yes | yes | yes | yes |
POST /execution/lots |
production.consume |
yes | yes | yes | yes |
POST /execution/report |
production.book |
yes | yes | yes | yes |
POST /maintenance/corrective |
maintenance.perform |
yes | yes | yes | yes |
POST /maintenance/dispatch |
maintenance.perform |
yes | yes | yes | yes |
POST /maintenance/orders/{code}/assign |
maintenance.plan |
no | no | yes | no |
POST /maintenance/orders/{code}/complete |
maintenance.perform |
yes | yes | yes | yes |
POST /maintenance/orders/{code}/start |
maintenance.perform |
yes | yes | yes | yes |
POST /maintenance/plans |
maintenance.plan |
no | no | yes | no |
POST /maintenance/raise |
maintenance.perform |
yes | yes | yes | yes |
POST /maintenance/rules |
maintenance.plan |
no | no | yes | no |
POST /masterdata/equipment |
masterdata.write |
no | no | yes | no |
POST /masterdata/materials |
masterdata.write |
no | no | yes | no |
POST /masterdata/materials/{code}/bom |
masterdata.write |
no | no | yes | no |
POST /masterdata/personnel |
users.manage |
no | no | yes | no |
POST /masterdata/routings |
masterdata.write |
no | no | yes | no |
POST /quality/checks |
quality.record |
yes | yes | yes | yes |
POST /quality/counts |
quality.record |
yes | yes | yes | yes |
POST /quality/gauges |
masterdata.write |
no | no | yes | no |
POST /quality/gauges/{code}/calibrate |
quality.close_nc |
no | yes | yes | no |
POST /quality/nonconformances/{code}/close |
quality.close_nc |
no | yes | yes | no |
POST /quality/nonconformances/{code}/disposition |
quality.close_nc |
no | yes | yes | no |
POST /quality/nonconformances/{code}/review |
quality.close_nc |
no | yes | yes | no |
POST /quality/samples |
quality.record |
yes | yes | yes | yes |
POST /quality/severities |
quality.define |
no | no | yes | yes |
POST /quality/severities/{code}/approve/{revision} |
quality.approve |
no | no | yes | no |
POST /quality/specs |
masterdata.write |
no | no | yes | no |
POST /scheduling/calendar/exceptions |
scheduling.plan |
no | no | yes | no |
POST /scheduling/calendar/shifts |
scheduling.plan |
no | no | yes | no |
POST /scheduling/plan |
scheduling.plan |
no | no | yes | no |
POST /scheduling/plan/{code} |
scheduling.plan |
no | no | yes | no |
POST /trace/units |
production.book |
yes | yes | yes | yes |
POST /trace/units/batch |
production.book |
yes | yes | yes | yes |
POST /trace/units/pack |
production.book |
yes | yes | yes | yes |
POST /trace/units/{serial}/status |
quality.close_nc |
no | yes | yes | no |
POST /triggers |
triggers.write |
no | yes | yes | yes |
POST /triggers/{code}/approve |
triggers.approve |
no | no | yes | no |
POST /triggers/{code}/withdraw |
triggers.approve |
no | no | yes | no |
POST /workorders |
orders.create |
yes | yes | yes | yes |
POST /workorders/{code}/cancel |
orders.close |
no | yes | yes | no |
POST /workorders/{code}/close |
orders.close |
no | yes | yes | no |
POST /workorders/{code}/hold |
orders.close |
no | yes | yes | no |
POST /workorders/{code}/operations/{seq}/complete |
production.book |
yes | yes | yes | yes |
POST /workorders/{code}/operations/{seq}/start |
production.book |
yes | yes | yes | yes |
POST /workorders/{code}/release |
orders.release |
yes | yes | yes | yes |
POST /workorders/{code}/resume |
orders.close |
no | yes | yes | no |
PUT /admin/roles/{code} |
users.manage |
no | no | yes | no |
PUT /admin/users/{code}/role |
users.manage |
no | no | yes | no |
PUT /masterdata/personnel/{code}/home-equipment |
users.manage |
no | no | yes | no |