feat(core): add the write audit log and the workspace create service - #2087
feat(core): add the write audit log and the workspace create service#2087ka1kqi wants to merge 6 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
…emplar Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
PR overviewThis pull request adds persistent audit-log storage and a trusted internal endpoint and service for creating workspaces with idempotent behavior. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Scanned with Semgrep · TruffleHog · Trident review. View in Trident Fixed/addressed: 0 · PR risk: 0/10 |
There was a problem hiding this comment.
1 issue found across 9 files
Confidence score: 3/5
- In
frontend/ui/src/lib/write-services/audit.ts,writeAuditinserts into the interactive transaction beforecreateWorkspacecommits; if that insert aborts the transaction, catching the error can leave the workspace transaction unusable and cause the surrounding operation to fail. Handle audit writes outside the resource transaction or roll back/propagate the transaction failure explicitly.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/ui/src/lib/write-services/audit.ts">
<violation number="1" location="frontend/ui/src/lib/write-services/audit.ts:20">
P1: When `writeAudit` receives the interactive `tx` used by `createWorkspace`, the audit insert runs before the resource transaction commits. A database error that aborts that transaction is caught here, but the aborted transaction still rolls back the workspace writes, so an audit outage can block the write; invoke the writer with the root Prisma client only after the resource transaction resolves.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Caller as Trusted Caller (Public API / Agent)
participant Route as Internal Write Route
participant Service as createWorkspace Service
participant Audit as writeAudit Helper
participant DB as Database (Prisma)
Note over Caller,DB: Workspace Create Flow (Trusted Internal Write Path)
Caller->>Route: POST /api/internal/write/workspaces
alt Invalid Internal Secret
Route->>Route: verifyInternalSecret() fails
Route-->>Caller: 401 Unauthorized
else Valid Secret
Route->>Route: Parse & validate body (zod schema)
alt Invalid JSON / Validation Failure
Route-->>Caller: 400 with error message
else Valid Input
Route->>Service: createWorkspace({actorUserId, name, provenance})
Service->>Service: Trim & validate name (max 100 chars)
alt Invalid name
Service-->>Route: {ok: false, status: 400}
Route-->>Caller: 400 error
else Valid name
Service->>DB: $transaction starts
Service->>DB: findFirst (existing workspace by owner+name)
alt Workspace exists (idempotent hit)
DB-->>Service: Existing workspace
Service-->>Route: {ok: true, created: false, data: existing}
Route-->>Caller: 200 {created: false, workspace}
else No existing workspace
Service->>DB: create workspace
DB-->>Service: Workspace row
Service->>DB: create workspaceMember (role: ADMIN)
DB-->>Service: Member row
Service->>Audit: writeAudit(tx, entry)
alt Audit succeeds
Audit->>DB: Insert audit_logs row
DB-->>Audit: Success
else Audit fails (best-effort)
Audit->>Audit: Swallow error, log to console
end
Service-->>Route: {ok: true, created: true, data: workspace}
Route-->>Caller: 200 {created: true, workspace}
end
Service->>DB: $transaction commits
end
end
end
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
|
||
| // Best-effort by design: losing an audit row is better than failing the | ||
| // user's write after the resource already exists. | ||
| export async function writeAudit(tx: PrismaTxLike, entry: AuditEntry): Promise<void> { |
There was a problem hiding this comment.
P1: When writeAudit receives the interactive tx used by createWorkspace, the audit insert runs before the resource transaction commits. A database error that aborts that transaction is caught here, but the aborted transaction still rolls back the workspace writes, so an audit outage can block the write; invoke the writer with the root Prisma client only after the resource transaction resolves.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/ui/src/lib/write-services/audit.ts, line 20:
<comment>When `writeAudit` receives the interactive `tx` used by `createWorkspace`, the audit insert runs before the resource transaction commits. A database error that aborts that transaction is caught here, but the aborted transaction still rolls back the workspace writes, so an audit outage can block the write; invoke the writer with the root Prisma client only after the resource transaction resolves.</comment>
<file context>
@@ -0,0 +1,38 @@
+
+// Best-effort by design: losing an audit row is better than failing the
+// user's write after the resource already exists.
+export async function writeAudit(tx: PrismaTxLike, entry: AuditEntry): Promise<void> {
+ try {
+ await tx.auditLog.create({
</file context>
A failed audit INSERT inside an interactive transaction aborts the whole transaction in Postgres. Catching the error in writeAudit did not make it best-effort: the enclosing COMMIT still rolled back, discarding the workspace the caller had already been told was created. Move the audit write after the transaction resolves and issue it through the root client, so an audit outage can no longer undo the resource write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
Part 1 of the public write paths, split for review. Stack: this → project → detector → dashboard/widget → session guard → public routes → registry.
Adds the
AuditLogmodel and its best-effort writer, plus the first write service (workspace create) and its internal route — the exemplar every later service follows: validate, check role, write in a transaction, audit after the transaction commits so a failed audit can never roll back the resource it describes.Creates are idempotent on a natural key (owner + name) and return the existing row rather than erroring. There is no unique constraint behind this, so a concurrency window exists.
Part of #2010.
🤖 Generated with Claude Code
https://claude.ai/code/session_012CLSaVXhLYJxH6DS9mDjiB
Summary by cubic
Adds the
AuditLogmodel and a best-effort writer, plus the first write service (createWorkspace) and its internal route — the pattern later write services follow. Part of #2010.Audit logging
Workspace create
Written for commit 0653f7e. Summary will update on new commits.