Guide
The upload audit trail — who wrote what, and when
Every upload-link issuance and every claim lands in `upload_link` — a governed, forensic record of who touched the workspace. Read the trail from the dashboard, from `info`, and (for operators) directly from Postgres.
5/18/2026 · connect0 · 3 min
Because the core sees every write, "who put this file here?" is a lookup, not an investigation.
connect0 keeps a forensic record of every upload-link issued: who issued it, when it expires, whether the upload completed, and how many bytes R2 actually saw. The record exists for three reasons:
- Debug "did the upload work?" — the same question every user has after their first PUT.
- Operations — when something looks weird ("why is project X 50 GB?"), follow the rows.
- Compliance — for accounts on the audit plan, the trail is the answer to "who put this file here?"
From the dashboard
/a/<account>/projects/<project>/upload has a "Recent uploads" card showing the 10 most recent entries. Each row shows:
target_path— where the file landed under/workspace/.source—mcp(viaupload.create_link),mcp-write(viaupload.write), orweb(dashboard drag-drop).bytes_received/content_length— how big it actually was vs. how big the link promised.- Status —
pending/claimed/expired/oversize(see the status guide).
From the agent's perspective
> What did I upload to this project recently?
Agent calls info with both slugs set. current_project.recent_uploads returns the same 10-row window. Useful when the agent itself is the consumer — it can confirm a file landed without you having to switch to the dashboard.
For operators: direct SQL
The upload_link table lives in connect0_controlplane. Schema:
upload_link (
id UUID,
project_id UUID REFERENCES project(id),
ip_local_id TEXT, -- the Identity Platform user id that issued the link
client_id TEXT, -- OAuth client id (when issued via API token)
target_path TEXT,
content_type TEXT,
content_length BIGINT, -- declared by the agent
expires_at TIMESTAMPTZ,
issued_at TIMESTAMPTZ,
claimed_at TIMESTAMPTZ, -- null = never used
bytes_received BIGINT, -- what R2 actually saw
etag TEXT, -- from the R2 event
oversize BOOLEAN, -- bytes_received > content_length
source TEXT
)
Common ops queries:
-- All uploads for a project in the last 24h.
SELECT issued_at, ip_local_id, target_path, status_label
FROM upload_link
WHERE project_id = '<uuid>'
AND issued_at > now() - INTERVAL '24 hours'
ORDER BY issued_at DESC;
-- Oversize incidents — files bigger than declared.
SELECT * FROM upload_link WHERE oversize = true ORDER BY claimed_at DESC LIMIT 50;
-- Stale unclaimed links — issued but never used, useful for cleanup analytics.
SELECT count(*) FROM upload_link WHERE claimed_at IS NULL AND expires_at < now() - INTERVAL '7 days';
What the trail can't tell you
- The file's contents. We never log stdout/stderr or upload bodies. The audit is about that a write happened, not what was written.
- The user's identity beyond
localId. Cross-reference to Identity Platform's directory to get a name/email. - Anything about files written by
sh "tee"orsh "cat >". Those go throughsandbox_invocation, notupload_link. The R2 event notification fires for them too, but it doesn't match anupload_linkrow and is silently dropped.
Retention
90 days. After that the rows are pruned by the daily housekeeping job. Audit-plan accounts get longer retention per their contract.
Next steps
Where this fits
This is the One system of record pillar of connect0 — the core that connects everything your company runs on. Because every upload flows through the core, it records who issued each link and what R2 actually received — so the audit trail is a byproduct of how the system works, not a bolt-on. Start building → · All guides →