mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
fix: prove generated outputs in a second regen run; literal names only
The regen verdict comes from the unmodified run again, so a generator that reads its own registered output (gen-skill-docs and qa/templates/functional-report-template.md) passes as before; the proof is a second run with the touched outputs deleted, and its failure only means not proven. The carry ignores a proven path with glob syntax, since it uses the paths as pathspec globs (pre-landing review, #3564 pass 2). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f47aba6fe7
commit
bf9a417d54
10 files changed
+59
-29
No files matched your search
@@ -9,7 +9,7 @@ import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { EXIT } from '../lib/headless-artifacts';
|
||||
import { missingDependencies, parseRegistry, selectEntries, validateRegistry } from '../lib/regen';
|
||||
import { missingDependencies, parseRegistry, runRegen, selectEntries, validateRegistry } from '../lib/regen';
|
||||
import { BASE_REGISTRY, cleanup, commit, makeFixtureRepo, readFile, runBin, write, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
|
||||
const repos: FixtureRepo[] = [];
|
||||
@@ -135,6 +135,21 @@ describe('gstack-regen (bin)', () => {
|
||||
expect(parsed.wtree).toMatch(/^[0-9a-f]{40}$/);
|
||||
});
|
||||
|
||||
// Value: protects=the receipt carry's generated class (only outputs a generator recreates) and a generator that reads
|
||||
// its own registered output (gen-skill-docs and qa/templates/functional-report-template.md); fails_when=the proof
|
||||
// deletes outputs before the verdict run, or proves a path the command never wrote; seam=runRegen; no_claim=-
|
||||
test('prove: a writer proves its touched output; a generator that reads its own output stays fresh and proves nothing', () => {
|
||||
const registryOf = (repo: FixtureRepo) => parseRegistry(fs.readFileSync(path.join(repo.work, '.gstack/generated.json'), 'utf8')).registry;
|
||||
const writer = fixture();
|
||||
commit(writer, { 'src/version.js': "module.exports = { VERSION: '1.1.0' };\n", 'generated/digest.md': '# digest v1.1.0\n', 'generated/notes.md': '# hand-written\n' });
|
||||
expect(runRegen({ repoRoot: writer.work, registry: registryOf(writer), mode: 'check', only: ['digest'], prove: ['generated/digest.md', 'generated/notes.md', 'src/version.js'] }).results)
|
||||
.toMatchObject([{ id: 'digest', status: 'fresh', proven: ['generated/digest.md'] }]);
|
||||
const selfRead = fixture({ registry: { entries: [{ id: 'self', command: 'node selfread.js', outputs: ['generated/**'], inputs: ['selfread.js'] }] }, baseFiles: { 'selfread.js': "const fs=require('fs');fs.writeFileSync('generated/digest.md',fs.readFileSync('generated/digest.md','utf8'));\n" } });
|
||||
commit(selfRead, { 'generated/digest.md': '# hand edit\n' });
|
||||
expect(runRegen({ repoRoot: selfRead.work, registry: registryOf(selfRead), mode: 'check', prove: ['generated/digest.md'] }).results)
|
||||
.toMatchObject([{ id: 'self', status: 'fresh', proven: [] }]);
|
||||
});
|
||||
|
||||
test('a failing command is failed (exit 1, REGEN_COMMAND_FAILED, status incomplete); an output outside the declared globs is stale', () => {
|
||||
const repo = fixture({ registry: { entries: [{ id: 'boom', command: 'node -e "process.exit(7)"', outputs: ['generated/**'] }, { id: 'leak', command: 'node -e "require(\'fs\').writeFileSync(\'docs/extra.md\',\'x\')"', outputs: ['generated/**'] }] } });
|
||||
const r = runBin(repo, 'gstack-regen', ['check', '--base', 'main', '--allow-repo-commands']);
|
||||
|
||||
@@ -123,12 +123,15 @@ describe('carry across a base merge that changes a registered output', () => {
|
||||
const pregate = JSON.parse(fs.readFileSync(pregateJson, 'utf8'));
|
||||
const regen = pregate.checks.find((c: { id: string }) => c.id === 'regen');
|
||||
expect(regen).toMatchObject({ status: 'pass', proven: ['generated/digest.md'] });
|
||||
const headRegistry = path.join(repo.root, 'pregate-head-registry.json');
|
||||
regen.inputs.registry = 'head (unreviewed)';
|
||||
fs.writeFileSync(headRegistry, JSON.stringify(pregate));
|
||||
const foreign = carry(repo, body, ['--pregate', headRegistry]);
|
||||
expect(foreign.out).toContain('RECEIPT_CARRY_PRODUCT_CHANGED');
|
||||
expect(foreign.err).toContain('generated/digest.md');
|
||||
const tampered = path.join(repo.root, 'pregate-tampered.json');
|
||||
const label = regen.inputs.registry;
|
||||
for (const [registry, proven] of [['head (unreviewed)', ['generated/digest.md']], [label, ['generated/*']]]) {
|
||||
Object.assign(regen, { proven, inputs: { ...regen.inputs, registry } });
|
||||
fs.writeFileSync(tampered, JSON.stringify(pregate));
|
||||
const refused = carry(repo, body, ['--pregate', tampered]);
|
||||
expect(refused.out).toContain('RECEIPT_CARRY_PRODUCT_CHANGED');
|
||||
expect(refused.err).toContain('generated/digest.md');
|
||||
}
|
||||
const out = carry(repo, body, ['--pregate', pregateJson]);
|
||||
expect(out.status).toBe(EXIT.ok);
|
||||
expect(receiptIn(body)).toMatchObject({ head: repo.git('rev-parse', 'HEAD'), carry: { from: reviewed } });
|
||||
|
||||
Reference in new issue
Block a user