opbox

The file.* family (39 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.

Logfile.access.log

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

Append a file-access audit row. { fileId, accessedById?, operation, grantId?, ipAddress?, userAgent? }. accessedById defaults to the acting user.

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

Access Logfile.accessLog

PropertyValue
Capabilityfile.read
Risk classRead - autonomy L0
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

The per-file forensics: the audit events naming it + any deletion certificate. { fileId }. Reads the event log (the file’s lifecycle/access events) + the deletion_certificate (if any). RLS-scoped + explicit ws predicate; sensitive-read forensics (ADMIN).

Call POST /v/file.accessLog with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Archivefile.archive

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

([2026] VJS-CC-OPBOX 55). Move a file to status=ARCHIVED (retired but RECOVERABLE - the bytes are RETAINED, never purged). { fileId }. This is DISTINCT from the DESTRUCTIVE file.retain.sweep (which writes a DeletionCertificate + purges bytes): archive is a reversible lifecycle move, not a regulated destruction. CAS on a NON-TERMINAL status - an already-DELETED file (purged bytes) or an already-ARCHIVED file is refused (fail-closed Conflict; idempotent-safe verdict is the Conflict, not a silent no-op, so the caller learns the file was not in an archivable state).

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

Createfile.attachment.create

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

Create an attachment row; the submission is fenced in ctx.workspace_id (fail-closed). { submissionId, filename, fileUrl, fileSize, mimeType }.

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

Deletefile.attachment.delete

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

Delete by id (fenced via submission) OR bulk by { fileUrls:[...] } (all attachments whose submission is in ctx.workspace_id). Idempotent. Returns count.

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

Deletefile.delete

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

Sets deleted_at = NOW() on the file_record. The file remains readable to workspace members via file.get (status stays CURRENT); it is excluded from share resolution. This is DISTINCT from file.soft_delete (which flips status = 'DELETED' in the lifecycle machine) — a deliberate lighter-weight tombstone scoped to the share-link liveness gate.

A file that is already deleted (deleted_at NOT NULL) is rejected with Conflict.

Input { fileId } (fields ending in ? are optional)

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

Downloadfile.download

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

Fetches the file from the storage backend, decrypts it, and returns the bytes base64-encoded. disposition is “inline” | “attachment” (default “attachment”) — passed through so the Next.js route can set the correct Content-Disposition header without a second round-trip.

The file must belong to this workspace and must not be deleted (deleted_at IS NULL).

Input { fileId, disposition? } (fields ending in ? are optional)

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

Publicfile.download.public

PropertyValue
Capabilityfile.read
Risk classRead - autonomy L0
Min. permissionExternal (portal / signer token)
AvailabilityCore - always on (MCP tier 1)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Resolves a share token (same hash+expiry+liveness logic as file.share_link.resolve), then decrypts and returns the file bytes base64-encoded. No workspace session required — the token is the credential. All failure modes return the same verdict (oracle prevention).

Input { token, disposition? } (fields ending in ? are optional)

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

Setfile.ext.set

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

Upsert/patch the frontend ext row keyed by fileId (the file_record_ext PK). Partial: only provided fields are written on update; create defaults starred=false, versionNumber=1. { fileId, storageKey?, storagePath?, checksum?, folder?, starred?, versionNumber?, globalIdentityId? }.

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

Toggle Starredfile.ext.toggleStarred

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

Atomic starred-flip (folds toggleFileStarred’s read-then-write). Synthesises the ext row at starred=true when absent. { fileId }. Returns the resulting boolean.

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

Createfile.folder.create

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

Create a folder row; 409 (CONFLICT) on a duplicate (workspace, path). { path, parent? }. created_by_id = acting user (nullable).

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

Deletefile.folder.delete

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

Reparent the files under the folder (file_record_ext.folder -> parentFolder) THEN delete the file_folders subtree (path + path/%). Subsumes folder-sql.ts moveFolderFilesToParent + deleteFolderTree. { path, parent? } (parent = the destination for orphaned files).

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

Renamefile.folder.rename

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

Re-path the file_folders subtree (path SUBSTRING + recomputed parent) AND re-prefix the per-file file_record_ext.folder for oldPath + oldPath/% (subsumes folder-sql.ts renameFolderTree + renameFolderPrefix $executeRaw). { oldPath, newPath }.

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

Getfile.get

PropertyValue
Capabilityfile.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 METADATA read is gated only by the ACL (assert_can_read) + the registry tier gate (MEMBER). includeBytes:true ADDITIONALLY enforces the File’s SENSITIVITY (INV-11, the catalogue’s file.get gate): the required minRole for (sensitivity, READ) is read from the file_policy SENSITIVITY body when authored, else a built-in fail-closed default ladder (NORMAL→MEMBER, SENSITIVE→ADMIN, CONFIDENTIAL→OWNER, RESTRICTED→OWNER, an unknown sensitivity→OWNER). A caller whose resolved tier is BELOW that minRole is REFUSED the bytes (Forbidden) while still receiving the metadata. This is a REAL sensitivity-gated read, not just a surfaced tag (the gap the verifier found: the bytes were released after only the ACL + MEMBER tier).

Input { fileId, includeBytes? } (fields ending in ? are optional)

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

Createfile.grant.create

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

COURT-GATED cross-workspace write: the row references TWO workspaces. Binds ctx.workspace_id = custodianWorkspaceId, resolves fileId there, and treats grantedToWorkspaceId as payload (the deliberate RLS-fence exception). The sensitivity GRANT minRole guard (checkSensitivityAccess, R4-3) MUST be enforced at the wiring seam before this verb is reached. { fileId, custodianWorkspaceId, grantedToWorkspaceId, documentPresenceId?, accessLevel?, purpose?, expiresAt? }.

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

Deletefile.grant.delete

PropertyValue
Capabilityfile.write
Risk classDestructive - autonomy L3
Min. permissionOwner
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

DESTRUCTIVE cross-workspace hard-purge of all grants where the workspace is custodian OR grantee (org teardown). COURT-GATED (runs under org-admin, not a workspace session). { workspaceId }. Returns count.

Call POST /v/file.grant.delete with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Expirefile.grant.expire

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

Flip ACTIVE -> EXPIRED. grantId = a single lazy expire; absent = sweep ALL active grants in the workspace with expiresAt < now (the bearerless cron path - DEFERRED). Idempotent. Returns count.

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

Revokefile.grant.revoke

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

Flip ACTIVE -> REVOKED (revokedAt=now, revokedById). EXACTLY ONE selector: grantId | fileId | { workspaceA, workspaceB } (the custodian/grantee pair, either order). Idempotent (only ACTIVE rows). Returns count. { ..selector, revokedById? }.

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

Historyfile.history

PropertyValue
Capabilityfile.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

The version chain + resolved CURRENT for a file. { fileId }. Walks the previous_version_id chain (the supersede lineage). RLS-scoped + explicit ws predicate.

Call POST /v/file.history with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Holdfile.hold

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

Place a LEGAL_HOLD. { fileId, reason? }. A held file is never deleted/expired by the retention sweep (the deterministic precedence). Sets legal_hold=true; if the file was CURRENT it moves status->LEGAL_HOLD (the held standing is legible). Idempotent (re-hold = no-op).

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

Createfile.link.create

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

Batch-insert file<->entity links, skip-duplicates on the (file_id,“entityType”,entity_id,role) unique. Each fileId is fenced in ctx.workspace_id. Returns the created ids. { links:[{ fileId, entityType, entityId, tableId?, role?, kind?, sourceFileId?, metadata?, createdById? }] }.

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

Deletefile.link.delete

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

Delete one link by id, fenced via the link’s file (idempotent). { id }.

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

Updatefile.link.update

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

Patch a single link’s metadata. Fenced via the link’s file. { id, metadata }. Returns { id, changed }.

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

Listfile.list

PropertyValue
Capabilityfile.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

List the workspace files (all current + historical status). { status?, sensitivity? } (optional filters). Workspace-scoped + explicit ws predicate. Returns { items: [...] } ordered by created_at DESC.

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

Setfile.policy.folderTemplate.set

PropertyValue
Capabilityfile.policy.author
Risk classWrite - autonomy L1
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Author a folder-template (typed nodes carry role). { body }. Role is a node facet, never path-regex-derived (gotcha 13).

Call POST /v/file.policy.folderTemplate.set with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Get Retentionfile.policy.getRetention

PropertyValue
Capabilityfile.policy.author
Risk classRead - autonomy L0
Min. permissionOwner
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Return the workspace’s retention policy list stored in the file_policy row keyed RETENTION. Transitional: the kernel does not yet act on this body (the sweep uses per-file retained_until), so the shape is opaque to the kernel.

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

Get Sensitivityfile.policy.getSensitivity

PropertyValue
Capabilityfile.policy.author
Risk classRead - autonomy L0
Min. permissionOwner
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Return the workspace’s sensitivity policy list stored in the file_policy row keyed SENSITIVITY. Transitional: the kernel does not yet enforce this body (enforcement reads the per-row sensitivity + role check), so the shape is opaque to the kernel.

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

Set Retentionfile.policy.setRetention

PropertyValue
Capabilityfile.policy.author
Risk classDestructive - autonomy L3
Min. permissionOwner
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Author the per-sensitivity retention policy. { body }. Regulated retention is non-negotiable; deterministic match (the sweep evaluates it).

NOTE (input contract, deferred): the policy body is stored opaquely and has NO consumer yet - file.retain.sweep purges off the per-file retained_until COLUMN directly, never this row. A typed input contract (the “schema” a UI would author against) cannot be fixed until the design decision for HOW a stored RETENTION policy drives each file’s retained_until lands; inventing a shape now would be a speculative guess against a non-existent reader. Tracked, not built.

Call POST /v/file.policy.setRetention with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Set Sensitivityfile.policy.setSensitivity

PropertyValue
Capabilityfile.policy.author
Risk classDestructive - autonomy L3
Min. permissionOwner
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Author the per-(sensitivity,operation)->minRole authz rows. { body }. Process-level blast radius (governs every future file op).

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

Putfile.put

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

INV-7: the bytes are AES-256-GCM sealed before write (no plaintext bytes column). The id is minted FIRST so the AAD binds the ciphertext to this exact row. Returns the new file id + metadata.

Input { filename, bytes(base64), contentType?, sensitivity?, origin? } (fields ending in ? are optional)

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

Release Legal Holdfile.releaseLegalHold

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

Release a LEGAL_HOLD. { fileId }. Clears legal_hold; a file at status=LEGAL_HOLD returns to CURRENT. Idempotent (releasing an un-held file = no-op). After release the file is once again eligible for the retention sweep (the hold no longer blocks deletion).

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

Replace bytesfile.replace_bytes

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

([2026] VJS-CC-OPBOX 55).

The reupload / AI-regen contract: NEW bytes replace the existing file’s bytes IN PLACE, PRESERVING the file_record id (so every annotation FK, bundle source-ref, audit-chain reference, and portal link that names this id stays valid). This is DISTINCT from file.put (which MINTS a new id) and from file.supersede (which mints a new id + chains the old to it). The id-preserving re-seal is the only path the audit chain can follow across a re-version without a relink, which is why CC-OPBOX 55 authorised it as its own typed verb (NOT a generic update).

INV-7: the new bytes are AES-256-GCM sealed before write (the AAD is the SAME (workspace, file id), so the re-sealed envelope is still bound to this exact row). content_sha256 + byte_size are recomputed over the new plaintext; version_number is bumped by 1 (the same-id re-seal counter, migration 0098). The previous_version_id distinct-id supersede chain is NOT touched (it tracks new-id lineage, orthogonal).

Guard: the row must EXIST in this workspace AND be NON-TERMINAL (not DELETED/ARCHIVED) - replacing the bytes of a deleted (purged) or archived file is a fail-closed Conflict (you re-seal a LIVE file, never a tombstoned/retired one). contentType is optional; absent ⇒ the existing content_type is preserved.

Input { fileId, bytes(base64), contentType? } (fields ending in ? are optional)

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

Sweepfile.retain.sweep

PropertyValue
Capabilityfile.retain
Risk classDestructive - autonomy L3
Min. permissionOwner
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

Run the retention sweep. { fileId?, mode? }. Deterministic precedence: a file under LEGAL_HOLD or with a future retained_until is SKIPPED (never deleted). For an eligible file, mode=ARCHIVE flips status->ARCHIVED; mode=HARD_DELETE writes a DeletionCertificate (NFR-SEC-8) THEN purges the bytes (bytes_encrypted -> empty, status->DELETED). The record + cert survive (the index + audit references survive - only the BYTES are purged). Idempotent on a re-run (an already-DELETED file is skipped). Scope: one fileId, or the whole workspace if absent.

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

Set Sensitivityfile.setSensitivity

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

Set a file’s sensitivity (NORMAL|SENSITIVE|CONFIDENTIAL| RESTRICTED). { fileId, sensitivity }. Enforced at file.get (INV-11). A DELETED file is refused.

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

Createfile.share_link.create

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

Verifies the file belongs to this workspace and is not deleted. Generates 32 CSPRNG bytes, hex-encodes them to a 64-char token, stores the SHA-256 of the token in share_token_hash, and sets share_expires_at. Returns the raw token (NEVER stored — the caller must save it; it cannot be recovered from the kernel later). A second call overwrites any previous link.

Input { fileId, expiresInSeconds? } (fields ending in ? are optional)

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

Resolvefile.share_link.resolve

PropertyValue
Capabilityfile.read
Risk classRead - autonomy L0
Min. permissionExternal (portal / signer token)
AvailabilityCore - always on (MCP tier 1)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Hashes the incoming token and does a point-lookup via the partial index on share_token_hash. Returns file metadata (id, filename, contentType, workspaceId) if the token is valid, not expired, and the file has not been deleted. The caller can use file.get with workspace auth to retrieve decrypted bytes.

Any failure (not found / expired / deleted) returns the same BadInput verdict to avoid leaking which condition was hit (token oracle prevention).

Auth: External (rank 0 — any authenticated caller, the minimum tier the kernel supports). The kernel has no fully unauthenticated verb path. Workspace RLS is NOT scoped here: the lookup is by token hash across the whole table; the partial index + liveness guards are the gate.

Input { token } (fields ending in ? are optional)

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

Revokefile.share_link.revoke

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

NULLs both share columns. Idempotent: revoking a file with no active link is a no-op success.

Input { fileId } (fields ending in ? are optional)

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

Soft deletefile.soft_delete

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

([2026] VJS-CC-OPBOX 55). Move a file to status=DELETED as a REVERSIBLE tombstone - the BYTES ARE RETAINED (the deletion is a logical hide, recoverable). { fileId }. This is DISTINCT from the DESTRUCTIVE file.retain.sweep HARD_DELETE (which writes a DeletionCertificate THEN purges the bytes): soft_delete never touches the bytes, never writes a certificate. CAS on a NON-TERMINAL status - an already-DELETED file is refused (fail-closed Conflict).

NOTE (CC-OPBOX 55 scope): the GDPR created_by PII-scrub is RESERVED (subject-rights / CC-OPBOX 26) and is OUT OF SCOPE here - this verb only flips the status, it never scrubs the creator attribution.

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

Supersedefile.supersede

PropertyValue
Capabilityfile.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Mark an existing file SUPERSEDED by a newer one. { fileId, newFileId }. Sets the new file’s previous_version_id = fileId (the chain) + flips the old to SUPERSEDED in ONE tx (audit on the transition). A held/DELETED file is refused (a held file is frozen). Both files must be in this workspace (RLS + explicit predicate).

Call POST /v/file.supersede with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.