feat: implement sensitive operation audit retention (TASK-WP6-04)
Dada P0-A isolated Windows CI / validate-and-package (push) Failing after 59s

This commit is contained in:
suyx
2026-08-04 11:51:09 +08:00
parent c589b8bb4e
commit 1c46311e05
21 changed files with 1670 additions and 11 deletions
@@ -1,15 +1,19 @@
import { createHash, randomUUID } from "node:crypto";
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { Readable } from "node:stream";
import type BetterSqlite3 from "better-sqlite3";
import { afterEach, describe, expect, it } from "vitest";
import { HARD_LIMIT_BYTES, ManagedStorage, StorageCapacityError, StorageUnavailableError } from "../../apps/api/src/managed-storage.js";
import { StickerReleaseService } from "../../apps/api/src/sticker-releases.js";
const now = Date.parse("2026-08-03T12:00:00.000Z");
const require = createRequire(resolve("apps/api/package.json"));
const Database = require("better-sqlite3") as typeof BetterSqlite3;
const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAEklEQVQImWO4E6XxHxkzEBQAANIxHF3ECQOzAAAAAElFTkSuQmCC", "base64");
const webp = Buffer.from("UklGRjoAAABXRUJQVlA4IC4AAADQAQCdASoGAAUAAUAmJaACdLoB+AADsAD+9IiH/pNnibPE2fJI/+Uq8Fjc3wAA", "base64");
const roots: string[] = [];
@@ -38,7 +42,7 @@ function fixture() {
const storage = new ManagedStorage({ dataRoot, databasePath });
const stickers = new StickerReleaseService({ clock: () => now, databasePath, storage });
closeables.push(stickers, storage);
return { dataRoot, stickers, storage };
return { dataRoot, databasePath, stickers, storage };
}
async function upload(stickers: StickerReleaseService, stableId: string, order: number, bytes = png, mimeType: "image/png" | "image/webp" = "image/png") {
@@ -82,6 +86,26 @@ describe("TDD-WP5-UPL-001 upload metering", () => {
expect(test.stickers.listPublic(published.release_version).items).toHaveLength(1);
expect(test.stickers.readPublicAsset(published.release_version, "STK1408", "original")?.bytes).toEqual(png);
expect(test.stickers.readPublicAsset(disabled.release_version, "STK1408", "original")).toBeUndefined();
const audit = new Database(test.databasePath, { readonly: true });
expect(audit.prepare(`
SELECT operation_type, target_ref, result FROM admin_operation_logs
WHERE operation_type IN ('sticker_release_publish', 'sticker_release_update')
ORDER BY occurred_at
`).all()).toEqual([
{ operation_type: "sticker_release_publish", result: "succeeded", target_ref: published.release_version },
{ operation_type: "sticker_release_update", result: "succeeded", target_ref: disabled.release_version },
]);
audit.close();
const auditFailure = new Database(test.databasePath);
auditFailure.exec(`
CREATE TRIGGER force_sticker_release_audit_failure BEFORE INSERT ON admin_operation_logs
WHEN NEW.operation_type = 'sticker_release_update'
BEGIN SELECT RAISE(ABORT, 'forced_audit_failure'); END;
`);
expect(() => test.stickers.update({ actorId: randomUUID(), enabled: true, stableId: "STK1408" }))
.toThrow("forced_audit_failure");
expect(test.stickers.adminView()).toMatchObject({ release_version: disabled.release_version, items: [{ enabled: false }] });
auditFailure.close();
evidence("fs-before.json", before);
evidence("fs-after.json", { files: filesBelow(join(test.dataRoot, "managed-assets")), state: test.storage.getState() });
@@ -136,4 +136,45 @@ describe("TDD-WP5-CLN-001 sticker history cleanup", () => {
{ operation_type: "asset_cleanup_physical_completed", result: "succeeded" },
]);
});
it("records a redacted physical failure in the same transaction and leaves the request retryable", async () => {
const test = fixture();
const adminId = seedAdmin(test.database);
const files = await seedHistoricalPair(test);
const candidates = test.storage.listAssetCleanupCandidates();
const intent = test.storage.createAssetCleanupIntent({
actorId: adminId,
fileIds: [files.original.file_id, files.thumbnail.file_id],
idempotencyKey: `cleanup-${randomUUID()}-${randomUUID()}`,
snapshotVersion: candidates.candidate_snapshot_version,
});
test.storage.confirmAssetCleanupIntent({
actorId: adminId,
confirmationToken: intent.confirmation_token,
requestId: intent.request_id,
});
test.database.prepare(`
UPDATE file_cleanup_queue SET relative_path = '../outside-fixture'
WHERE managed_file_id = ?
`).run(files.original.file_id);
const worker = new ProjectPurgeCleanup({ dataRoot: test.dataRoot, databasePath: test.databasePath });
const result = worker.processFileCleanup();
worker.close();
expect(result).toEqual({ completed: 1, failed: 1 });
expect(test.database.prepare("SELECT status FROM asset_cleanup_requests WHERE request_id = ?").get(intent.request_id)).toEqual({ status: "queued" });
expect(test.database.prepare("SELECT status, last_error FROM file_cleanup_queue WHERE managed_file_id = ?").get(files.original.file_id))
.toEqual({ last_error: "physical_file_cleanup_failed", status: "failed" });
const failure = test.database.prepare(`
SELECT operation_type, result, before_summary, after_summary
FROM admin_operation_logs WHERE target_ref = ? AND operation_type = 'asset_cleanup_physical_failed'
`).get(intent.request_id) as { after_summary: string; before_summary: null; operation_type: string; result: string };
expect(failure).toEqual({
after_summary: JSON.stringify({ failed_count: 1, status: "retry_pending" }),
before_summary: null,
operation_type: "asset_cleanup_physical_failed",
result: "failed",
});
expect(JSON.stringify(failure)).not.toMatch(/outside-fixture|relative_path|absolute_path|image|prompt|secret/i);
});
});
@@ -0,0 +1,90 @@
import { randomUUID } from "node:crypto";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
import { wp604OperationMatrix } from "../fixtures/wp6-04-audit.js";
const roots: string[] = [];
const services: RegistrationService[] = [];
const now = Date.parse("2026-08-04T09:30:00.000Z");
function fixture() {
const root = mkdtempSync(join(tmpdir(), "dada-wp6-04-integration-"));
roots.push(root);
const registration = new RegistrationService({
challengePepper: Buffer.alloc(32, 0xb1),
clock: () => now,
currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
databasePath: join(root, "dada.sqlite3"),
invitePepper: Buffer.alloc(32, 0xb2),
inviteCodeGenerator: () => "fixture-invite-code",
resend: new MockResendAdapter(),
sessionPepper: Buffer.alloc(32, 0xb3),
});
services.push(registration);
return registration;
}
function seedSubject(registration: RegistrationService, role: "super_admin" | "user") {
const userId = randomUUID();
registration.database.prepare(`
INSERT INTO users (
user_id, normalized_email, role, status, counts_toward_stage_limit,
registration_id, created_at
) VALUES (?, ?, ?, 'active', ?, ?, ?)
`).run(userId, `${role}-${userId}@example.invalid`, role, role === "user" ? 1 : 0, randomUUID(), now);
if (role === "super_admin") {
registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(userId);
}
return userId;
}
afterEach(() => {
for (const service of services.splice(0)) service.close();
for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});
describe("TDD-WP6-AUD-001-sensitive-operations", () => {
it("writes invite and user status audits in the same transaction as the mutation", () => {
const registration = fixture();
const adminId = seedSubject(registration, "super_admin");
const userId = seedSubject(registration, "user");
const adminOperations = registration as RegistrationService & {
createAdminInvite(input: { actorId: string; expiresAt: number; maxUses: number }): { code: string; inviteId: string };
changeUserStatus(userId: string, status: "suspended" | "deleted", actorId: string): void;
};
const invite = adminOperations.createAdminInvite({ actorId: adminId, expiresAt: now + 86_400_000, maxUses: 1 });
adminOperations.changeUserStatus(userId, "suspended", adminId);
expect(registration.database.prepare(`
SELECT operation_type, target_ref FROM admin_operation_logs
WHERE operation_type IN ('invite_create', 'user_status_change') ORDER BY occurred_at
`).all()).toEqual([
{ operation_type: "invite_create", target_ref: invite.inviteId },
{ operation_type: "user_status_change", target_ref: userId },
]);
registration.database.exec(`
CREATE TRIGGER force_user_status_audit_failure BEFORE INSERT ON admin_operation_logs
WHEN NEW.operation_type = 'user_status_change'
BEGIN SELECT RAISE(ABORT, 'forced_audit_failure'); END;
`);
expect(() => adminOperations.changeUserStatus(userId, "deleted", adminId)).toThrow("forced_audit_failure");
expect(registration.database.prepare("SELECT status FROM users WHERE user_id = ?").get(userId)).toEqual({ status: "suspended" });
const evidenceRoot = process.env.DADA_EVIDENCE_DIR_WP6_AUD;
if (evidenceRoot) {
mkdirSync(evidenceRoot, { recursive: true });
writeFileSync(resolve(evidenceRoot, "operation-matrix.json"), `${JSON.stringify({ operations: wp604OperationMatrix }, null, 2)}\n`);
writeFileSync(resolve(evidenceRoot, "db-diff.json"), `${JSON.stringify({
audit_failure_rollback: { user_status: "suspended" },
tables: { admin_operation_logs: "append_only", private_content_access_logs: "append_only_separate" },
}, null, 2)}\n`);
}
});
});