Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion scripts/blo-30608-gate-revalidation-backfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,16 @@ export function renderReport(
"resolved-but-open by who can clear it:",
` blocker edge cancelled (never self-clears) : ${report.countsByResolutionKind["blocker-cancelled-edge-stuck"]}`,
` every question card withdrawn/expired : ${report.countsByResolutionKind["interaction-abandoned"]}`,
// "a", not "every": `approval-abandoned` is assigned ahead of the refusal
// branch, on *at least one* abandoned card, so a mixed row is counted here
// with refused cards still on it. Deliberately asymmetric with the line
// above — `interaction-abandoned` is its probe's fall-through and so is
// genuinely terminal. See RESOLUTION_KIND_HEADINGS in
// human-gated-gate-revalidation.ts, which carries the full argument.
` a board card withdrawn/cancelled : ${report.countsByResolutionKind["approval-abandoned"]}`,
` all blockers done, row never moved : ${report.countsByResolutionKind["blocker-done-row-not-moved"]}`,
` every linked approval decided : ${report.countsByResolutionKind["approval-decided"]}`,
` board granted the ask, row unperformed : ${report.countsByResolutionKind["approval-granted"]}`,
` board refused the ask : ${report.countsByResolutionKind["approval-refused"]}`,
` at least one question card answered : ${report.countsByResolutionKind["interaction-answered"]}`,
"",
"unverifiable by why no gate was checkable:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,35 @@ describe("BLO-30608 backfill — API acquisition", () => {
expect(rendered).toContain("Probed : 2 (3 beyond the budget)");
});

// The legend label for `approval-abandoned` must not claim "every". The kind
// is assigned on *at least one* abandoned card, ahead of the refusal branch,
// so a mixed row is counted under it with refused cards still on it. The
// service-file heading for this same kind is pinned in
// human-gated-gate-revalidation.test.ts; this is the second site carrying the
// claim, and it drifted out of step with the first once already (PEN-3089).
// Asserted with the padding, because the label sits in a fixed-width column
// that nothing else exercises.
it("does not claim every board card was withdrawn in the resolution legend", async () => {
stub = stubApi({ blocked: humanGatedRows() });

const acquisition = await acquireFromApi(COMPANY_ID, null, NOW);
const report = revalidateGates(acquisition.evidence, {});
const rendered = renderReport(report, {
population: acquisition.population,
calls: acquisition.calls,
elapsedMs: 1_000,
source: "api",
notProbed: 0,
});

expect(rendered).toContain(" a board card withdrawn/cancelled : ");
expect(rendered).not.toContain("every board card");
// Deliberately asymmetric with the line above it: `interaction-abandoned`
// is its probe's fall-through and so is genuinely terminal, which is what
// entitles that one to "every".
expect(rendered).toContain(" every question card withdrawn/expired : ");
});

it("excludes agent-owned, hidden, and digest rows from the population", async () => {
stub = stubApi({ blocked: [...humanGatedRows(), ...excludedRows()] });

Expand Down
76 changes: 72 additions & 4 deletions server/src/__tests__/human-gated-gate-revalidation-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ describeEmbeddedPostgres("gate re-validation (wired into the digest producer)",
const markdown = section!.markdown;

// Reported in its own section...
expect(markdown).toContain("Resolved but still open — 1");
expect(markdown).toContain("Gate resolved but row still open — 1");
expect(markdown).toContain("GRW-1 (41.0d silent)");
expect(markdown).toContain("GRW-2=done");
// ...and NOT aged as if still blocked.
Expand Down Expand Up @@ -354,7 +354,75 @@ describeEmbeddedPostgres("gate re-validation (wired into the digest producer)",
await linkApproval(companyId, stale, "approved");

const markdown = (await collect(companyId))!.markdown;
expect(markdown).toContain("Every linked approval has been decided");
expect(markdown).toContain("resolved-but-open 1");
expect(markdown).toContain(
"The board granted the ask and the row has not moved since — authorised, unperformed",
);
});

it("escalates a granted-but-unperformed row instead of exempting it (PEN-3089)", async () => {
// PEN-2526's shape: the board approved, the founder posted the
// instruction-to-begin, and the row then sat `todo` for 19 days. The gate
// really did resolve — into work nobody performed — and the digest read
// that resolution as "this row is not still waiting" and dropped it from
// the one list the founder reads. `threshold (1)` is the whole fix: the
// pre-PEN-3089 producer rendered `(0)` for exactly this input.
const { companyId } = await createCompany("GRG");
const authorised = await insertIssue({
companyId,
identifier: "GRG-1",
status: "in_review",
createdAt: daysAgo(41),
});
await linkApproval(companyId, authorised, "approved");

const markdown = (await collect(companyId))!.markdown;
expect(markdown).toContain("Human-gated work past its human-silence threshold (1)");
expect(markdown).toContain("GRG-1");
// Still rendered in the resolved section too, carrying its age — being
// escalated must not cost the reader the diagnosis of *why* it is stalled.
expect(markdown).toContain("GRG-1 (41.0d silent)");
expect(markdown).toContain("⛔ action owed");
});

it("escalates a row whose only board card the requester withdrew (PEN-3089)", async () => {
// PEN-2224's shape, end to end. `withdrawn` used to render identically to
// `approved` — the probe had no abandoned branch at all — so the root
// blocker of a critical credential-exposure chain sat 26 days inside a
// section headed "these are not still waiting".
const { companyId } = await createCompany("GRD");
const dropped = await insertIssue({
companyId,
identifier: "GRD-1",
status: "in_review",
createdAt: daysAgo(41),
});
await linkApproval(companyId, dropped, "withdrawn");

const markdown = (await collect(companyId))!.markdown;
expect(markdown).toContain(
"At least one board card was withdrawn or cancelled — that ask died unanswered",
);
expect(markdown).toContain("someone must re-ask or drop the row");
expect(markdown).toContain("Human-gated work past its human-silence threshold (1)");
});

it("still exempts a refused ask from the age-ranked list (PEN-3089)", async () => {
// The narrowing has to stay a narrowing. A rejection is a real answer: the
// ask is over and the row needs closing, not escalating. If this one ever
// starts escalating, the change has stopped discriminating and the digest
// is on its way back to being muted.
const { companyId } = await createCompany("GRR");
const refused = await insertIssue({
companyId,
identifier: "GRR-1",
status: "in_review",
createdAt: daysAgo(41),
});
await linkApproval(companyId, refused, "rejected");

const markdown = (await collect(companyId))!.markdown;
expect(markdown).toContain("The board refused the ask");
expect(markdown).toContain("Human-gated work past its human-silence threshold (0)");
});

Expand Down Expand Up @@ -389,7 +457,7 @@ describeEmbeddedPostgres("gate re-validation (wired into the digest producer)",

const section = await collect(companyId);
expect(section).not.toBeNull();
expect(section!.markdown).toContain("Resolved but still open — 1");
expect(section!.markdown).toContain("Gate resolved but row still open — 1");
});

it("reports an unreadable-clock row as malformed instead of throwing the producer", async () => {
Expand Down Expand Up @@ -421,7 +489,7 @@ describeEmbeddedPostgres("gate re-validation (wired into the digest producer)",
const section = await collect(companyId);
// The producer survived, and the readable row is still classified.
expect(section).not.toBeNull();
expect(section!.markdown).toContain("Resolved but still open — 1");
expect(section!.markdown).toContain("Gate resolved but row still open — 1");
expect(section!.markdown).toContain("GRM-1");
});

Expand Down
Loading
Loading