MiCyte

← The wiki

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 inRegistered with
draws a panel for a selected documentmicyte/tools/micyte.tools.register
runs when a condition holds, without a personmicyte/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.

MemberTypeMeaning
tool_idstrRegistry key and URL token.
labelstrName shown in the palette.
summarystrOne line shown under the label.
routestrUse WORKBENCH_UI_TOOL_ROUTE from micyte.state_machine.portal_shell.shell_schemas.
applies_to_archetypetuple[str, ...]Archetype tokens the tool is offered for.
applies_to_source_kindtuple[str, ...]Source kinds the tool is offered for.
build_panel_payload(*, authority_db_file, sandbox_id, document_id, datum_address)methodReturns the dict the JavaScript renderer draws.

Optional attributes read by the host:

AttributeMeaning
writesTuple of DeclaredWrite(document_kind, action, sandbox_id="") from micyte/ports/datum_write_policy/contracts.py. The write routes build their authorization requests from these.
requiresToolRequirement 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_queryTrue passes the request's query string to build_panel_payload as extra_query.
wants_host_contextTrue passes host values (for example the instance's port bindings) as host_context.
iconGlyph id for the rail.

Steps

  1. Write the module. Create micyte/tools/<name>.py with 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.
  2. Register it. End the module with register(<Class>()) and add <name> to the import list in micyte/tools/__init__.py. A module that is not imported does not register.
  3. Declare where it applies. A tool is offered for a document when its applies_to_archetype intersects the document's archetype tokens or its applies_to_source_kind intersects 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.
  4. 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.
  5. Pin it in the registry test. fnd_app/tests/architecture/test_tool_registry_shape.py pins the registry as an exact set (REGISTERED_TOOL_IDS). Add the new tool_id. Every tool must either declare writes or have an entry in DERIVES naming the figure it computes that no document carries; a tool that does neither fails test_every_registered_tool_writes_or_derives.
  6. 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's COMPOSED_PANES, naming the hub whose payload contains it, or an empty hub when it is opened by address; otherwise the registry-shape test fails.
  7. 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.py may not import os, sys, pathlib, sqlite3, adapters, ports or instances, and may not hold module-level state. fnd_app/tests/architecture/test_palette_eligibility_purity.py enforces 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_route and sandbox_id. A payload with no sandbox_id disables saving; there is no fallback sandbox. Per-row buttons are row_actions built with post_action, toggle_action, nav_action or param_action. A value the server computes goes in computed_columns, which have no input.
  • Money is an integer number of cents. Parse and format only with parse_cents and format_cents in micyte/core/datum_ops/fiat_datum.py. Half a cent is refused, not rounded. Find a sandbox's price field with find_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

TestWhat it checks
micyte/tests/unit/test_tool_eligibility.pyrecognize_applicable_tools: archetype match, source-kind match, hyphae-chain widening, ordering, empty address.
fnd_app/tests/integration/test_tool_palette.pybuild_eligible_tools_response and GET /portal/api/tools/eligible; each entry has label, summary, route.
fnd_app/tests/architecture/test_palette_eligibility_purity.pyThe recognizer imports and state rules above.
fnd_app/tests/architecture/test_tool_registry_shape.pyThe 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.