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
7 changes: 4 additions & 3 deletions agentic-org/scripts/publish-edition-branch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { buildBylinesTsv } from './build-bylines-tsv.mjs';
import { CORPUS_PROVENANCE_FILE, hostAssertedProvenance } from './edition-provenance.mjs';
import { PUBLIC_CONTENT_PATHS, isPublicContentPath } from './public-content.mjs';
import { releaseClock } from './release-time.mjs';
import { resolveStagedEdition } from './staged-edition.mjs';
export { artifactDigest, resolveStagedEdition } from './staged-edition.mjs';
import { pruneEarlierEditions, resolveStagedEdition } from './staged-edition.mjs';
export { artifactDigest, pruneEarlierEditions, resolveStagedEdition } from './staged-edition.mjs';
export { CORPUS_PROVENANCE_FILE, CORPUS_PROVENANCE_VERSION, DEFAULT_LANDED_RECORD, LANDED_VERSION, editionCorpusClaim, hostAssertedProvenance, landedCorpus } from './edition-provenance.mjs';

// One ed25519 deploy key per repository, reached through an SSH host alias so
Expand Down Expand Up @@ -382,7 +382,8 @@ export async function publishEditionBranch(options, url = remoteUrl(EDITION_PUSH
}

export async function main(argv = process.argv.slice(2)) {
const result = await publishEditionBranch(parseArguments(argv));
const options = parseArguments(argv), result = await publishEditionBranch(options);
if (!options.dryRun && (result.pushed || result.already_published)) await pruneEarlierEditions(options.staging, result.edition).catch((error) => console.warn(`staging prune skipped: ${error.message}`));
// A positive signal, not an exit code: the branch and commit are what a
// person needs to open the pull request, and their absence is the failure.
console.log(JSON.stringify(result, undefined, 2));
Expand Down
17 changes: 16 additions & 1 deletion agentic-org/scripts/staged-edition.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createHash } from 'node:crypto';
import { lstat, readFile, readdir, readlink, stat } from 'node:fs/promises';
import { lstat, readFile, readdir, readlink, rm, stat } from 'node:fs/promises';
import path from 'node:path';
import { validateRenderedEvidence } from '../../ops/validate-rendered-evidence.mjs';

Expand Down Expand Up @@ -94,3 +94,18 @@ async function stagedReceipt(stateRoot, edition, artifact) {
return value;
}

// Each staged artifact is a full site build (~0.5 GB) and only proves the
// edition compiles; once its branch is pushed it is never read again. Only
// earlier editions go: today's may still be restaged for a late addition, and a
// candidate pressman is writing right now always carries today's date.
export async function pruneEarlierEditions(stagingRoot, edition) {
const removed = [];
for (const name of await readdir(stagingRoot)) {
const date = name.slice(0, 10);
if (!EDITION_PATTERN.test(date) || name[10] !== '-' || date >= edition) continue;
await rm(path.join(stagingRoot, name), { recursive: true, force: true });
removed.push(name);
}
return removed;
}

13 changes: 12 additions & 1 deletion agentic-org/scripts/staged-edition.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
import { mkdir, mkdtemp, readFile, readdir, rename, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { artifactDigest, resolveStagedEdition } from './staged-edition.mjs';
import { artifactDigest, pruneEarlierEditions, resolveStagedEdition } from './staged-edition.mjs';

const edition = '2026-09-11';
async function stages(t) {
Expand Down Expand Up @@ -101,3 +101,14 @@ test('publication rechecks final evidence even when the receipt authenticates co
await writeFile(latest.file, JSON.stringify(latest.receipt));
assert.equal((await f.resolve()).artifact, latest.artifact);
});

test('pruning after a publish removes only earlier editions and keeps the current link working', async t => {
const f = await stages(t);
const older = ['2026-09-09-aaaaaaaaaaaaaaaa', '2026-09-10-bbbbbbbbbbbbbbbb'];
for (const name of older) await mkdir(path.join(f.staging, name, 'website'), { recursive: true });
await mkdir(path.join(f.staging, 'scratch'));
assert.deepEqual((await pruneEarlierEditions(f.staging, edition)).sort(), older);
const left = (await readdir(f.staging)).sort();
assert.deepEqual(left, ['current-edition', ...f.entries.map(e => e.name), 'scratch'].sort());
assert.equal((await f.resolve()).artifact, f.entries.at(-1).artifact);
});
Loading