fix(housekeeping): complete people workflow fidelity
This commit is contained in:
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",
|
||||
]),
|
||||
|
||||
Reference in new issue
Block a user