feat(workspaces): connectWorkspace use case with audit
Manifest-first: connectWorkspace declared mutates:true with the workspace-connected audit event, requiredCores gains audit. Workspace entity gains gitUrl + persisted status enum (created/connecting/ready/ error). Input takes name + git URL + PAT; the output schema is the credential-free workspace entity, so the PAT can never round-trip. Audit emission asserted with RecordingAuditLog; binders wire the use case through wireUseCase with the __audited brand, and web-next bindAll now binds core-audit (payload+stdout sinks in production, stdout in dev-seed) so boot conformance passes in both modes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { z } from "zod";
|
||||
import type { AuditLogProtocol } from "@repo/core-shared/di/bind-protocols";
|
||||
|
||||
import { gitUrlSchema, workspaceSchema } from "../../entities/models/workspace";
|
||||
import type { IWorkspaceRepository } from "../repositories/workspace.repository.interface";
|
||||
|
||||
// ── Input ────────────────────────────────────────────────────────────────
|
||||
export const connectWorkspaceInputSchema = z
|
||||
.object({
|
||||
name: z.string().min(1).max(128),
|
||||
gitUrl: gitUrlSchema,
|
||||
/** Repository personal access token. Write-only: encrypted at rest and never returned by any output. */
|
||||
pat: z.string().min(1).max(4096),
|
||||
})
|
||||
.strict();
|
||||
export type ConnectWorkspaceInput = z.infer<typeof connectWorkspaceInputSchema>;
|
||||
|
||||
// ── Output ───────────────────────────────────────────────────────────────
|
||||
// The credential is deliberately absent: `workspaceSchema` has no credential
|
||||
// field, and `.parse` strips any unknown keys a repository might leak.
|
||||
export const connectWorkspaceOutputSchema = workspaceSchema;
|
||||
export type ConnectWorkspaceOutput = z.infer<
|
||||
typeof connectWorkspaceOutputSchema
|
||||
>;
|
||||
|
||||
// ── Use case ─────────────────────────────────────────────────────────────
|
||||
export type IConnectWorkspaceUseCase = ReturnType<
|
||||
typeof connectWorkspaceUseCase
|
||||
>;
|
||||
|
||||
export const connectWorkspaceUseCase =
|
||||
(workspaceRepository: IWorkspaceRepository, auditLog?: AuditLogProtocol) =>
|
||||
async (input: ConnectWorkspaceInput): Promise<ConnectWorkspaceOutput> => {
|
||||
const workspace = await workspaceRepository.createWorkspace({
|
||||
name: input.name,
|
||||
gitUrl: input.gitUrl,
|
||||
credential: input.pat,
|
||||
});
|
||||
|
||||
// Audit event "workspace-connected" — declared in feature.manifest.ts
|
||||
// (useCases.connectWorkspace.audits). No session context exists at this
|
||||
// layer yet (auth wiring is a later story), so the system sentinels apply.
|
||||
await auditLog?.record({
|
||||
actorId: "system",
|
||||
actorType: "system",
|
||||
actorRoles: [],
|
||||
action: "CREATE",
|
||||
resource: { type: "workspaces", id: workspace.id },
|
||||
at: new Date(),
|
||||
scope: {
|
||||
feature: "workspaces",
|
||||
environment: process.env.NODE_ENV ?? "development",
|
||||
tenant: "default",
|
||||
},
|
||||
from: { ipTruncated: "system", userAgent: "control-plane" },
|
||||
containsPii: false,
|
||||
outcome: "success",
|
||||
reason: "workspace-connected",
|
||||
});
|
||||
|
||||
return connectWorkspaceOutputSchema.parse(workspace);
|
||||
};
|
||||
Reference in New Issue
Block a user