opbox

The step.* family (8 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.

Activatestep.activate

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

PENDING→ACTIVE manual override (ADMIN). { matterId, stepId }.

Call POST /v/step.activate with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.activate.

Liststep.list

PropertyValue
Capabilitymatter.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

The workspace-wide matter-step list ([2026] CC-OPBOX 24 gate (b): surfaces the matter_step rows the matter engine writes, which NO read verb exposed - matter.get returns only ONE matter’s steps). Slim standing (id/matterId/stepIndex/title/stepKind/status). Access = the PARENT MATTER is readable: the permission::readable_sql("matter") fence is evaluated INSIDE the matter subquery (so its unqualified columns visibility/owner_id/id resolve to matter, never matter_step) - exactly the INV-17 belt-and-braces matter.list carries. { matterId?, status? }, paginated, RLS-scoped. Pure read: no new table/enum/write/state (the lawful gate-(b) substrate-extension, CC-OPBOX 24 obiter 3).

Call POST /v/step.list with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.list.

Parkstep.park

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

park an ACTIVE step BLOCKED on a runtime failure (ADMIN, SENSITIVE). { matterId, stepId, reason? }.

The workflow engine’s block-on-failure leg: an ACTIVE step whose runtime work FAILED is parked BLOCKED WITHOUT a completion (no completed_at stamp) and WITHOUT a cascade (no progress() call). This is the missing peer of the step-lifecycle family and is distinct from every existing verb:

  • step.reject parks BLOCKED but SETS completed_at = now() AND runs progress() (a forward cascade) - it treats the block as a phase-completing event; a failed-while-running step has NOT completed.
  • step.revert parks BLOCKED with NO cascade but REQUIRES a COMPLETED step (it un-does a completion); a step.park subject is ACTIVE (it never completed), so revert’s guard refuses it.
  • step.skip sets SKIPPED + completed_at + cascades. None fit “an ACTIVE step failed: BLOCK it, leave completed_at NULL, run NO cascade so downstream stays exactly where it is”.

Modelled on step.revert (same no-cascade shape): the step must be ACTIVE (else Conflict), the matter must be non-terminal (guard_non_terminal, INV-8), engine-owned cross-matter waits are refused, the write is workspace/CAS-scoped, an INV-9 0-rows belt guards a phantom audit. SENSITIVE/ADMIN (a trust-boundary lifecycle override, the same tier as step.reject/revert/reopen/skip); idempotent

(the ACTIVE guard refuses a re-park - a parked step is BLOCKED, not ACTIVE).

Call POST /v/step.park with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.park.

Rejectstep.reject

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

park a step BLOCKED with a reason (ADMIN). { matterId, stepId, reason? }.

Call POST /v/step.reject with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.reject.

Reopenstep.reopen

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

a terminal step → ACTIVE, DATA-PRESERVING (COMPLEX reopen is first-class, ADR-0012). { matterId, stepId, resetDownstreamRange? }. ADMIN. Clears completed_at; keeps data.

resetDownstreamRange (optional bool, default false = the original behavior, BACK-COMPAT, [2026] VJS-CC-OPBOX 60): when TRUE, reopening also RESETS the lane-isolated downstream range. After the same terminal-target guard, every LANE-ISOLATED step with step_index > target.step_index in the matter is reset to PENDING (status=PENDING, activated_at=NULL, completed_at=NULL - DATA-PRESERVING, the data column is NEVER cleared), the target is ACTIVATED, then the ONE progression engine runs ONCE (the same progress() / next-candidate path the engine uses on advance - never a second hand-written progression). This MIRRORS matter.goto’s backward-reset (goto_skip_or_reset backward branch + the progress engine): “lane-isolated” = the SAME (container_id, lane_id) scope goto resets, so reopening one lane does not disturb a parallel lane. Without the flag, reopen is the original single-step data-preserving reactivate.

Call POST /v/step.reopen with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.reopen.

Revertstep.revert

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

UN-COMPLETE a just-completed step (ADMIN, SENSITIVE). { matterId, stepId, reason? }.

[2026] VJS-CC-OPBOX 97 (group step-lifecycle, SITE 1). The coverage gate completes a step, then discovers the step failed its required-coverage check AFTER the fact, so the completion must be UNDONE: the step returns COMPLETED -> BLOCKED, the completion stamp (completed_at + completed_by) is CLEARED, and - the whole point of this verb vs step.reject - there is NO cascade (no progress() call). This is distinct from every existing step verb: step.reject sets completed_at = now() (it does NOT clear it) AND runs progress() (a forward cascade a revert must not do); step.reopen goes terminal -> ACTIVE (not -> BLOCKED) and may reset a downstream range; step.skip sets SKIPPED + completed_at + cascades. “Un-complete back to BLOCKED, clear the completion stamp, NO cascade” has no other verb. SENSITIVE/ADMIN (a trust-boundary lifecycle override, the same tier as step.reject/reopen/skip); idempotent

(the COMPLETED guard refuses a re-revert).

Call POST /v/step.revert with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.revert.

Set Configstep.setConfig

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

The matters-engine reconfigure path ([2026] VJS-CC-OPBOX 60). { matterId, stepId, config, expectConfigCrc? }. An idempotent CAS PATCH of the engine-authored matter_step.config JSONB: it overwrites the WHOLE config (+ updated_at) for the named step, optionally gated on expectConfigCrc matching a config_crc of the CURRENT stored config (fail-closed Conflict on mismatch = a concurrent write landed between the caller’s read and this write). Status is UNTOUCHED: config is the AUTHORED step shape, ORTHOGONAL to the PENDING/ACTIVE/…/terminal state machine, so a reconfigure never advances/reopens/blocks the step. This is DISTINCT from the rejected generic step.setData (CC-OPBOX 54 HEAD 2 refused it): config is authored, data is runtime - the engine reads config for visibility/onComplete/typed-step shape, while data is the caller’s runtime payload. Guarded non-terminal (a terminal matter is immutable, INV-8); ONE INV-8 event at dispatch level.

Call POST /v/step.setConfig with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.setConfig.

Skipstep.skip

PropertyValue
Capabilitymatter.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

→SKIPPED audited override (ADMIN). { matterId, stepId }. INV-3: completedAt set.

Call POST /v/step.skip with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool step.skip.