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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | External (portal / signer token) |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Destructive - autonomy L3 |
| Min. permission | Owner |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.policy.author |
| Risk class | Write - autonomy L1 |
| Min. permission | Admin |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.policy.author |
| Risk class | Read - autonomy L0 |
| Min. permission | Owner |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.policy.author |
| Risk class | Read - autonomy L0 |
| Min. permission | Owner |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.policy.author |
| Risk class | Destructive - autonomy L3 |
| Min. permission | Owner |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.policy.author |
| Risk class | Destructive - autonomy L3 |
| Min. permission | Owner |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.retain |
| Risk class | Destructive - autonomy L3 |
| Min. permission | Owner |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.read |
| Risk class | Read - autonomy L0 |
| Min. permission | External (portal / signer token) |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | Yes |
| Egress | None - in-box (pure Postgres, no outbound call) |
| Render schema | Yes - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Core - always on (MCP tier 1) |
| Idempotent | Yes |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Sensitive - autonomy L2 |
| Min. permission | Admin |
| Availability | On demand (MCP tier 2) |
| Idempotent | No |
| Egress | None - 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
| Property | Value |
|---|---|
| Capability | file.write |
| Risk class | Write - autonomy L1 |
| Min. permission | Member |
| Availability | Admin only (CLI/HTTP; never advertised over MCP) |
| Idempotent | No |
| Egress | None - 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.