Chat entity references
This contract covers durable Issue operations, their reference API and CLI. It supplies data for chat cards; it does not provide a frontend card renderer, proposal confirmation or dedicated entity WebSocket events. Mutation producers must persist operations and references through the host’s verified authority checks; these read endpoints never create or execute work.Agent operations
The sidecar exposes typed tools at/mcp/issues, injected into supported CLI
adapters as crewship-issues. Creation, update, comment and explicit show use
the same handlers as HTTP /issue/... calls. Tool arguments cannot supply an
actor, chat, workspace or human authority; these come from authenticated tokens
and durable host run admission. Read-only calls do not create cards.
Every agent mutation requested by a human requires an explicit operation key, at most
200 bytes. HTTP accepts operation_key or Idempotency-Key; conflicting
values return 409. MCP mutation tools require request_key. Reuse that key
when retrying the same operation; never generate another key for a retry.
The scope is workspace plus actor. Agent calls use the agent ID; explicitly
keyed autonomous routines without an author agent use the persisted routine and
crew IDs as a system actor. The same key with a different request or run
returns 409 operation_key_conflict. A successful retry returns the original
result and operation ID with replayed: true, after fresh authorization. An
issue that is no longer readable is not returned through replay.
The host verifies both the agent’s crew scope and the requesting human’s
current authority inside the mutation transaction. A card is anchored to the
host-admitted run and triggering message. Forged or unavailable run identity,
ended runs and revoked requester authority fail closed. Mutation, operation
result and reference commit or roll back together. Successful no-op updates
also reserve their key. Genuine host-admitted autonomous work retains its
existing mutation behavior without generating a human chat card. Explicitly
keyed autonomous operations also persist retry results; keyless autonomous
HTTP calls retain legacy behavior. Agentless autonomous routine actions must
come from authenticated host dispatch, match a persisted running routine in
its live workspace and crew. Keyless autonomous actions retain their legacy
workspace reach; explicitly keyed mutations and replay remain within the
admitting crew. A missing agent never removes a stored human origin. Human-origin
routine mutations still require an identified agent. Explicit show requires a
human chat origin.
Legacy crews without run tokens retain unkeyed HTTP and MCP issue mutations; the sidecar drops keys it cannot bind to a verified run. These calls retain legacy permissions and do not claim verified requester guarantees. Normal current execution provisions v2 tokens, including channel jobs and delegated runs; a v2 token cannot downgrade through an old sidecar. Replace retained pre-v2 sidecars and agent processes with old v1 credentials during rollout before claiming the new authority guarantees for them. Once run tokens are enabled, old agent tokens cannot mutate issues. This
delivery supports human-run create, update, comment and show. Work execution,
relations and attachment writes from human runs return
403 chat_issue_action_not_supported until their host paths implement the same
transactional authority contract; autonomous runs retain those existing verbs.
This guard is intentional and is not a claim that every issue action has been
migrated.
Delegated and deferred work
The host stores an immutable execution origin separately from the executing agent. Assignments, missions, peer queries, mentions, routine drafts, published routine heads and versions, schedules, pending work and pipeline runs preserve that origin. Completing the parent run does not turn its children autonomous. Each subsequent issue mutation rechecks the original human’s current authority and the executing agent’s access inside the mutation transaction. Revocation also prevents publishing a delegated result comment, while allowing the run to settle. Deleting the source chat revokes the origin without deleting its record. Only the authenticated host dispatcher may supply a direct pipeline execution context. Agent or crew tokens cannot assert an execution origin, and routine arguments cannot override identity fields. A missing or invalid persisted origin fails closed; it never falls back to autonomous execution. A new chat invocation supplies its own origin even when invoking a previously published routine. Non-chat public routine invocation retains the existing routine-launch authorization policy and any stored authoring origin; this contract does not replace standing automation grants with a new launcher role policy. Immutable pinned versions retain their original authoring origin. Unpinned schedules store only their own explicit invocation override; otherwise they resolve the current head at execution, rather than freezing its origin at schedule creation. A fresh chat save can bind the head to a fresh verified source. An authenticated OWNER/ADMIN may explicitly clear the head and selected schedule bindings through the audited operator origin-binding API. This never clears immutable versions or already admitted work. Clearing a binding does not re-enable a circuit-broken schedule; use its ordinary operator enable action afterward. Authenticated public issue creation, starting work, handing work back to an agent and commenting capture a web requester admission, rather than inventing a chat receipt. The resource identifies where admission happened; it does not grant unrestricted access or restrict all subsequent actions to that one issue. Existing backend action, target and crew checks remain authoritative. It pins the actual requesting person and membership identity plus a dedicatedweb_execution_revision. Only role, capabilities and access-mode changes
advance that fence; unrelated chat participant/grant changes do not revoke it.
Execution rechecks the live account, membership and target permissions.
Recurring issue admissions bind the authenticated recurring definition and
derive separate per-issue receipts without renewing the captured membership
epoch. Deleting a template does not revoke work it already admitted.
Existing admitted work retains its own origin when a later request starts new
work. These verified web origins authorize operations but do not manufacture
chat cards or a triggering chat message. Narrow CLI tokens cannot be converted
into a broader standing execution grant. Their permitted issue CRUD, comments
and recurring-template edits/disable remain available without acquiring a new origin;
explicit Start and human-to-agent handoff require capture-capable authority.
Unrestricted tokens and the explicit global wildcard follow the same admission
policy as the web session. The existing account-level restricted-member rule
is intentionally global across workspaces, consistently with chat admissions.
Permanent invalid recurring receipts disable the template, advance its due
bucket and notify a current eligible member. Transient database failures retry.
Legacy/scoped NULL-origin templates may generate ordinary issue records, but
do not authorize agent execution. Each such occurrence receives an immutable
unavailable origin (unverified-recurring:<issueID>, empty chat/message and
human_authority={}), including templates without creator attribution. This
prevents later assignment dispatch from classifying unknown provenance as
autonomous. The template remains NULL-origin and its schedule advances, with one reauthorization
notice to an eligible owner per template. A fresh authenticated Start can authorize
the occurrence explicitly. Repeated pauses deduplicate notices per template/origin.
Scoped execution-affecting edits preserve a valid origin only for its original
human. Another scoped editor must use a full authenticated session to capture
their own admission; otherwise a 403 problem with
requester_authority_required in its detail leaves the
entire template unchanged. Only labels and disable may preserve another person’s
origin. Mixed disable/content edits require the content editor’s admission.
Unbound scoped templates remain unbound; explicit scoped re-enable is refused
without changing the template.
Channel jobs without a verified authenticated admission may answer,
but their runs carry a host-derived missing-requester marker. Issue mutations
and further delegation fail closed; they do not inherit the completing run’s
authority or become autonomous. Queued mentions are grouped by their actual
stored origin and author, never the preceding run. A delegated execution’s
chat is resolved from its own host admission; the sidecar’s boot chat is never
a substitute for run identity. A verified source lacking a persisted execution
chat returns 403 delegation_execution_chat_unavailable; no chat is invented
and no unrelated issue receives delegation side effects.
Routine agent steps have durable ROUTINE journal admissions and publish agent
RUNNING state. Admission failure fails the step; this is intentional so every
issued run token has a durable authority record.
Backup and fork include origins alongside their dependent records. Forks clear
human receipts without granting copied work autonomy. Membership rows are restored,
but each imported membership receives a new web execution epoch above live and
archived epochs in the same restore transaction. Historical web receipts are
preserved for audit and become stale, including after replacement restore;
a fresh authenticated action must admit new web-origin work. Chat receipts still
require current audience authority. Restore does not promise exactly-once model
delivery: nonterminal mention reconciliation and fork claimed-state handling
remain tracked in #3103.
Upgrade boundary
The migration preserves legacy records, terminal history and queue status. For unfinished assignments, orchestration missions and routine work, recovery requires unique durable host-admission evidence. Exact historical human receipts are copied without renewing membership/chat epochs, then checked live at execution. Positively host-admitted machine roots retain autonomy. Missing, malformed, duplicate, cyclic or excessively deep provenance remains fenced until a fresh authenticated request admits replacement work. This includes historical UI/cron work without positive evidence; absence of a human receipt is not proof of autonomy. Legacy backup imports use the same rule when the origin column is absent. Omitting the Journal category does not authorize unknown work. Current backups with explicit autonomousNULL origins remain unchanged. Standing routine
grants remain unchanged. Old autonomous journal admissions without an explicit
host-admission marker also fail closed. Check affected queued work before deployment.
Authorization
Every request requires authentication and a selected workspace, through the same workspace middleware as chat history. Supply the workspace inX-Workspace-ID or workspace_id.
Only ordinary trusted sessions are supported. Restricted sessions receive an
opaque 404; these routes are absent from the restricted route allowlist.
This includes restricted Guest viewers even when ordinary issue access permits
them to read a shared project. The E1 frontend must project this unsupported
card capability as a generic “Unavailable” state, without exposing an object
ID, title, kind, action or count. It must not infer card visibility from ordinary
issue access, fetch through another identity, or display cached protected data.
The list requires current chat access, a live workspace and a live chat agent.
Each returned issue must belong to the selected workspace, matching the current
ordinary issue GET read predicate. A readable chat alone does not make a
foreign-workspace issue visible. Authorization is checked again after gathering
a page; a changed chat audience invalidates the response.
An operation is readable only when its issue is currently readable and at least
one of its source chats is readable in the selected workspace. Missing and
inaccessible operations both return 404. Operations without an authorized
source reference are unavailable through this endpoint.
Hard deletion through the public issue DELETE handler stores a minimal human
operation and deletion reference in every chat that previously referenced the
issue, atomically with deletion. Its actor is the actual human deleter; its run
and message IDs retain the original chat anchor. Old non-deletion references
are omitted after deletion. Explicit deletion references use entity.deleted: true and an empty identifier; no old title or arbitrary result is returned.
Only exact version-1 snapshots of the current ordinary trusted-workspace access
boundary are readable, with current workspace membership and chat access.
Restricted sessions, foreign workspaces, unsupported versions, duplicate or
additional snapshot fields fail closed. Future narrower issue access requires
a new evaluator. Issues deleted through other paths without a receipt remain
omitted.
All responses set Cache-Control: no-store.
Limited CLI tokens cannot mint an unrestricted WebSocket ticket or enqueue
steering text: both endpoints return 403 scoped_token_chat_unavailable until
scope-bearing chat authority is implemented. Unrestricted sessions/tokens and
the global * scope retain access. A version marker rejects old WebSocket
tickets that predate this gate, including cached tickets; clients must request
a fresh ticket on reconnect. Access and refresh tokens are unchanged. Public
journal projections omit human admission receipts and triggering-message IDs;
their durable host records remain available for authorization.
List references
GET /api/v1/chats/{chatId}/entity-refs
The response has these fields:
The initial page has
reset: true. Continuation pages have reset: false and
the same snapshot_id. Passing a completed cursor starts a new bounded
replacement snapshot, also with reset: true. This deliberately uses full
replacement polling instead of claiming an incomplete permission fingerprint
can safely support incremental visibility changes.
A snapshot fixes the upper reference position for pagination. It is not a
frozen historical copy of issue data: current authorization and object existence
are checked on each request. New references after that position appear in the
next replacement snapshot. Internal positions and counts of hidden records are
never returned.
Cursors are authenticated and encrypted with a process-local key, bind the
viewer, workspace, chat and captured authority, and expire after 15 minutes.
A cursor belonging to a different viewer/chat/workspace, a corrupted cursor,
an expired cursor or a cursor from a previous server process returns
400 {"error":"cursor_invalid"}. Discard local pagination state and retry
without it. If the captured authority changes while the viewer retains access,
a valid cursor starts a new replacement snapshot. If access is gone, the
request returns 404 instead. A database failure returns 503; an authority change while gathering one page returns 503 entity_refs_authority_changed, requiring a fresh snapshot.
Client reconciliation
- Load the first page without a cursor.
- On
reset, immediately remove the previous displayed list and stale authorized detail data; append subsequent pages only to thissnapshot_id. - Follow
next_cursorwhilehas_moreis true. - Reconcile cached entities against the complete replacement list only when
snapshot_completeis true. Absence from the first page does not prove that an entity is absent from the complete list. - Poll again using the completed cursor. This starts another replacement list.
Reference record
A reference containsevent_id, operation_id, kind, verb, entity, provenance, anchor
and revision_at_action. The supported kind is issue. The operation records
its verb and actor identity; the list does not expose operation keys, request
hashes, authority receipts, access snapshots or stored mutation payloads.
operation_id is accepted by crewship operation get. Live issues also include
current: title, status, priority, nullable assignee type/ID, current field and
aggregate revisions, and allowed_actions. This bounded batch projection and
run status are gathered with all references in one query, without per-card
operation/detail requests. Deleted receipts omit current entirely.
entity contains id, current identifier and workspace_id.
provenance contains actor: {type, id} and at. Actor display names are not
part of this projection. revision_at_action contains the issue’s revision
and aggregate_revision as recorded after the operation, not current counters.
anchor contains trigger_message_id, run_id and run_status. Status comes
from the durable run journal and can be running, completed, failed,
cancelled, timeout, or null when no matching run record exists. A failed
run can have a persisted reference even without a final assistant message;
clients can render it behind the triggering user message.
Read an operation
GET /api/v1/operations/{operationId}
Returns a sanitized projection with id, status, kind, verb, entity,
provenance and revision_at_action. It intentionally does not return the raw
stored result, operation key, hash, human authority or access snapshot.
This is a human-viewer projection, not a mutation replay endpoint.
Read current revisions
Both routes support an issue identifier or internal ID:GET /api/v1/issues/{identifier}/revisionGET /api/v1/workspaces/{workspaceId}/issues/{identifier}/revision
{ "revision": 1, "aggregate_revision": 1 }, with current integer
counters. Missing or inaccessible issues return 404. Revision counters come
from the issue and child-data database triggers; they are not timestamps or an
assertion that unrelated run/automation data has been refreshed.
The counters cover issue-owned fields and child records, not a version of the
entire rendered detail. Joined crew, project, routine, creator, assignee, owner,
delegate and worker display names, membership-dependent name visibility, run
state and allowed_actions are read-time projections. Renaming an assignee or
changing an active run can change a GET response while both counters stay equal.
Label membership belongs to the field revision; label definition changes also
invalidate the aggregate through the label trigger.
An open detail must refresh its current GET on focus, reconnect and a bounded
poll while visible, even when the revision counters match. Resource/roster
events can refresh joined names sooner but do not replace reconnect recovery.
Until dedicated projection freshness is supplied, show the detail’s last
successful fetch time; counter equality alone must never label the entire
detail as live. Refresh permissions before presenting an action after a stale
interval; the mutation endpoint remains authoritative. The reference endpoint
already returns replacement snapshots of current card data on each completed
poll, including current actions.
Existing issue detail responses also include revision, aggregate_revision
and allowed_actions. Revisions are selected with the issue fields, rather
than assigned after another query. Actions describe the viewer’s current
permission and issue state. Delete is offered only for BACKLOG/CANCELLED;
start_work only for BACKLOG/TODO with no active assignment/routine or human
hold; workflow changes are omitted for terminal issues. Mutation endpoints
still reauthorize and validate state.
Human card edits and comments
ExistingPATCH /api/v1/crews/{crewId}/issues/{identifier} accepts optional
expected_revision (positive integer) or If-Match (positive integer, optionally
quoted). If both appear they must agree. A stale edit returns
409 {"error":"revision_conflict","revision":2} without mutation.
This header is a numeric issue-field precondition, not general HTTP ETag
negotiation: wildcard *, weak validators and validator lists are rejected.
Clients must not infer an ETag for the complete joined detail from this number.
PATCH and POST .../comments accept Idempotency-Key (1–200 UTF-8 bytes),
scoped to the authenticated human and selected workspace. The reserve, mutation
and successful response commit together. A retry with unchanged intent returns
the original response/status and Idempotency-Replayed: true; both responses
include Operation-ID. Changed intent returns 409 operation_key_conflict.
Current account and membership authority are checked before replay; a successful
old response cannot authorize access after revocation. Replays do not repeat
comments, activity or mention dispatch. PATCH labels and fields are atomic.
Mutation-owned activity records and durable delivery intent commit in the same
transaction. A bounded server worker resumes pending intent after disconnect,
restart or a lost acknowledgement. Stable journal replay IDs and stable mention
assignment IDs prevent repeating completed effects. Human activity uses an
issue-activity:<event_id> audit trace, never a fabricated agent run ID.
Pending journal audit intent survives hard deletion with a historical issue
reference; pending model mentions are cancelled when their comment or issue is
deleted. Recovery preserves the original audit event without exposing a deleted
object snapshot through the chat reference API.
Human and agent Create, PATCH/comments, bulk edits, attachment add/remove and
code-link add/remove persist audit intent with their mutation. Other issue
workflow audit paths retain their existing behavior; this contract does not
claim all historic issue emitters were converted.
Request-side recovery is bounded to the authenticated/verified workspace
(32 jobs, one second); it may recover other jobs in that same workspace, never
another workspace. The background worker handles global recovery.
Retries back off exponentially from five seconds to ten minutes and stop after
eight attempts. Malformed payloads and permanent authority refusals fail
immediately, without blocking the tail. Only terminal failures tell the author
to retry; their private notice and failed receipt commit together. Administrators
can inspect sanitized diagnostics with crewship issue deliveries failed.
Successful ACKs clear the payload and completed receipts expire after seven
days. Failed rows remain for diagnosis; they are not retried automatically.
A committed autonomous pipeline mention carries a server-only admission marker
and source crew snapshot; neither can come from tool arguments. Recovery checks
its persisted origin-free pipeline run/version and live source actor. Ordinary
agent machine roots require positive host run.started admission. Unproven
legacy agent sources do not acquire autonomous mention execution authority.
Journal outages retain retry intent and do not starve unrelated mention work.
Normal restore recognizes an exactly matching already-written journal effect;
forks retain history but never replay source notifications or model dispatches.
Label membership changes advance the field revision as well as the aggregate
revision, so concurrent labels-only editors cannot overwrite a newer label set.
Replacing a label set with the same IDs, in any order or with duplicates, is a
no-op and does not advance either counter. A changed set writes only its diff.
These parameters remain optional for old clients. Chat card editors MUST send
both an explicit expected revision and stable retry key; they retain the same
key/precondition after a timeout and start a new operation after resolving a
conflict. Comment composers MUST retain their key until success is known.
CLI
--cursor is transmitted without parsing
or rebuilding it. Neither command follows every page automatically or executes
work. API authorization failures produce a failing CLI exit status.
Implementation and fixtures
- Handlers and cursor codec
- Handler, authority and route tests
- Handler-verified JSON fixtures
- CLI commands
- Built-binary CLI acceptance
- Operation and reference storage
Closeout regression matrix and delivery limits
Automated evidence and live-agent acceptance are separate. These tests protect existing paths; the dev-slot real-agent restart/retry/revocation/restore scenario and the 500-reference / 50-object / 10-viewer performance profile remain required before promoting this E0 delivery.
Projection-only attachment/code-link drains never consume mention attempts.
Delivery outcome ACK/backoff/terminal writes detach from the request deadline
with a separate two-second bound and retain the same lease fence. Foreground
recovery is still workspace-scoped, not restricted to the originating transaction.
Plain comments now produce
mission.comment audit events and follow existing
issues.comment notification preferences and automation rules; no preference is
silently enabled or muted. The issue-activity: audit trace is not an agent run
and renders as an empty event.run_id in automation inputs.
Remaining bounded follow-ups: ordinary CRUD versus unavailable admission (#3101),
legacy comment source attribution (#3102), restore/fork mention reconciliation
(#3103), failed payload retention and safe journal recovery (#3104), and bulk
label/project audit semantics (#3105). Failed jobs currently retain payloads;
exhausted journal projections require operator diagnosis and have no requeue API.
Bulk status/priority/assignment audit is transactional; bulk label/project fields,
internal relation changes, workflow and Inbox mutation doors are not covered by
this PR’s durable audit guarantee.