Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a6fe876
Add the apply-through action contracts
ndisidore Sep 17, 2026
334d63a
Replace per-action approval with serialized batch action sync
ndisidore Sep 17, 2026
dc512f2
Show why an action was not applied
ndisidore Sep 17, 2026
a757f84
Simplify the apply-through plumbing
ndisidore Sep 17, 2026
adeef97
Report out-of-order gatekeeper action submissions
ndisidore Sep 18, 2026
56cf8e0
Derive an action's status label in one place
ndisidore Sep 18, 2026
7eec060
Require gatekeepers to publish retained actions in order
ndisidore Sep 18, 2026
30429ad
Refuse queued approvals that predate a new failure
ndisidore Sep 18, 2026
de3c207
Report a gatekeeper stop ahead of an undecided gate
ndisidore Sep 19, 2026
b5372cf
Keep a stale action refresh from dropping a newer failure
ndisidore Sep 20, 2026
5724476
Stage each selected veto once
ndisidore Sep 20, 2026
d02c813
Report a pack build's lifetime failure with its code
ndisidore Sep 20, 2026
31ccf11
Trim guards the apply-through paths cannot reach
ndisidore Sep 20, 2026
0f6ed20
Keep an empty pushedCommits list off the wire
ndisidore Sep 20, 2026
2ff9a51
Withhold "Always approve" on an action that stopped
ndisidore Sep 20, 2026
3afb24b
Derive the action driver's seams from the real types
ndisidore Sep 22, 2026
fa50dd7
Derive the always-approve target in one place
ndisidore Sep 22, 2026
725b82d
Report a veto of an already-applied action instead of ignoring it
ndisidore Sep 22, 2026
2d0d0cc
Say on the card that a refused veto was already applied
ndisidore Sep 22, 2026
36b7438
Keep turns ended, and cards current, after a veto decision
ndisidore Sep 22, 2026
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: 7 additions & 3 deletions packages/gatekeeper-kit/__tests__/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,9 +828,13 @@ describe("defineActions", () => {
}
});

it("puts no pushedCommits key on the wire when the description declares none", async () => {
// Absent, not `undefined`: the overseer reads presence as "this action pushes".
const { actions } = bind();
it.each([
{ declared: "no key", present: undefined },
{ declared: "an empty list", present: () => ({ ...presentation, pushedCommits: [] }) },
])("puts no pushedCommits key on the wire when the description declares $declared",
async ({ present }) => {
// Absent, not `undefined` or `[]`: the overseer reads presence as "this action pushes".
const { actions } = bind({ describe: present });
const submitAction = submitSpy();

await actions.submit(fakeQueue(submitAction), "execute", { sql: "one" });
Expand Down
5 changes: 3 additions & 2 deletions packages/gatekeeper-kit/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,9 @@ export function defineActions<Host, M extends Record<string, unknown>>(
description,
implementsRevert,
// Spread, so an action with no git, no kind, or no awaited decision puts no key on the
// wire at all.
...(pushedCommits ? { pushedCommits } : {}),
// wire at all. An empty list is "no git" too: the overseer reads presence as a push,
// and `[]` is a push of nothing it refuses to build a pack for.
...(pushedCommits?.length ? { pushedCommits } : {}),
autoApprovable: definition.autoApprovable === true,
...(definition.kind ? { actionKind: definition.kind } : {}),
...(definition.delivery === "await-decision" ? { awaitDecision: true } : {}),
Expand Down
20 changes: 3 additions & 17 deletions packages/workshop-backend/__tests__/action-log-pagination.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { describe, expect, it, vi } from "vitest";
import type { RpcStub } from "capnweb";
import type { ActionLogEntry, ActionsSubscriber } from "@gadgets/workshop-shared/api";
import type { ActionLogEntry } from "@gadgets/workshop-shared/api";
import {
ACTION_HISTORY_PAGE_DEFAULT_LIMIT, ACTION_REPLAY_PAGE_SIZE,
} from "../src/overseer.js";
import { makeMockStorage } from "./mock-storage.js";
import {
FIXTURE_EPOCH, makeActionStorage, makePreIndexActionStorage, openFakeOverseer, putAction,
FIXTURE_EPOCH, makeActionStorage, makePreIndexActionStorage, makeSubscriber, openFakeOverseer,
putAction,
} from "./fixtures.js";

vi.mock("capnweb-validate", () => ({ validateRpc: () => () => undefined }));

// Hand-rolled ActionsSubscriber stub. `events` interleaves entry ids with "ready", so tests can
// assert both content and ordering of the delivered stream.
function makeSubscriber(entry?: (record: ActionLogEntry) => Promise<void>) {
let events: Array<number | "ready"> = [];
let subscriber = {
entry: entry ?? (async (record: ActionLogEntry) => { events.push(record.id); }),
ready: async () => { events.push("ready"); },
dup: () => subscriber,
onRpcBroken: () => {},
[Symbol.dispose]: () => {},
};
return { subscriber: subscriber as unknown as RpcStub<ActionsSubscriber>, events };
}

describe("subscribeToActions", () => {
it("delivers no pre-existing records: ready fires immediately", async () => {
// Live deltas only — the current pending set is queried via listActions({filter: "pending"}).
Expand Down
Loading
Loading