merge: integrate current master and preserve preview migration upgrades

Regenerate migration metadata after mainline claimed 0272. Preserve the work-folder SQL bytes under 0273 and exercise missing earlier migrations against saved preview data.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-11 17:15:48 -05:00
commit 3dd2b51596
201 files changed
+62608 -3880

No files matched your search

@@ -0,0 +1,29 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const workflow = readFileSync(new URL("../../workflows/refresh-lockfile.yml", import.meta.url), "utf8");
const nodeStep = workflow.split(" - name: Setup Node.js\n")[1]?.split(" - name:")[0];
assert.ok(nodeStep, "the refresh workflow must set up Node");
const input = (name) => nodeStep.match(new RegExp(`^ ${name}: (.+)$`, "m"))?.[1].trim();
// setup-node's explicit cache input enables a store cache independently of its
// automatic npm detection. Disabling only automatic detection is insufficient.
function cacheProvider(explicitCache, automaticCache, packageManager) {
if (explicitCache) return explicitCache;
if (automaticCache !== "false" && packageManager.startsWith("npm@")) return "npm";
return undefined;
}
for (const packageManager of ["pnpm@9.15.4", "npm@11.0.0"]) {
test(`resolution-only refresh cannot write a package-store cache (${packageManager})`, () => {
assert.match(workflow, /run: pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile/);
assert.equal(
cacheProvider(input("cache"), input("package-manager-cache"), packageManager),
undefined,
"a metadata-only job must not claim the shared cache key with an empty store",
);
// This is the original failure mode, even with automatic caching disabled.
assert.equal(cacheProvider("pnpm", "false", packageManager), "pnpm");
});
}
@@ -0,0 +1,83 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { runInNewContext } from "node:vm";
const fleet = "runs-on/fleet=paperclip-post-merge-x64/env=public-ci";
const sha = "a".repeat(40);
const base = {
repository: "paperclipai/paperclip", repository_id: "1170821064",
ref: "refs/heads/master", event_name: "push", sha,
};
const expectedJobs = {
"cloud-readiness.yml": ["artifacts", "source_verified", "ready"],
"cloud-artifacts.yml": ["dispatch_migrator"],
"release-verify.yml": ["typecheck", "general_tests", "serialized_tests", "runner_workflow_evals", "verify_paperclip_runner", "build"],
"runner-chaos-evals.yml": ["chaos_and_recovery"],
"release.yml": ["plan_preview", "package_preview"],
};
for (const [file, expectedNames] of Object.entries(expectedJobs)) {
const workflow = readFileSync(new URL(`../../workflows/${file}`, import.meta.url), "utf8");
const jobs = [...workflow.matchAll(/^ ([a-z_]+):\n([\s\S]*?)(?=^ [a-z_]+:\n|(?![\s\S]))/gm)];
const routed = jobs.filter(([, , body]) => body.includes(fleet));
test(`${file}: all intended jobs carry the post-merge guard`, () => {
assert.deepEqual(routed.map(([, name]) => name).sort(), [...expectedNames].sort());
});
for (const [, job, body] of routed) {
const expression = body.match(/^ runs-on: \$\{\{ (.+) \}\}$/m)?.[1];
assert.ok(expression, `${file}/${job} must use an explicit runner expression`);
const release = file === "release.yml";
const checkRef = release || file === "release-verify.yml" || file === "runner-chaos-evals.yml";
const inputs = { ref: sha, source_ref: sha, channel: "cloud-migrator" };
const defaultContext = { ...base, event_name: release ? "workflow_dispatch" : "push" };
const cases = [
{ name: "exact master source", expected: fleet },
{ name: "manual exact master source", github: { event_name: "workflow_dispatch" }, expected: fleet },
{ name: "switch disabled", enabled: "false" },
{ name: "switch absent", enabled: "" },
{ name: "malformed switch", enabled: "yes" },
{ name: "fork", github: { repository: "someone/paperclip", repository_id: "123" } },
{ name: "repository renamed or transferred", github: { repository_id: "123" } },
{ name: "unapproved PR", github: { event_name: "pull_request", ref: "refs/pull/1/merge" } },
{ name: "PR event even with master ref", github: { event_name: "pull_request" } },
{ name: "privileged PR event", github: { event_name: "pull_request_target" } },
{ name: "workflow completion event", github: { event_name: "workflow_run" } },
{ name: "repository dispatch", github: { event_name: "repository_dispatch" } },
{ name: "scheduled caller", github: { event_name: "schedule" } },
{ name: "branch workflow", github: { ref: "refs/heads/feature" } },
{ name: "release tag", github: { ref: "refs/tags/v2026.911.0" } },
];
if (checkRef) {
const key = release ? "source_ref" : "ref";
for (const value of ["b".repeat(40), "refs/pull/1/head", "master", "feature", "v1.0.0", ""]) {
cases.push({ name: `unverified source ${value || "(empty)"}`, inputs: { [key]: value } });
}
cases.push({ name: "missing source identity", github: { sha: "" }, inputs: { [key]: "" } });
}
if (release) {
cases.push({ name: "preview of master", inputs: { channel: "preview" } });
cases.push({ name: "stable release", inputs: { channel: "stable" } });
}
for (const { name, github = {}, inputs: overrides = {}, enabled = "true", expected = "ubuntu-latest" } of cases) {
test(`${file}/${job}: ${name}`, () => {
const context = { github: { ...defaultContext, ...github }, inputs: { ...inputs, ...overrides }, vars: { AWS_POST_MERGE_CI_ENABLED: enabled } };
// These canonical contexts use boolean operators and string comparisons
// whose results match GitHub's expression evaluation.
assert.equal(runInNewContext(expression, context), expected);
const timeout = body.match(/^ timeout-minutes: (.+)$/m)?.[1];
assert.ok(timeout, "AWS jobs need a timeout below the 45-minute instance lifetime");
const minutes = timeout.startsWith("${{") ? runInNewContext(timeout.slice(3, -2), context) : Number(timeout);
if (expected === fleet) assert.ok(minutes > 0 && minutes < 45);
if (release && job === "plan_preview") assert.equal(minutes, expected === fleet ? 10 : 360);
});
}
}
if (file === "release.yml") {
test("npm publisher always uses a GitHub-hosted runner", () => {
const publisher = jobs.find(([ , job]) => job === "publish_preview")?.[2];
assert.match(publisher, /^ runs-on: ubuntu-latest$/m);
assert.match(publisher, /^ environment: npm-canary$/m);
assert.match(publisher, /^ id-token: write$/m);
});
}
}
@@ -0,0 +1,38 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { runInNewContext } from "node:vm";
const workflow = readFileSync(new URL("../../workflows/release-verify.yml", import.meta.url), "utf8");
const typecheck = workflow.split(" typecheck:\n")[1].split(" general_tests:\n")[0];
const cache = typecheck.split(" - name: Cache typecheck Rust dependencies\n")[1].split(" - name: Validate release package manifest")[0];
const sha = "a".repeat(40);
const github = { repository: "paperclipai/paperclip", event_name: "push", ref: "refs/heads/master", sha };
for (const [name, overrides, ref, allowed] of [
["exact master push", {}, sha, true],
["PR", { event_name: "pull_request", ref: "refs/pull/1/merge" }, sha, false],
["privileged PR", { event_name: "pull_request_target" }, sha, false],
["fork", { repository: "someone/paperclip" }, sha, false],
["branch", { ref: "refs/heads/feature" }, sha, false],
["manual source", { event_name: "workflow_dispatch" }, sha, false],
["unmerged source", {}, "b".repeat(40), false],
["moving ref", {}, "master", false],
]) {
test(`typecheck cache restore and save: ${name}`, () => {
for (const field of ["if", "save-if"]) {
const expr = cache.match(new RegExp(`^ +${field}: \\$\\{\\{ (.+) \\}\\}$`, "m"))?.[1];
assert.ok(expr);
assert.equal(runInNewContext(expr, { github: { ...github, ...overrides }, inputs: { ref } }), allowed);
}
});
}
test("cache excludes workspace code and executable installs, and preserves full checks", () => {
assert.match(cache, /uses: Swatinem\/rust-cache@[a-f0-9]{40}/);
assert.match(cache, /workspaces: packages\/paperclip-runner\/runner -> target/);
assert.match(cache, /shared-key: release-typecheck-v1/);
assert.match(cache, /cache-workspace-crates: false/);
assert.match(cache, /cache-bin: false/);
assert.ok(typecheck.indexOf('echo "RUSTUP_TOOLCHAIN=$toolchain"') < typecheck.indexOf("uses: Swatinem/rust-cache"));
assert.match(typecheck, /run: pnpm -r typecheck/);
assert.match(workflow, /shared-key: release-runner-v1/);
});
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
dispatch_migrator:
name: Start exact-source cloud migrator publication
if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 5
permissions:
actions: write
+3 -3
View File
@@ -32,7 +32,7 @@ jobs:
artifacts:
if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master'
name: Wait for exact-source cloud artifacts
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 35
permissions:
contents: read
@@ -55,7 +55,7 @@ jobs:
name: Cloud source verified v1
needs: [verify]
if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 5
permissions:
contents: read
@@ -81,7 +81,7 @@ jobs:
name: Cloud deployable v1
needs: [verify, image, artifacts]
if: github.repository == 'paperclipai/paperclip' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 5
steps:
- name: Record cloud readiness
+3 -1
View File
@@ -32,7 +32,9 @@ jobs:
uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
# Resolution-only installs do not populate the package store. Do not
# claim the shared cache key with an empty archive before full installs.
package-manager-cache: false
- name: Refresh pnpm lockfile
run: pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile
+33 -6
View File
@@ -8,6 +8,9 @@ on:
required: true
type: string
# Caller-provided refs may name unmerged PR code. AWS is eligible only when
# the caller runs on canonical master and verifies that event's exact SHA.
# The organization group also restricts these workflow files to master.
jobs:
runner_chaos_evals:
name: Pre-release Runner chaos evals
@@ -17,7 +20,7 @@ jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
permissions:
contents: read
@@ -39,6 +42,30 @@ jobs:
node-version: 24
cache: pnpm
- name: Select the pinned Runner Rust toolchain
working-directory: packages/paperclip-runner
run: |
set -euo pipefail
rustup show
toolchain="$(rustup show active-toolchain | awk '{print $1}')"
echo "RUSTUP_TOOLCHAIN=$toolchain" >> "$GITHUB_ENV"
- name: Cache typecheck Rust dependencies
# Restore and save only within trusted master-push verification. GitHub
# isolates branch/PR caches from master; other callers compile afresh.
if: ${{ github.repository == 'paperclipai/paperclip' && github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ref == github.sha }}
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: packages/paperclip-runner/runner -> target
shared-key: release-typecheck-v1
# Rebuild workspace code and rerun every check. Cache only compiled
# dependencies; never restore installed executables from cargo/bin.
cache-workspace-crates: false
cache-bin: false
# The step guard also restricts restores. Save only after a successful
# master-push verification of that push's exact commit.
save-if: ${{ github.repository == 'paperclipai/paperclip' && github.event_name == 'push' && github.ref == 'refs/heads/master' && inputs.ref == github.sha }}
- name: Validate release package manifest
run: node ./scripts/release-package-map.mjs check
@@ -50,7 +77,7 @@ jobs:
general_tests:
name: General tests (${{ matrix.group_label }})
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
permissions:
contents: read
@@ -157,7 +184,7 @@ jobs:
serialized_tests:
name: Serialized tests (${{ matrix.shard_label }})
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
permissions:
contents: read
@@ -206,7 +233,7 @@ jobs:
runner_workflow_evals:
name: Runner workflow eval scorer contract
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 10
permissions:
contents: read
@@ -236,7 +263,7 @@ jobs:
verify_paperclip_runner:
name: Verify Paperclip Runner
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
permissions:
contents: read
@@ -290,7 +317,7 @@ jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
permissions:
contents: read
+7 -2
View File
@@ -76,11 +76,15 @@ env:
jobs:
plan_preview:
# Only the current master commit can use AWS. A preview or older source
# falls back to GitHub-hosted runners, including raced merge dispatches.
name: Check preview artifacts
if: github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' && (inputs.channel == 'preview' || inputs.channel == 'cloud-migrator') && !inputs.dry_run
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.event_name == 'workflow_dispatch' && inputs.channel == 'cloud-migrator' && github.sha != '' && inputs.source_ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
permissions:
contents: read
# Preserve the previous hosted default; only AWS needs the Fleet limit.
timeout-minutes: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.event_name == 'workflow_dispatch' && inputs.channel == 'cloud-migrator' && github.sha != '' && inputs.source_ref == github.sha && 10 || 360 }}
outputs:
image: ${{ steps.plan.outputs.image }}
packages: ${{ steps.plan.outputs.packages }}
@@ -104,7 +108,7 @@ jobs:
name: Build preview migrator
needs: plan_preview
if: needs.plan_preview.outputs.packages == 'true'
runs-on: ubuntu-latest
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.event_name == 'workflow_dispatch' && inputs.channel == 'cloud-migrator' && github.sha != '' && inputs.source_ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 30
permissions:
contents: read
@@ -141,6 +145,7 @@ jobs:
retention-days: 7
publish_preview:
# npm trusted publishing supports GitHub-hosted runners only.
name: Publish preview migrator
needs: [plan_preview, package_preview]
if: github.ref == 'refs/heads/master' && needs.plan_preview.outputs.packages == 'true' && needs.package_preview.result == 'success'
+2 -2
View File
@@ -20,8 +20,8 @@ concurrency:
jobs:
chaos_and_recovery:
name: Restart, replay, trace, and recovery faults
runs-on: ubuntu-latest
timeout-minutes: 45
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 40 || 45 }}
permissions:
contents: read