opbox

The form.* family (10 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.

Createform.create

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(DEFERRED mixed bearered/bearerless). { title, description?, schema?(={}), status?(=PUBLISHED), createdById? }. created_by_id defaults to the acting user (NOT NULL). Mints form_.

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

Deleteform.delete

PropertyValue
Capabilityform.write
Risk classSensitive - autonomy L2
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(DEFERRED, destructive). Fenced id+workspace; FK cascade clears form_versions/form_submissions/form_favourites/form_watches. rows_affected==0 -> BadInput. { id }.

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

Createform.favourite.create

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Idempotent INSERT … ON CONFLICT (form_id, user_id) DO NOTHING. The form is fenced within the workspace. { formId }; userId = acting user.

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

Deleteform.favourite.delete

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

DELETE by (form_id, acting user, workspace); idempotent no-op if absent (no id round-trip needed). { formId }.

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

Generateform.generate

PropertyValue
Capabilityform.gen
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

The sibling of doc.generate.

NEVER auto-published (the load-bearing invariant). The save path is board.create(kind

, status=DRAFT) → board.validate → board.publish (a SEPARATE, ADMIN, deliberate step). This verb runs the lint + refuse-harmful gate (INV-9) and lands a DRAFT board only. An agent-authored schema that fails the lint is REFUSED (no silent default); a published-form is impossible from this verb.

Input { prompt, title?, schema? } (fields ending in ? are optional)

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

Ingestform.ingest

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionExternal (portal / signer token)
AvailabilityCore - always on (MCP tier 1)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

The pipeline (TASK-FORM-04, fixed ordering):

  1. PUBLISHED-gate: a submission against a non-ACTIVE(=published) intake-form board is hard-rejected.
  2. Idempotency: a stable submissionId dedups a callback/retry (the second ingest is a no-op).
  3. For each data field: sanitise strings → DETECT an inline data: base64 file-URL → extract it into an ENCRYPTED Fact (INV-7, never a plaintext passport scan in a JSON column) and replace it with a {_type:'stored_file', storageKey, ...} ref; a plain value lands as a key-bound Fact (C7, bound to the field key).
  4. If a matterId (+ optional stepId) is supplied, route to matter.advance - the ONE step-completion write path (INV-1). The submission→work on-ramp is the canonical verb, not a shadow write. (A submission with no matter simply persists - intake without a spawn is valid.)

Input { formId, data, submissionId?, matterId?, stepId? } (fields ending in ? are optional)

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

Updateform.update

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(DEFERRED). Partial PATCH fenced by id+workspace. { id, title?, description?|null, schema?, status?, currentVersion? }. rows_affected==0 -> BadInput not-found.

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

Createform.version.create

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

(DEFERRED). ONE verb = both writes atomically: INSERT form_versions (version = MAX+1, serialised by a tx-scoped advisory lock on form_id, salt 71) AND UPDATE forms.current_version, on ctx.tx. { formId, schema, title, description?, status, changelog?, createdById? }. Mints fver_.

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

Createform.watch.create

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Idempotent INSERT … ON CONFLICT (form_id, user_id) DO NOTHING. The form is fenced within the workspace. { formId }; userId = acting user.

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

Deleteform.watch.delete

PropertyValue
Capabilityform.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

DELETE by (form_id, acting user, workspace); idempotent. { formId }.

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