fix(housekeeping): complete people workflow fidelity

This commit is contained in:
Simo committed 2026-08-29 14:39:38 +02:00
1 parent 25b76437ff
commit 91c9efcbb4
29 files changed
+1365 -290

No files matched your search

@@ -780,7 +780,7 @@ describe("dispatchHousekeepingCommand", () => {
expect(audit.entries.map((entry) => entry.outcome)).toEqual(["failure"]);
});
it("throws completed-operation evidence and persists partial when success audit rejects", async () => {
it("returns completed-operation evidence and persists partial when success audit rejects", async () => {
const attempts: string[] = [];
const persisted: string[] = [];
register(
@@ -806,10 +806,14 @@ describe("dispatchHousekeepingCommand", () => {
}),
);
await expect(dispatch).rejects.toMatchObject({
name: "AuditOutcomePersistenceError",
operationCompleted: true,
operationResult: { ok: true, data: { changed: true } },
await expect(dispatch).resolves.toMatchObject({
ok: true,
data: { changed: true },
completion: {
status: "partial",
external: "not-required",
audit: "persisted",
},
});
expect(attempts).toEqual(["success", "partial"]);
expect(persisted).toEqual(["partial"]);
@@ -5,15 +5,12 @@ import { satisfiesCapability } from "../capability-context";
import {
fail,
type HousekeepingCapabilityContext,
type HousekeepingPartialCompletion,
type HousekeepingResult,
mapUnknownError,
} from "../contracts";
import { createCorrelationId } from "../correlation";
import {
AuditOutcomePersistenceError,
writeIntent,
writeOutcome,
} from "./audit-envelope";
import { writeIntent, writeOutcome } from "./audit-envelope";
import { confirmHousekeepingCommand } from "./confirmation";
import {
getHousekeepingCommand,
@@ -62,11 +59,25 @@ const housekeepingResultSchema = z.discriminatedUnion("ok", [
ok: z.literal(true),
data: z.unknown(),
correlationId: z.string().min(1).max(160),
completion: z
.strictObject({
status: z.literal("partial"),
external: z.enum(["not-required", "completed", "failed"]),
audit: z.enum(["persisted", "unavailable"]),
})
.optional(),
}),
z.strictObject({
ok: z.literal(false),
error: housekeepingErrorSchema,
correlationId: z.string().min(1).max(160),
completion: z
.strictObject({
status: z.literal("partial"),
external: z.enum(["not-required", "completed", "failed"]),
audit: z.enum(["persisted", "unavailable"]),
})
.optional(),
}),
]);
@@ -295,6 +306,13 @@ export async function dispatchHousekeepingCommand(
correlatedResult,
);
}
if (correlatedResult.completion?.status === "partial") {
return persistPartialCompletionOutcome(
dependencies.audit,
auditEntry,
correlatedResult,
);
}
return persistSuccessfulOutcome(
dependencies.audit,
auditEntry,
@@ -455,17 +473,49 @@ async function persistReturnedOutcome<T>(
async function persistSuccessfulOutcome<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
result: HousekeepingResult<T>,
result: Extract<HousekeepingResult<T>, { ok: true }>,
): Promise<HousekeepingResult<T>> {
try {
await writeOutcome(writer, entry, "success");
return result;
} catch (auditOutcomeError) {
} catch {
let audit: HousekeepingPartialCompletion["audit"] = "persisted";
try {
await writeOutcome(writer, entry, "partial");
} catch {
// The completed-operation error below remains the primary evidence.
audit = "unavailable";
}
throw new AuditOutcomePersistenceError(result, auditOutcomeError);
return withPartialCompletion(result, {
status: "partial",
external: result.completion?.external ?? "not-required",
audit,
});
}
}
async function persistPartialCompletionOutcome<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
result: Extract<HousekeepingResult<T>, { ok: true }>,
): Promise<HousekeepingResult<T>> {
try {
await writeOutcome(writer, entry, "partial");
return result;
} catch {
return withPartialCompletion(result, {
...(result.completion ?? {
status: "partial",
external: "not-required",
audit: "unavailable",
}),
audit: "unavailable",
});
}
}
function withPartialCompletion<T>(
result: Extract<HousekeepingResult<T>, { ok: true }>,
completion: HousekeepingPartialCompletion,
): HousekeepingResult<T> {
return { ...result, completion };
}
@@ -28,6 +28,7 @@ export {
fail,
type HousekeepingError,
type HousekeepingErrorCode,
type HousekeepingPartialCompletion,
type HousekeepingResult,
mapUnknownError,
ok,
@@ -17,12 +17,32 @@ export interface HousekeepingError {
fieldErrors?: Readonly<Record<string, readonly string[]>>;
}
export interface HousekeepingPartialCompletion {
readonly status: "partial";
readonly external: "not-required" | "completed" | "failed";
readonly audit: "persisted" | "unavailable";
}
export type HousekeepingResult<T> =
| { ok: true; data: T; correlationId: string }
| {
ok: true;
data: T;
correlationId: string;
completion?: HousekeepingPartialCompletion;
}
| { ok: false; error: HousekeepingError; correlationId: string };
export function ok<T>(data: T, correlationId: string): HousekeepingResult<T> {
return { ok: true, data, correlationId };
export function ok<T>(
data: T,
correlationId: string,
completion?: HousekeepingPartialCompletion,
): HousekeepingResult<T> {
return {
ok: true,
data,
correlationId,
...(completion === undefined ? {} : { completion }),
};
}
export function fail(
@@ -39,7 +39,6 @@ const approvedRuntimeImports = new Map<string, ReadonlySet<string>>([
[
"src/features/housekeeping/domains/people/pages/multi-accounts.tsx",
new Set([
"src/features/housekeeping/domains/people/pages/people-command-form",
"src/features/housekeeping/domains/people/pages/page-state",
"src/features/housekeeping/domains/people/queries/users",
]),