fix(housekeeping): restore people partial compatibility
CI / check (pull_request) Successful in 42s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-29 21:03:43 +02:00
1 parent fcd1dfd96e
commit a6a288d9ff
6 files changed
+412 -11

No files matched your search

@@ -59,6 +59,14 @@ function parseField(field: PeopleCommandField, formData: FormData): unknown {
}
const initialState: HousekeepingResult<unknown> | null = null;
const externalSyncFailureReason =
"Database applied; emulator sync failed. Do not retry automatically.";
interface ExternalSyncFailure {
readonly key: string;
readonly userId: number;
readonly reason: typeof externalSyncFailureReason;
}
export async function submitPeopleCommandForm(
configuration: PeopleCommandSubmission,
@@ -96,6 +104,7 @@ export function PeopleCommandResult({
readonly result: HousekeepingResult<unknown>;
}) {
const generatedPassword = readGeneratedPassword(result);
const externalSyncFailures = readExternalSyncFailures(result);
const message = result.ok
? result.completion?.status === "partial"
? `Partially completed (${result.correlationId})`
@@ -103,10 +112,28 @@ export function PeopleCommandResult({
: `Failed: ${result.error.messageKey} (${result.correlationId})`;
return (
<div
role="status"
role={externalSyncFailures.length > 0 ? "alert" : "status"}
className="space-y-1 text-xs text-[var(--admin-text-muted)]"
>
<p>{message}</p>
{externalSyncFailures.length > 0 ? (
<>
<p>
<strong>Do not retry automatically.</strong> The database updates
were applied; retrying may duplicate them.
</p>
<ul
aria-label="External synchronization debt"
className="list-disc space-y-1 pl-5"
>
{externalSyncFailures.map((failure) => (
<li key={failure.key}>
User #{failure.userId}: {failure.reason}
</li>
))}
</ul>
</>
) : null}
{generatedPassword ? (
<p>
Generated temporary password:{" "}
@@ -119,6 +146,42 @@ export function PeopleCommandResult({
);
}
function readExternalSyncFailures(
result: HousekeepingResult<unknown>,
): readonly ExternalSyncFailure[] {
if (
!result.ok ||
result.completion?.status !== "partial" ||
result.completion.external !== "failed" ||
!isRecord(result.data) ||
!isRecord(result.data.after) ||
!Array.isArray(result.data.after.externalSyncFailures)
) {
return [];
}
const occurrences = new Map<number, number>();
const failures: ExternalSyncFailure[] = [];
for (const value of result.data.after.externalSyncFailures.slice(0, 100)) {
if (
!isRecord(value) ||
typeof value.userId !== "number" ||
!Number.isSafeInteger(value.userId) ||
value.userId <= 0 ||
value.reason !== externalSyncFailureReason
) {
continue;
}
const occurrence = (occurrences.get(value.userId) ?? 0) + 1;
occurrences.set(value.userId, occurrence);
failures.push({
key: `${value.userId}:${occurrence}`,
userId: value.userId,
reason: externalSyncFailureReason,
});
}
return failures;
}
function readGeneratedPassword(
result: HousekeepingResult<unknown>,
): string | null {
@@ -401,6 +401,115 @@ describe("People actionable form contract", () => {
expect(html.match(new RegExp(password, "gu"))).toHaveLength(1);
});
it("renders typed external sync debt with an explicit do-not-retry warning", () => {
const completion = {
status: "partial",
external: "failed",
audit: "persisted",
} as const;
const html = renderToStaticMarkup(
<PeopleCommandResult
result={ok(
{
before: { completed: 0, total: 2, failedIds: [] },
after: {
completed: 2,
total: 2,
failedIds: [],
externalSyncFailures: [
{
userId: 7,
reason:
"Database applied; emulator sync failed. Do not retry automatically.",
},
{
userId: "8",
reason: "unsafe payload from an untyped result",
},
],
debug: "sensitive internal payload",
},
completion,
},
"bulk-sync-debt",
completion,
)}
/>,
);
expect(html).toContain('role="alert"');
expect(html).toContain("Do not retry automatically.");
expect(html).toContain('aria-label="External synchronization debt"');
expect(html).toContain("User #7");
expect(html).toContain("Database applied; emulator sync failed.");
expect(html).not.toContain("unsafe payload from an untyped result");
expect(html).not.toContain("sensitive internal payload");
});
it("keeps form-returned external sync debt visible in the operator result surface", async () => {
const completion = {
status: "partial",
external: "failed",
audit: "persisted",
} as const;
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
ok(
{
before: { completed: 0, total: 1, failedIds: [] },
after: {
completed: 1,
total: 1,
failedIds: [],
externalSyncFailures: [
{
userId: 23,
reason:
"Database applied; emulator sync failed. Do not retry automatically.",
},
],
},
completion,
},
"form-sync-debt",
completion,
),
);
const formData = new FormData();
formData.set("userIds", "23");
formData.set("amount", "100");
formData.set("type", "credits");
formData.set("reason", "Planned grant");
const result = await submitPeopleCommandForm(
{
commandId: "people.users.bulk-currency",
input: {},
fields: [
{ name: "userIds", label: "User IDs", type: "number-list" },
{ name: "amount", label: "Amount", type: "number", min: 1 },
{
name: "type",
label: "Currency",
type: "select",
options: [{ value: "credits", label: "Credits" }],
},
],
requiresReason: true,
},
null,
formData,
);
const html = renderToStaticMarkup(<PeopleCommandResult result={result} />);
expect(executeHousekeepingCommand).toHaveBeenCalledWith({
commandId: "people.users.bulk-currency",
input: { userIds: [23], amount: 100, type: "credits" },
reason: "Planned grant",
});
expect(html).toContain("Do not retry automatically.");
expect(html).toContain("User #23");
});
it("provides a real Next loading boundary", () => {
const source = readFileSync(
"src/app/ase-next/[domain]/[[...segments]]/loading.tsx",