@haneinfo/definition-registry
v0.2.9
Published
Stores, validates and publishes mechanism definitions (kind + schemaVersion + metadata + spec), with generic MCP design tools and repo definition sync.
Readme
@haneinfo/definition-registry
Stores, validates and publishes mechanism definitions (kind + schemaVersion + metadata +
spec) for every generic mechanism, with generic MCP design tools and a boot-time sync of
backend/definitions/**. Agents: read agent-guide.md.
Install
Add the plugin to
haneinfo.plugins.jsonand runhaneinfo build:{ "plugin": "@haneinfo/definition-registry", "instanceId": "default", "enabled": true, "configRef": "definitionRegistry" }Register it in the App's plugin list (
definitionRegistry()from@haneinfo/definition-registry/plugin) and add its Prisma fragment throughhaneinfo build; create a migration forDefinition,DefinitionVersionandDefinitionWriteReceipt.Optional config at
definitionRegistry:| Key | Default | Meaning | | --- | --- | --- | |
scopeNamespace|MCP_TOOL_PREFIX| MCP scopes<ns>:definition:read\|write| |appName| the namespace |appNameon audit records | |definitionsDir|<cwd>/definitions| Repo definitions directory | |syncRepoDefinitionsOnBoot|true| Publish new/changed repo definitions on bootstrap | |diffMaxChanges|5000| Changes one definition diff returns (1–50,000) | |diffValueMaxChars|2000| Characters of one displayed diff value (1–100,000) |Register the MCP tools into the registry the App exposes:
definitionMcpTools.registerInto(registry).Add one test so
pnpm verifyruns the semantic checks:import { verifyRepoDefinitions } from '@haneinfo/definition-registry/testing'; expect(verifyRepoDefinitions(path.join(__dirname, '../definitions'), kinds)).toEqual([]);
HTTP API (people only)
InteractiveAuthGuard; machine credentials are refused.
| Route | Purpose |
| --- | --- |
| POST /definitions { definition } | Create a runtime draft, or start a draft of a published runtime definition; 201 with DraftWriteResult |
| PATCH /definitions/:kind/:code/draft { expectedVersion, operations } | Apply 1–200 RFC 6902 operations using revision CAS; 200 with DraftWriteResult |
| POST /definitions/validate { definition } or { kind, code } | Validate unsaved content or the stored draft (published content if no draft); 200 with { valid, diagnostics }, without saving |
| GET /definitions/kinds | Installed kinds |
| GET /definitions?kind=&source= | Definitions the user may read: { definitions, truncated }, at most 1,000 in (kind, code) order |
| GET /definitions/:kind/:code | Draft and latest published version, for review and diff |
| GET /definitions/:kind/:code/diff?from=&to= | Difference between two published versions or a version and draft |
| POST /definitions/publish { refs: [{ kind, code, expectedVersion? }] } | Publish drafts together; expectedVersion is the revision the person reviewed, and a draft changed since is refused with VERSION_CONFLICT |
| POST /definitions/:kind/:code/discard-draft { expectedVersion } | Drop a draft |
Draft writes require <kind>:definition:write; validation requires <kind>:definition:read.
DraftWriteResult contains { kind, code, revision, created, valid, diagnostics }. Invalid specs
are saved as drafts with valid: false and diagnostics; the envelope, installed kind and schema
version must still be accepted. HTTP results and diagnostic messages are plain values, without
$userContent. Human writes and draft attribution use isAiOperation: false.
HTTP writes do not use MCP requestId receipts. A repeated create returns DRAFT_EXISTS (409)
with details.revision; a stale patch returns VERSION_CONFLICT (409) with
details.currentRevision. Read the definition again before retrying. Existing errors retain their
statuses: FORBIDDEN 403, DEFINITION_NOT_FOUND 404, REPO_DEFINITION_READ_ONLY 409,
INVALID_PATCH / IMMUTABLE_FIELD 422, UNKNOWN_KIND / Zod INVALID_INPUT 400. REPO definitions
remain read-only and kind / metadata.code cannot change. Input schemas and limits are shared
with the corresponding MCP tools.
The registry imposes no definition size limit. Configure the host's JSON body limit for its own
kinds (Express defaults to 100kb); editor autosaves must fit that limit. Validation never changes
the stored revision. After editing, review the latest draft and send its revision as
expectedVersion when publishing.
Review UI
@haneinfo/definition-registry/frontend ships presentational components for the admin page the
manifest declares (/dashboard/definitions, i18n namespace definitionRegistry):
DefinitionsTable— rows fromGET /definitions; agent-made drafts are flagged.DefinitionReview— draft next to the latest published version, the changed JSON Pointer paths, and publish / discard buttons. The App passes server actions that call the HTTP API above;publishAction(kind, code, expectedVersion)receives the revision on screen and must send it, so an agent edit made after the review cannot be published unseen. A blocked publish returnsdiagnosticsto display: behind@haneinfo/common's GlobalExceptionFilter the response carriesdetails.definitions(each blocked definition's{ kind, code, reason }with the code and path of every diagnostic, no messages); map them to{ ref: { kind, code }, code, path }.
DefinitionReview titles its sections with h3, so the host page supplies the h1 and an h2
(for example the definition name) above it. Its bounded JSON blocks are labelled, keyboard-focusable
regions so keyboard users can scroll them.
For mechanism packages
Inject DEFINITION_REGISTRY (DefinitionRegistryPort from @haneinfo/plugin-api), call
registerKind() in onModuleInit, and read published definitions with getPublished(),
getPublishedVersion() and resolveBundle() (pin the bundle when an instance starts; it re-reads
until two passes agree, so it never mixes versions published together, and fails with
VERSION_CONFLICT after five passes while the set keeps being published). Registry failures are
DefinitionRegistryError, a MechanismError subclass, so a mechanism's own error mapping turns
them into their HTTP status (DEFINITION_NOT_FOUND is 404). Declare
requires: ["haneinfo.definition-registry"] in the manifest. Test with
createTestDefinitionRegistry() and checkDefinitionKindContract() from ./testing.
Draft data preparation (202610-8b3e)
Draft implementation; the decision remains draft. Contract 1.18.0 adds optional PrepareDataOptions (requireRequired?: boolean) to DefinitionKindRegistration.prepareData and DefinitionRegistryPort.prepareData. Omission retains full validation. StateMachineInstanceView optionally exposes fieldRevisions with removed-field tombstones; older providers can omit it. No new capability, MCP write tool or runtime port method is added. MechanismError.toJSON retains details.currentRevision, bounded conflicts and retryAfterSeconds without a new error envelope. Upgrade the registry forwarder and Form consumer together with state-machine; their manifests require 1.18.0.
Definition version diff (draft 202610-d9a5)
Draft implementation; the decision remains draft. GET /definitions/:kind/:code/diff?from=&to=
compares two sides of one definition: from and to are published version numbers
(DefinitionVersion.content) or draft (the current draft). Both are required; nothing else is
accepted in the query. The response is { from, to, summary, changes, truncated } in the
@haneinfo/diff format.
- Same read rule as
GET /definitions/:kind/:code:<kind>:definition:read(or ADMIN / ALLOW_ALL / READ_ALL) is checked before anything is loaded, so a refused caller gets FORBIDDEN and learns nothing about the content. REPO definitions compare like runtime ones. DEFINITION_NOT_FOUND(404) for an unknown code,DEFINITION_VERSION_NOT_FOUND(404,details.version) for a missing version or a missing draft,DIFF_INPUT_TOO_LARGE/DIFF_TOO_DEEP(422) when a side exceeds 1 MB or 32 levels.- Key rules (
DEFINITION_DIFF_KEYS): Form fields, layout children and subform columns pair by stableid, falling back tokey([id=...]segments); StateMachine states and transitions bykey, falling back tocodeorname. Other kinds use the library defaults (id,key,code,name, else position). diffMaxChangesanddiffValueMaxCharsbound the output; there are no secrets.- MCP:
get_definition_diff(<ns>:definition:read, user-bound) returns the same result with every string inchanges(path segments and values) wrapped as$userContent. It changes nothing; there is still no revert.
