Skip to content

Commit c33ccb2

Browse files
Merge pull request #647 from corbitsdev/cl-11525-delete-the-old-workflow-packages
refactor: delete the old pr-triage, pr-workflow and standalone code review workflows
2 parents 0fcd8a1 + 4f02f33 commit c33ccb2

96 files changed

Lines changed: 291 additions & 2261 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
File renamed without changes.

‎apps/extension/src/run-checks.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { fixtureGithub } from "@corbits/triage-workflows/evals/serve";
77
import { runChecks } from "./run-checks.ts";
88
import { outcomeOf, type ViewCheck } from "./view.ts";
99

10-
const PR = JSON.parse(readFileSync(join(import.meta.dir, "../../../packages/pr-workflow-anthropic/fixtures/godotenv-262.json"), "utf8")) as Snapshot;
10+
const PR = JSON.parse(readFileSync(join(import.meta.dir, "../fixtures/godotenv-262.json"), "utf8")) as Snapshot;
1111

1212
function readOnly(writes: string[]) {
1313
const served = fixtureGithub(PR, {});

‎apps/extension/src/run-checks.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as actions from "@corbits/triage-workflows/actions/index";
44
import type { RulesOutput } from "@corbits/triage-workflows/actions/rules";
55
import { credentialBindings } from "@corbits/triage-workflows/agents";
66
import { localActionCredentials } from "@corbits/triage-workflows/local-credentials";
7-
import { workflow } from "@corbits/triage-workflows/pr-triage-github";
7+
import { workflow } from "@corbits/triage-workflows/pr-triage";
88
import { runLocal, type ActionHandler, type OnTriggerPrimitive, type StepInvoker, type WorkflowAuthorizeFn, type WorkflowRun } from "@intx/workflow";
99
import { fileNotes, type FileNote } from "./notes.ts";
1010
import type { PrRef } from "./pr.ts";

‎apps/extension/src/session-reads.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { fixtureGithub } from "@corbits/triage-workflows/evals/serve";
88
import { runChecks } from "./run-checks.ts";
99
import { sessionGithub, type Session } from "./session-reads.ts";
1010

11-
const PR = JSON.parse(readFileSync(join(import.meta.dir, "../../../packages/pr-workflow-anthropic/fixtures/godotenv-262.json"), "utf8")) as Snapshot;
11+
const PR = JSON.parse(readFileSync(join(import.meta.dir, "../fixtures/godotenv-262.json"), "utf8")) as Snapshot;
1212
const SHA = "c0ffee".padEnd(40, "0");
1313
// Tag characters spell text no reader sees; only the commit's body, past its headline, carries them.
1414
const HIDDEN = "\u{E0041}\u{E0042}\u{E0043}";

‎apps/hub/src/audit-log.postgres.test.ts‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
import { expect, test } from "bun:test";
33
import { randomBytes } from "node:crypto";
44
import { sql } from "drizzle-orm";
5-
import { createDB, runMigrations, schema } from "@intx/db";
5+
import { createDB, runMigrations, schema, type DB } from "@intx/db";
66
import { createArtifact, runArtifactMigrations } from "@corbits/artifacts";
77
import { APPROVE_DO_KIND, AuditLog, checkPackName, emptyPack, stockTriggerMail } from "@corbits/triage-contracts";
88
import type { WorkflowRunEvent } from "@intx/hub-sessions";
9-
import { createAuditLog } from "./audit-log.js";
9+
import { createAuditLog, ROTATION_REASON } from "./audit-log.js";
1010
import { loadCheckPack } from "./github/check-pack-store.js";
1111
import { migrateTriageTables } from "./triage-db.js";
12-
import { ROTATION_REASON } from "./github/deployment.js";
13-
import { createVerdictRecordStore, type RecordedVerdict } from "./github/verdict-record-store.js";
12+
import { type RecordedVerdict, replaceVerdict } from "./github/verdict-record-store.js";
1413

1514
const TEST_DB = { host: "localhost", port: 5432, user: "postgres", password: "postgres", database: "interchange" };
1615
const TENANT = "tnt_audit";
@@ -35,14 +34,19 @@ const LOGS: Record<string, WorkflowRunEvent[]> = {
3534
wfr_forged: approvalRun({ ...stockTriggerMail(APPROVAL), headers: { from: "mallory@elsewhere.test", to: ["pr-triage@acme.test"] } }),
3635
};
3736

38-
function verdict(runId: string, time: string, state: string, comment: string): RecordedVerdict {
37+
function verdict(runId: string, time: string, state: string, comment: string, headSha = "abcdef123"): RecordedVerdict {
3938
return {
40-
runId, repo: REPO, number: 7, headSha: "abcdef123", startedAt: at(time), verdictAt: at(time), settled: true, state, actor: "maintainer", humanGated: true,
39+
runId, repo: REPO, number: 7, headSha, startedAt: at(time), verdictAt: at(time), settled: true, state, actor: "maintainer", humanGated: true,
4140
degraded: null, checks: [], dos: [], commented: false, runStatus: "completed",
4241
detail: { actions: [{ list: "fail", effectId: "eff_comment", kind: "comment", status: comment }, { list: "pass", effectId: "eff_approve", kind: "approve", held: true, status: "done" }] },
4342
};
4443
}
4544

45+
/** Seeds verdict rows as managers record them. */
46+
async function seed(db: DB["db"], tenantId: string, verdicts: readonly RecordedVerdict[]): Promise<void> {
47+
for (const verdict of verdicts) await replaceVerdict(db, tenantId, verdict);
48+
}
49+
4650
function lines(log: AuditLog): string[] {
4751
return log.entries.map(({ actor, action, target }) => `${actor} | ${action} | ${target}`);
4852
}
@@ -81,7 +85,7 @@ test("PostgreSQL: the audit log merges verdicts, the Dos they ran, approvals, ch
8185
{ id: "wfr_forged", anchorRunId: "wfr_anchor", definitionId: "wfd_triage", tenantId: TENANT, status: "completed", createdAt: at("10:04") },
8286
{ id: "wfr_approval", anchorRunId: "wfr_anchor", definitionId: "wfd_triage", tenantId: TENANT, status: "completed", createdAt: at("10:05") },
8387
]);
84-
await createVerdictRecordStore(db).record(TENANT, [verdict("wfr_verdict", "10:00", "needs-review", "done"), verdict("wfr_retriage", "10:30", "ready", "failed")]);
88+
await seed(db, TENANT, [verdict("wfr_verdict", "10:00", "needs-review", "done"), verdict("wfr_retriage", "10:30", "ready", "failed", "fedcba321")]);
8589
await db.transaction(async function seedPack(tx) {
8690
await createArtifact(tx, {
8791
scope: { tenantId: TENANT, principalId: "prn_owner" }, ownerPrincipalId: "prn_owner", kind: "document",
@@ -104,9 +108,6 @@ test("PostgreSQL: the audit log merges verdicts, the Dos they ran, approvals, ch
104108
return LOGS[runId] ?? [];
105109
},
106110
},
107-
async sameSource() {
108-
return true;
109-
},
110111
async authorize(_principalId, _tenantId, resource) {
111112
return !denied.has(resource);
112113
},

‎apps/hub/src/audit-log.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { schema, type DB } from "@intx/db";
99
import { formatRunAddress } from "@intx/types";
1010
import { WORKFLOW_RUN_REF, workflowRunRepoIdForAddress, type WorkflowRunReader } from "@intx/hub-sessions";
1111
import type { CheckPackRead } from "./github/check-pack-store.js";
12-
import { ROTATION_REASON } from "./github/deployment.js";
1312
import { failure } from "./github/portal-credential.js";
1413
import { repoRecords, sourceOf, triageNs } from "./github/tenant-config.js";
1514
import { approvalIn, type Approval } from "./github/triage-runs.js";
@@ -36,6 +35,9 @@ const DO_OUTCOMES: Record<string, string> = { done: "Ran", satisfied: "Found don
3635

3736
type Entry = Omit<AuditEntry, "time"> & { at: Date };
3837

38+
/** The reason the hub gave when it replaced a busy deployment with a fresh copy, which earlier entries carry. */
39+
export const ROTATION_REASON = "Replaced by a fresh deployment of the same workflow";
40+
3941
/** `people` is whether the caller may read the workspace's principals, which emails and members need. */
4042
type Window = { tenantId: string; repo?: string; since?: Date; limit: number; people: boolean };
4143

@@ -44,9 +46,8 @@ export type AuditLogDeps = {
4446
artifacts: ArtifactDb;
4547
readCheckPack: (tenantId: string, repo: string, source: RepoSource) => Promise<CheckPackRead>;
4648
runReader: Pick<WorkflowRunReader, "readRunEvents">;
47-
sameSource: (anchorRunId: string, otherAnchorRunId: string) => Promise<boolean>;
4849
authorize: (principalId: string, tenantId: string, resource: string, action: string) => Promise<boolean>;
49-
/** The pr-triage workflows whose approval runs are read, one per pull request host. */
50+
/** The workflows whose approval runs are read. */
5051
workflows: readonly string[];
5152
};
5253

@@ -227,8 +228,7 @@ export function createAuditLog(deps: AuditLogDeps) {
227228
const previous = rows.slice(index + 1).find((older) => older.name === row.name);
228229
const rotated = previous?.cancellationReason === ROTATION_REASON
229230
&& previous.cancellationRequestedAt !== null
230-
&& row.createdAt <= previous.cancellationRequestedAt
231-
&& await deps.sameSource(previous.id, row.id);
231+
&& row.createdAt <= previous.cancellationRequestedAt;
232232
entries.push({ at: row.createdAt, actor: rotated ? HUB : row.principalId ?? UNKNOWN, action: "Deployed", target: row.name, detail: row.id });
233233
if (row.endedAt === null) continue;
234234
const byHub = row.status === "failed" || row.cancellationReason === ROTATION_REASON;

‎apps/hub/src/bitbucket-datacenter/integration.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { randomBytes } from "node:crypto";
44
import { type } from "arktype";
55
import { BITBUCKET_DATACENTER_HOOK_HEADERS, xHubHookMetadata } from "@corbits/common/hook-metadata";
66
import type { BitbucketFetch } from "@corbits/bitbucket-datacenter-reads";
7+
import { TRIAGE_LISTENERS } from "@corbits/triage-contracts";
78
import { ensureDeployerRole } from "../deployer-role.js";
89
import { ensureTriageRole, grantEnabledKinds } from "../github/automation.js";
910
import { tenantConfigOf } from "../github/bridge.js";
1011
import { activeCredential, credentialSecret, failure, holds, type PortalCredentialDeps } from "../github/portal-credential.js";
1112
import { patchCorbitsTriage, repoRecords, triageNs, type CorbitsTriageNs, type RepoRecord } from "../github/tenant-config.js";
1213
import type { Member } from "../integrations/app.js";
13-
import { triageWorkflowFor } from "../github/source-workflow.js";
1414
import { asCaller } from "../stock-caller.js";
1515
import {
1616
BitbucketFailed, bitbucketFetch, createWebhook, defaultBranch, deleteWebhook, InstanceRepo, lookupAddresses, pagedValues, readRepo,
@@ -22,7 +22,7 @@ export const BITBUCKET_PATH = "/api/integrations/bitbucket-datacenter";
2222
const BITBUCKET_HOOK_PATH = "/api/hooks/bitbucket-datacenter";
2323
const NO_STORE = { headers: { "Cache-Control": "no-store" } };
2424
const HOST_REFUSED = "The host must resolve only to public addresses; a private-network instance needs the hub operator to allowlist it.";
25-
const HOOK_METADATA = xHubHookMetadata(triageWorkflowFor("bitbucket-datacenter").id, BITBUCKET_DATACENTER_HOOK_HEADERS);
25+
const HOOK_METADATA = xHubHookMetadata(TRIAGE_LISTENERS["bitbucket-datacenter"], BITBUCKET_DATACENTER_HOOK_HEADERS);
2626

2727
const NewConnection = type({ baseUrl: "string > 0", token: "string > 0" });
2828
/** Connects, or replaces the token in place; `rotateHookSecret` instead signs the webhooks triage created with a new secret. */
Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,7 @@
11
import { describe, expect, test } from "bun:test";
2-
import { pinOf, resolveLiveDeployments, sameSource } from "./deployment.js";
2+
import { resolveLiveDeployments } from "./deployment.js";
33

44
const TENANT_ID = "tnt_acme";
5-
const SOURCE = { kind: "asset", assetId: "ast_triage", package: { format: "source", commitSha: "c0ffee", packageName: "@corbits/pr-triage-workflow" } };
6-
const SPEC = {
7-
anchorRunId: "run_old",
8-
deploymentDomain: "acme.test",
9-
sourceAuthorityPrincipalId: "prn_deployer",
10-
sourceOfferingIds: ["off_a", "off_b"],
11-
defaultSourceOfferingId: "off_a",
12-
deployContent: { systemPrompt: "Triage pull requests." },
13-
frozenApprovalBundle: { source: SOURCE, entry: "./pr-triage.mjs" },
14-
};
15-
16-
const TARBALL_SOURCE = { kind: "asset", assetId: "ast_corbits", package: { format: "tarball" } };
17-
18-
function closurePinning(...topLevel: { name: string; version: string }[]) {
19-
const entries = topLevel.map(({ name, version }) => ({
20-
name,
21-
version,
22-
source: { ...TARBALL_SOURCE, package: { format: "tarball", path: `tarballs/${name.slice(1).replace("/", "-")}-${version}.tgz`, integrity: "sha512-AAAA" } },
23-
}));
24-
return { schemaVersion: "1", topLevel, entries } as never;
25-
}
26-
27-
function tarballSpec(version: string) {
28-
return {
29-
...SPEC,
30-
frozenApprovalBundle: {
31-
source: TARBALL_SOURCE,
32-
entry: "./pr-triage.mjs",
33-
closure: closurePinning({ name: "@corbits/pr-triage-workflow", version }),
34-
},
35-
};
36-
}
37-
385
function selectReturning(rows: unknown[]) {
396
const chain = {
407
from: () => chain,
@@ -47,38 +14,15 @@ function selectReturning(rows: unknown[]) {
4714
}
4815

4916
describe("deployments", () => {
50-
test("two deployments run the same source only when asset, commit and entry match", async () => {
51-
const moved = { ...SPEC, frozenApprovalBundle: { ...SPEC.frozenApprovalBundle, source: { ...SOURCE, package: { ...SOURCE.package, commitSha: "beef" } } } };
52-
const specs: Record<string, unknown> = { run_a: SPEC, run_b: SPEC, run_moved: moved };
53-
const launchSpecs = { get: async (anchorRunId: string) => (specs[anchorRunId] ?? null) as never };
54-
expect(await sameSource(launchSpecs, "run_a", "run_b")).toBe(true);
55-
expect(await sameSource(launchSpecs, "run_a", "run_moved")).toBe(false);
56-
expect(await sameSource(launchSpecs, "run_a", "run_missing")).toBe(false);
57-
});
58-
59-
test("tarball-sourced deployments with different pins are not the same source", async () => {
60-
const specs: Record<string, unknown> = { run_a: tarballSpec("0.1.0-a"), run_b: tarballSpec("0.1.0-a"), run_bumped: tarballSpec("0.1.0-b") };
61-
const launchSpecs = { get: async (anchorRunId: string) => (specs[anchorRunId] ?? null) as never };
62-
expect(await sameSource(launchSpecs, "run_a", "run_b")).toBe(true);
63-
expect(await sameSource(launchSpecs, "run_a", "run_bumped")).toBe(false);
64-
});
65-
6617
test("a deployment whose cancellation was requested is listed as cancelling", async () => {
6718
const createdAt = new Date("2026-10-07T12:00:00.000Z");
6819
const db = selectReturning([
69-
{ runId: "run_new", workflow: "pr-triage", domain: "acme.test", createdAt, cancellationRequestedAt: null },
70-
{ runId: "run_old", workflow: "pr-triage", domain: "acme.test", createdAt, cancellationRequestedAt: createdAt },
20+
{ runId: "run_new", workflow: "triage-listener-github", domain: "acme.test", createdAt, cancellationRequestedAt: null },
21+
{ runId: "run_old", workflow: "triage-listener-github", domain: "acme.test", createdAt, cancellationRequestedAt: createdAt },
7122
]);
72-
expect(await resolveLiveDeployments(db, TENANT_ID, "pr-triage")).toEqual([
73-
{ runId: "run_new", address: "run_new@acme.test", createdAt, cancelling: false, workflow: "pr-triage" },
74-
{ runId: "run_old", address: "run_old@acme.test", createdAt, cancelling: true, workflow: "pr-triage" },
23+
expect(await resolveLiveDeployments(db, TENANT_ID, "triage-listener-github")).toEqual([
24+
{ runId: "run_new", address: "run_new@acme.test", createdAt, cancelling: false, workflow: "triage-listener-github" },
25+
{ runId: "run_old", address: "run_old@acme.test", createdAt, cancelling: true, workflow: "triage-listener-github" },
7526
]);
7627
});
7728
});
78-
79-
describe("pinOf", () => {
80-
test("pins the frozen closure's one top-level package, and refuses a closure without one", () => {
81-
expect(pinOf(closurePinning({ name: "@corbits/pr-triage-workflow", version: "0.1.0-a" }))).toBe("@corbits/pr-triage-workflow@0.1.0-a");
82-
expect(() => pinOf(closurePinning())).toThrow("frozen closure has 0 top-level packages, expected one");
83-
});
84-
});

‎apps/hub/src/github/deployment.ts‎

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { and, desc, eq, inArray, isNotNull, isNull, sql } from "drizzle-orm";
2-
import { schema, type DB, type ResolvedSenderPrincipal, type WorkflowRunLaunchSpecStore } from "@intx/db";
1+
import { and, desc, eq, inArray, isNotNull, sql } from "drizzle-orm";
2+
import { schema, type DB, type ResolvedSenderPrincipal } from "@intx/db";
33
import { MAIL_ADMISSION_DENIED_CODE } from "@intx/hub-sessions";
44
import type { RunTriggerMaterialize } from "@corbits/webhooks";
55
import type { ToolPackageManifest } from "@intx/types/tool-packages";
@@ -55,20 +55,7 @@ export type LiveDeployment = { runId: string; address: string; createdAt: Date;
5555

5656
export type TenantDeployment = LiveDeployment & { workflow: string };
5757

58-
/**
59-
* A worker deployment carries this key in its deploy content, so the hub can tell it from the listener it was copied
60-
* from: a worker ran one mail and was released when that run ended.
61-
*/
62-
const WORKER_MARKER = "corbitsTriageWorker";
63-
64-
export type DeploymentKind = "listener" | "worker";
65-
66-
function markedAs(kind: DeploymentKind) {
67-
const marker = sql`${schema.workflowRunLaunchSpec.deployContent} ->> ${WORKER_MARKER}`;
68-
return kind === "worker" ? eq(marker, "true") : isNull(marker);
69-
}
70-
71-
type DeploymentQuery = { tenantId: string; definitionName?: string; kind: DeploymentKind };
58+
type DeploymentQuery = { tenantId: string; definitionName?: string };
7259

7360
/**
7461
* A workflow the portal publishes per person deploys from an asset named for its publisher and release, which names
@@ -97,7 +84,6 @@ async function deploymentsWhere(db: DB["db"], query: DeploymentQuery): Promise<T
9784
isNotNull(workflowRun.anchorRunId),
9885
eq(workflowRun.id, workflowRun.anchorRunId),
9986
inArray(workflowRun.status, [...liveWorkflowRunStatuses]),
100-
markedAs(query.kind),
10187
))
10288
.orderBy(desc(workflowRun.createdAt), desc(workflowRun.id));
10389
return rows.map((row) => ({
@@ -109,9 +95,9 @@ async function deploymentsWhere(db: DB["db"], query: DeploymentQuery): Promise<T
10995
}));
11096
}
11197

112-
/** Live deployments; every workflow's when no name is given; listeners unless workers are asked for. */
113-
export function resolveLiveDeployments(db: DB["db"], tenantId: string, definitionName?: string, kind: DeploymentKind = "listener"): Promise<TenantDeployment[]> {
114-
return deploymentsWhere(db, { tenantId, definitionName, kind });
98+
/** Live deployments; every workflow's when no name is given. */
99+
export function resolveLiveDeployments(db: DB["db"], tenantId: string, definitionName?: string): Promise<TenantDeployment[]> {
100+
return deploymentsWhere(db, { tenantId, definitionName });
115101
}
116102

117103
export function newestLive(deployments: readonly LiveDeployment[]): LiveDeployment | undefined {
@@ -134,12 +120,6 @@ function rootOf(closure: ToolPackageManifest): PackageVersion {
134120
return { name: root.name, version: root.version };
135121
}
136122

137-
/** The launch spec does not store the pin, so it is read back from the frozen closure. */
138-
export function pinOf(closure: ToolPackageManifest): string {
139-
const { name, version } = rootOf(closure);
140-
return `${name}@${version}`;
141-
}
142-
143123
export type ClosureVersions = { workflow: PackageVersion; tools: PackageVersion[] };
144124

145125
/** A tarball-format source pins its one top-level package; a source-format one names its package, at the version it built. */
@@ -159,24 +139,3 @@ export function closureVersions(bundle: FrozenApprovalBundle): ClosureVersions |
159139
.map((entry) => ({ name: entry.name, version: entry.version }));
160140
return { workflow, tools };
161141
}
162-
163-
/** A tarball-format source names its package only through the pin. */
164-
function pinFor(bundle: FrozenApprovalBundle): string | undefined {
165-
if (bundle.source.kind !== "asset" || bundle.source.package.format !== "tarball") return undefined;
166-
return pinOf(bundle.closure);
167-
}
168-
169-
/** The reason the hub gave when it replaced a busy deployment with a fresh copy, which earlier audit entries carry. */
170-
export const ROTATION_REASON = "Replaced by a fresh deployment of the same workflow";
171-
172-
/** Whether two deployments were made from the same request: one source, package, pin and entry. */
173-
export async function sameSource(launchSpecs: Pick<WorkflowRunLaunchSpecStore, "get">, anchorRunId: string, otherAnchorRunId: string): Promise<boolean> {
174-
const [spec, other] = await Promise.all([launchSpecs.get(anchorRunId), launchSpecs.get(otherAnchorRunId)]);
175-
if (!spec || !other) return false;
176-
const a = spec.frozenApprovalBundle;
177-
const b = other.frozenApprovalBundle;
178-
if (a.source.kind !== "asset" || b.source.kind !== "asset" || a.entry !== b.entry) return false;
179-
return a.source.assetId === b.source.assetId
180-
&& JSON.stringify(a.source.package) === JSON.stringify(b.source.package)
181-
&& pinFor(a) === pinFor(b);
182-
}

0 commit comments

Comments
 (0)