Skip to content

feat(core): add the write audit log and the workspace create service - #2087

Open
ka1kqi wants to merge 6 commits into
mainfrom
feat/write-audit-workspace
Open

feat(core): add the write audit log and the workspace create service#2087
ka1kqi wants to merge 6 commits into
mainfrom
feat/write-audit-workspace

Conversation

@ka1kqi

@ka1kqi ka1kqi commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of the public write paths, split for review. Stack: this → project → detector → dashboard/widget → session guard → public routes → registry.

Adds the AuditLog model 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 AuditLog model 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

  • Audit rows carry no foreign keys, so they survive resource deletion.
  • The audit row is written after the resource transaction commits, through the root client, so a failed audit can't roll back the resource the caller was told it created.

Workspace create

  • Creates are idempotent on owner + name and return the existing workspace instead of erroring.
  • No unique constraint backs that natural key, so concurrent creates can still duplicate.

Written for commit 0653f7e. Summary will update on new commits.

Review in cubic

@trident-sentinel

trident-sentinel Bot commented Sep 1, 2026

Copy link
Copy Markdown

PR overview

This 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 review

No open security issues remain on this pull request.

Scanned with Semgrep · TruffleHog · Trident review. View in Trident

Fixed/addressed: 0 · PR risk: 0/10

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files

Confidence score: 3/5

  • In frontend/ui/src/lib/write-services/audit.ts, writeAudit inserts into the interactive transaction before createWorkspace commits; 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
Loading

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> {

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread frontend/ui/src/lib/write-services/workspaces.ts Outdated
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant