From 8eefc6fae17fa24c6358e7f5f0522cbd5943eb3d Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Fri, 2 Oct 2026 17:00:07 -0400 Subject: [PATCH] Canonicalize repository identity in checkpoint proof preparation Entire-Checkpoint: 0feafd472eff --- scripts/lib/checkpoint-proof.mjs | 5 +++-- tests/checkpoint-proof.test.mjs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/scripts/lib/checkpoint-proof.mjs b/scripts/lib/checkpoint-proof.mjs index 6fb919e..155236d 100644 --- a/scripts/lib/checkpoint-proof.mjs +++ b/scripts/lib/checkpoint-proof.mjs @@ -110,14 +110,15 @@ export async function resolveProofScope(options, dependencies) { const { repository, git, github, remote } = dependencies; assertScopeOptions(options, repository); const canonical = github(`repos/${repository.fullName}`); - if (`github.com/${canonical.full_name}` !== repositoryId) throw new Error('Canonical repository mismatch.'); + const canonicalId = `github.com/${canonical.full_name}`; + if (canonicalId.toLowerCase() !== repositoryId.toLowerCase()) throw new Error('Canonical repository mismatch.'); if (git('rev-parse', '--verify', `${candidate}^{commit}`) !== candidate) throw new Error('Candidate unavailable.'); const targetRef = event === 'push' ? 'refs/heads/main' : `refs/pull/${pullRequest}/head`; assertProofTarget(candidate, git('rev-parse', 'HEAD'), await remote(targetRef)); await assertFreshMain(event, git, remote); const base = event === 'push' ? git('rev-parse', `${candidate}^`) : git('merge-base', 'origin/main', candidate); const checkpointHead = checkpointSource(event, candidate, repository, git, github); - return { repositoryId, event, candidate, targetRef, ...(pullRequest === undefined ? {} : { pullRequest }), + return { repositoryId: canonicalId, event, candidate, targetRef, ...(pullRequest === undefined ? {} : { pullRequest }), base, checkpointHead, checkpointBase: git('merge-base', base, checkpointHead) }; } diff --git a/tests/checkpoint-proof.test.mjs b/tests/checkpoint-proof.test.mjs index 0bb7a27..86ba959 100644 --- a/tests/checkpoint-proof.test.mjs +++ b/tests/checkpoint-proof.test.mjs @@ -166,3 +166,35 @@ test('CLI resolves a checkout subdirectory before rejecting source-tree proof ou await assert.rejects(access(marker)); await assert.rejects(access(join(root, 'docs', 'handoff'))); } finally { await rm(root, { recursive: true, force: true }); } }); + + +for (const event of ['pull_request', 'push']) { + for (const [origin, input] of [ + ['fixture/test', 'github.com/fixture/test'], + ['FIXTURE/TEST', 'github.com/Fixture/Test'], + ['Fixture/Test', 'GITHUB.COM/FIXTURE/TEST'], + ]) { + test(`${event} scope canonicalizes ${origin} origin and ${input} input`, async () => { + const candidate = 'a'.repeat(40), base = 'b'.repeat(40); + const canonicalId = 'github.com/Fixture/Test'; + const repository = { fullName: origin, identity: `github.com/${origin}` }; + const options = { repositoryId: input, event, candidate, ...(event === 'pull_request' ? { pullRequest: '5' } : {}) }; + const dependencies = { + repository, + github: path => path.endsWith('/pulls') ? [] : { full_name: 'Fixture/Test' }, + git: (...args) => args[0] === 'merge-base' || args.at(-1) === `${candidate}^` || args.at(-1) === 'origin/main' ? base : candidate, + remote: async ref => event === 'pull_request' && ref === 'refs/heads/main' ? base : candidate, + }; + const resolved = await resolveProofScope(options, dependencies); + assert.equal(resolved.repositoryId, canonicalId); + assert.equal(JSON.parse(proofTransport(resolved, bytes).text).repositoryId, canonicalId); + assert.deepEqual(await resolveProofScope({ ...options, repositoryId: resolved.repositoryId }, dependencies), resolved); + for (const wrong of ['github.com/foreign/test', 'github.com/fixture/foreign']) { + await assert.rejects(resolveProofScope({ ...options, repositoryId: wrong }, dependencies), /Repository mismatch/); + } + for (const full_name of ['Foreign/Test', 'Fixture/Foreign']) { + await assert.rejects(resolveProofScope(options, { ...dependencies, github: () => ({ full_name }) }), /Canonical repository mismatch/); + } + }); + } +}