80 — Writing a workbench tool
GuideRendered from 80-tool-authoring-guide.md in the public repository. Edits belong there.
This page lists the steps for adding a workbench tool to micyte/tools/ and the rules the tests enforce on it.
A workbench tool draws a panel for the document an operator has selected. It is not the right register for every feature:
| If the feature… | Put it in | Registered with |
|---|---|---|
| draws a panel for a selected document | micyte/tools/ | micyte.tools.register |
| runs when a condition holds, without a person | micyte/automation/ | micyte.automation.register |
| is an outward-facing session (a visitor, an agent) | micyte/channels/ | the channel registry |
micyte.tools.register raises TypeError for an object that has a routine_id or a channel_id; micyte.automation.register raises for an object that has a channel_id or any of the tool attributes. See 40 for the three registers.
The contract
WorkbenchTool in micyte/tools/_contract.py is a runtime-checkable Protocol. register() checks it with isinstance.
| Member | Type | Meaning |
|---|---|---|
tool_id | str | Registry key and URL token. |
label | str | Name shown in the palette. |
summary | str | One line shown under the label. |
route | str | Use WORKBENCH_UI_TOOL_ROUTE from micyte.state_machine.portal_shell.shell_schemas. |
applies_to_archetype | tuple[str, ...] | Archetype tokens the tool is offered for. |
applies_to_source_kind | tuple[str, ...] | Source kinds the tool is offered for. |
build_panel_payload(*, authority_db_file, sandbox_id, document_id, datum_address) | method | Returns the dict the JavaScript renderer draws. |
Optional attributes read by the host:
| Attribute | Meaning |
|---|---|
writes | Tuple of DeclaredWrite(document_kind, action, sandbox_id="") from micyte/ports/datum_write_policy/contracts.py. The write routes build their authorization requests from these. |
requires | ToolRequirement from micyte/ports/tool_package/contracts.py: sources, documents (all of), documents_any (one of), and the logical fields the tool writes. An instance that does not meet it is not offered the tool. |
wants_surface_query | True passes the request's query string to build_panel_payload as extra_query. |
wants_host_context | True passes host values (for example the instance's port bindings) as host_context. |
icon | Glyph id for the rail. |
Steps
- Write the module. Create
micyte/tools/<name>.pywith one class that has the members above.micyte/tools/viewscope_view.py(79 lines) is a complete example: it refuses with a reason when there is no database or no selected document, otherwise it builds the payload from reads. - Register it. End the module with
register(<Class>())and add<name>to the import list inmicyte/tools/__init__.py. A module that is not imported does not register. - Declare where it applies. A tool is offered for a document when its
applies_to_archetypeintersects the document's archetype tokens or itsapplies_to_source_kindintersects the document's source kinds. A tool that declares both empty is offered for every document. Declaring a broad source kind next to a narrow archetype makes the tool appear on every document of that source kind. - Declare what it writes. If the tool edits documents, list each write in
writes. The write routes build their authorization requests from these declarations and check them against the instance's write grants. - Pin it in the registry test.
fnd_app/tests/architecture/test_tool_registry_shape.pypins the registry as an exact set (REGISTERED_TOOL_IDS). Add the newtool_id. Every tool must either declarewritesor have an entry inDERIVESnaming the figure it computes that no document carries; a tool that does neither failstest_every_registered_tool_writes_or_derives. - Make it reachable. The palette search lists only the ids in
LIVE_TOOL_IDS(fnd_app/instances/_shared/runtime/portal_palette_runtime.py). A registered tool not in that list must be declared in the test'sCOMPOSED_PANES, naming the hub whose payload contains it, or an empty hub when it is opened by address; otherwise the registry-shape test fails. - Check it.
GET /portal/api/tools/eligible?document_id=<id>returns the tools offered for a document. The tool opens at/portal/system/tools/workbench-ui?tools=<tool_id>.
Rules
- The eligibility recognizer is pure.
micyte/state_machine/portal_shell/tool_eligibility.pymay not importos,sys,pathlib,sqlite3, adapters, ports or instances, and may not hold module-level state.fnd_app/tests/architecture/test_palette_eligibility_purity.pyenforces this. - A document a write path creates must carry its archetype in its metadata. A document created with empty metadata is never offered the tools written for it.
- Record tables use
micyte/tools/_editable_table.py. The payload states the fields,save_routeandsandbox_id. A payload with nosandbox_iddisables saving; there is no fallback sandbox. Per-row buttons arerow_actionsbuilt withpost_action,toggle_action,nav_actionorparam_action. A value the server computes goes incomputed_columns, which have no input. - Money is an integer number of cents. Parse and format only with
parse_centsandformat_centsinmicyte/core/datum_ops/fiat_datum.py. Half a cent is refused, not rounded. Find a sandbox's price field withfind_fiat_chain(anchor_doc); its address differs per sandbox, so it is never hardcoded. A sandbox with no fiat chain gets a refusal. - An amount with a unit (
"10 lbs") is text. Nothing totals it.
Tests to run
| Test | What it checks |
|---|---|
micyte/tests/unit/test_tool_eligibility.py | recognize_applicable_tools: archetype match, source-kind match, hyphae-chain widening, ordering, empty address. |
fnd_app/tests/integration/test_tool_palette.py | build_eligible_tools_response and GET /portal/api/tools/eligible; each entry has label, summary, route. |
fnd_app/tests/architecture/test_palette_eligibility_purity.py | The recognizer imports and state rules above. |
fnd_app/tests/architecture/test_tool_registry_shape.py | The pinned set, DERIVES, COMPOSED_PANES. |
Not built
Binding a tool to a hyphae value. PortalToolRegistryEntry.applies_to_hyphae_value and the matching branch in recognize_applicable_tools exist; no entry sets the field. See 60.