@haneinfo/collaboration
v0.12.1
Published
Immutable instance comments, merged timelines and following.
Readme
Instance collaboration
Immutable plain-text comments, a read-time activity timeline, and following for StateMachine instances.
Install the collaboration plugin with StateMachine, identity, notification inbox, Prisma and optional
RBAC capabilities. Include prisma/collaboration.prisma in the App schema and migrate it normally.
No separate timeline event table is created. The package starts at 0.0.0 for a first minor release.
Readers and writers must be the submitter, have state-machine:instance:read, or hold a reader grant. Comments cannot be
updated or deleted through this package. Text is stored and returned literally: clients render it as
text, without HTML or Markdown interpretation. Mentions use @{userId}; recipients must be active
and currently able to read the instance. Agent/MCP comments are always marked isAiGenerated.
HTTP routes under /state-machine-instances/:id: GET/POST comments, GET timeline, POST/DELETE
follow. Reads accept offset (default 0) and limit (default 50, maximum 200). Timeline events sort by
ascending timestamp then stable source-prefixed id, and expose hasMore. Sources are history,
CHOICE decisions, issued numbers, notification failures and comments. Concurrent additions may
change offset boundaries between separate requests; one request uses a database snapshot.
Register CollaborationMcpTools.registerInto(registry, { scopeNamespace }) in the App's existing
internal MCP registry. Tools are get_instance_comments, get_instance_timeline,
comment_on_instance, follow_instance, unfollow_instance; all require <namespace>:instance:read
transport scope plus live instance-read authorization. User text and timeline details use
$userContent wrappers. The package creates no public endpoint or new business permission.
Following is unique per instance/user and covers every subsequent transition and new comment.
The approved state-machine.transition.recorded event is consumed through StateMachineListener.onEvent
inside the originating transaction, independent of stale state-entry suppression. Transition
notifications use the existing state-machine-transition:<historyId> key. Comment notifications use
collaboration-comment:<commentId>, union followers, mentions and submitter, exclude the author, and recheck each
recipient's active identity and instance-read permission immediately before delivery. The inbox's
recipient/key unique constraint handles replay and overlap with transition-action notifications.
Transition followers do not add an automatic submitter notification unless that person follows;
existing transition notify actions keep their recipient semantics.
Configuration: collaboration: { appName?: string }. App name defaults to app. No subscription
rules, event-type filtering, comment editing, or alternate objects are provided.
Mutating tools additionally declare mutates: true. Both the generated catalog and MCP wire
annotations then report readOnlyHint: false, even for writes authorized by existing read scopes.
This conservative declaration cannot turn a write scope into a read-only operation.
Instance readers and stored query values
Instance readers have the same discussion and following access as the submitter or a holder of
state-machine:instance:read. Apply the state-machine reader-table migration and existing-task
backfill before upgrading. Reader checks use the caller transaction, including mention validation
and delivery; task status does not revoke readership.
Comment recipients are the deduplicated union of followers, mentions and submitter, excluding the author even if they follow or mention themselves. Other recipients receive one notification. Transition notifications keep their existing behavior, including notifications to an actor who follows the instance. ADMIN has no new special case.
Mechanism compatibility
Supports state-machine 0.7 as well as 0.6. Instance readership continues to use the existing StateMachine contract; Collaboration does not implement condition comparison operators.
Frontend contracts
GET /state-machine-instances/:id/mentionable?q= returns { items: [{ id, name }] }, at most 20 active readers other than the caller. Names may be null. Matching uses case-insensitive name containment; an empty q searches all names. q is at most 200 characters. A nonreader receives INSTANCE_NOT_FOUND (404), exactly like a missing instance.
IdentityStorePort.searchActiveByName owns database filtering and keyset pages of at most 20 candidates. Upgrade identity-core with collaboration. Missing optional identity search fails closed with IDENTITY_SEARCH_UNAVAILABLE (503). Collaboration evaluates each candidate through the existing effective RBAC authorization and reader grant check; it does not query role tables or reproduce role policy. Memory is bounded by one candidate page plus 20 results; a sparse query can require many bounded queries. Results are ordered by identity id. Comments revalidate mentions when posted.
GET comments and GET timeline now include following: boolean for the caller, queried in the same transaction as the page. MCP get_instance_comments and get_instance_timeline preserve following. New list_mentionable_users accepts { instanceId, q? }, uses :instance:read, is read-only, and wraps each name in $userContent. The manifest declares all six tools; no confirmation or new mutation tool is added.
Instance privacy alignment
Collaboration already returns identical INSTANCE_NOT_FOUND/404 for missing and unreadable instances before comments, timeline, mentions and follow operations. State-machine now applies the same hiding behavior before human writes; collaboration authorization is unchanged. No general-user people lookup is added.
Delegated transition timeline
History timeline details optionally include actingUserId and onBehalfOfId from the existing history decision JSON, allowing clients to display who acted for whom. actorId, triggerRef and every existing field retain their meanings. No new table, timeline kind, frontend or historical name snapshot is added. Permanent instance readers, including past delegates, retain read/comment access.
Instance access lists (draft implementation)
Implemented against draft decision 202610-c8e3; the decision remains draft.
Comments, mentions, mentionable discovery, follows and notification recipients use the submitter, effective instance:read, or matching USER/ROLE access (READ or WRITE). Losing manual/role access immediately blocks new reads, mentions and notifications; old inbox entries are retained. The existing timeline SQL union classifies triggerType ACCESS as kind ACCESS and preserves the existing details fields, adding decision.access with the action, principal and previous/new levels. No state transition, revision bump or follower transition notification is generated for access changes. MCP wraps decision as $userContent, including access activity. User/grantor names are plain text.
Upgrade the affected mechanisms together after applying the App migration. The maintained model, HTTP routes, read-only list_instance_access MCP tool, errors and permission contract are documented in @haneinfo/state-machine/agent-guide.md. Its prisma/instance-access.sql migrates the existing reader table to USER/ROLE, READ/WRITE and ASSIGNED/DELEGATED/GRANTED rows while retaining timestamps. The maintainer selected (instanceId, principalType, principalId, source) uniqueness. Manual grants are limited to 100 by default; permanent READ entries survive manual removal. See the package repository validation/equipment-loan/instance-access/report.md for SQL, tests and App restoration evidence.
Optional attachments (draft decision 202610-5f3b)
Attachment activity uses existing history with triggerType ATTACHMENT and optional decision.attachment (action, id, plain-text fileName). No storage keys, state/revision changes or notifications are introduced. Collaboration classifies these history rows as ATTACHMENT in its existing SQL union; MCP retains decision $userContent wrapping. See @haneinfo/attachments/agent-guide.md for authorization, storage, scanning, migration and retention. Apps adopting the optional package add its reverse instance relation; the core fragment does not depend on attachments.
Document generation (draft 202610-d4f6)
The optional @haneinfo/document-templates package adds DOCUMENT activity using existing history and decision.document (templateCode, templateVersion, format), attributed by triggerUserId. No tables, state/revision changes or notifications are added. Collaboration classifies the row as DOCUMENT in its existing authorized SQL timeline. Instance history and timeline fields remain unchanged. Read the document-templates agent guide for fonts, configuration, HTTP and read-only MCP.
Instance lifecycle (draft 202610-f1a8)
Implemented from draft decision 202610-f1a8; the decision remains draft. Upgrade state-machine, list-view, collaboration, attachments, import-export, assignment and timer together. No frontend, automatic archive or bulk lifecycle operations are included. Permanently purged data cannot be restored by this API: database and private file backups must cover the entire deletion retention period and be restored consistently.
Apply prisma/instance-lifecycle.sql to add ON DELETE CASCADE foreign keys for comments/follows; add comments CollaborationComment[] and follows CollaborationFollow[] to the App's instance model. Check and reconcile pre-existing orphan rows before applying these constraints; migration does not silently delete them. The core fragment does not depend on this optional package. LIFECYCLE history joins the existing timeline UNION with the original event fields and optional decision.lifecycle. Actions are ARCHIVED, RESTORED, DELETED, UNDELETED; actorId and at retain who/when. Existing MCP decision wrapping applies. Deleted-instance comments, timeline, mentions and follows use the same missing-id 404. Archive does not block them. Lifecycle events produce no collaboration notifications.
Instance tags (draft 202610-2c5d)
Implemented from draft 202610-2c5d; the decision remains draft. See @haneinfo/tags/agent-guide.md for data, migration and authorization. The existing timeline SQL classifies TAG activities and retains all original fields/pagination. decision.tag contains action ADDED/REMOVED, normalizedKey and label; the existing MCP decision wrapper covers this user text. Each actual tag change has one activity and sends no notification.
Unified search (draft 202610-a3d8)
Draft implementation; the decision remains draft. Each comment emits
collaboration.comment.added (instanceId, commentId, authorId, isAiGenerated, at; never the body)
in the comment transaction. When the optional SEARCH_PROVIDERS registry is present (manifest
optionalRequires haneinfo.search), CollaborationService registers CommentsSearchProvider: the
newest commentLimit (default 200) comments, newest first, AI-generated included, composed with the
shared plugin-api helpers and the configured row limit. Without @haneinfo/search nothing is
registered and comments behave as before. The manifest requires plugin API 1.19.0.
Object references (draft 202610-f7a2)
The timeline SQL classifies REFERENCE history entries (written by the optional
@haneinfo/references through state-machine) as the REFERENCE kind, with decision.reference
(action LINKED or UNLINKED, linkId, kind, direction, other { type, id, number }) in details;
original fields, ordering and pagination are unchanged. No notification is sent.
Spaces (draft 202610-b1d6)
The timeline SQL classifies SPACE history entries (written by the optional @haneinfo/spaces
through state-machine when an instance is placed, moved or taken out) as the SPACE kind, with
decision.space (action PLACED, MOVED or REMOVED, nodeId, previousNodeId) in details; original
fields, ordering and pagination are unchanged. No notification is sent.
