Files
gstack/test/pregate-docs-lane-review.test.ts
Garry Tanandcapy-ai[bot] 568cd5aa56 v1.91.254.0 pregate: starter policy, benign-finding allowlist, environment vs diff failures, per-run scratch (#3480)
* pregate: starter policy, benign-finding allowlist, environment failures, per-run scratch

A repository with no .gstack/pregate.json now gets a warn (PREGATE_NO_POLICY)
with a starter policy printed, instead of PREGATE_NO_LANE on every touched
file; gstack-pregate init remains the explicit write. The policy gains an
allowlist for benign secrets/pii findings (env.value and secret never
allowlistable). Checks that cannot run because of the machine report
PREGATE_ENVIRONMENT (incomplete, with kind and evidence) apart from diff
failures. Every run writes its scratch under the state root, per run id.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* pregate: rename run-scratch module (strays rule) and map starter.ts to its test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* pregate literals: match a module as a path fragment, require the constant name for a one-character value

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

guards: an exited guard is the machine's only on exit 127, never from its output;
secrets: clear an interrupted run's added-line copies before scanning;
environment: one fix table per kind, one ANSI strip, hoisted not-found vocabulary;
starter returns its lines; allowlistDigest reuses sha256; stale headers and the
PREGATE_INCOMPLETE fix clause updated; tests for the mixed verify branch, disk
precedence, allowlist boundaries, a positionless finding, head-policy strictness,
run-id edges and the unwritable state root.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* pregate: count a module's full file name without a directory as a consumer

consumersOf anchored both the stem and the full basename on a path
separator, so run('install.sh') or bash install.sh without a directory
stopped counting for a multi-character old value. Only the bare stem
needs the separator; the full file name is specific on its own.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* pregate: a diff cannot clear itself as an environment failure; a taken --run id gets its own directory

Adversarial review of the environment classifier and the scratch layout:

- exit 127 and 'command not found' are missing-binary only when the
  command's own binary is off PATH (runner, guard and remote gate alike);
  a script the diff controls that exits 127 is the diff's failure
- a local lane with no test summary is classified from its output only
  when the runner was not seen starting a test file; a remote gate only
  from its first 40 lines, before any test output could stream
- environment.proof records launch (certain) or output (inferred); an
  output-inferred cause never says 'the diff is not at fault', its line
  says '(inferred from output)', and verify names such checks
- a runner killed by a signal is PREGATE_INCOMPLETE, not missing-binary
- a --run id whose directory exists takes <id>-2 .. -9 instead of
  sharing (and truncating) a live run's logs
- engine.* findings are never allowlistable; a pattern entry the scanner
  gave no position for is reported as not tested instead of silently inert
- docs: synopsis --run, no-policy lanes wording, verify's code list,
  guard exit 127 carve-out, PREGATE_SECRETS allowlist pointer, scratch

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(pregate): PREGATE_INCOMPLETE names a signal-killed runner; CHANGELOG lists every doc edit

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 20:17:41 +00:00

65 lines
3.7 KiB
TypeScript

/**
* Reviewer-written checks for the docs/no-tests empty lane (#3215, cherry-picked
* with its author's commits): the edges the contributor's tests do not reach.
* A declared [] labels only Markdown; template sources, unread test-tree
* Markdown and code still fail closed; renames and deletions of Markdown are
* docs; and an empty_lanes key the branch adds to its own pregate.json is
* not honoured, because only the base branch's copy is (the C1 trust
* boundary). Real gstack-pregate subprocesses on fixture repos.
*/
import { afterEach, describe, expect, test } from 'bun:test';
import { EXIT } from '../lib/headless-artifacts';
import { BASE_PREGATE, cleanup, commit, makeFixtureRepo, runBin, type FixtureRepo } from './helpers/pregate-fixture';
const repos: FixtureRepo[] = [];
afterEach(() => { while (repos.length) cleanup(repos.pop()!); });
const fixture = (pregate: Record<string, unknown> | null) => { const r = makeFixtureRepo({ pregate }); repos.push(r); return r; };
const lanes = (repo: FixtureRepo) => runBin(repo, 'gstack-pregate', ['--base', 'main', '--only', 'lanes', '--explain']);
describe('docs/no-tests empty lane: reviewer edges (#3215)', () => {
test('a [] declaration labels Markdown as docs/no-tests and leaves a declared non-Markdown file unlabeled', () => {
const repo = fixture(BASE_PREGATE);
commit(repo, { 'docs/guide.md': '# guide\n', 'docs/diagram.svg': '<svg/>\n' });
const r = lanes(repo);
expect(r.status).toBe(EXIT.ok);
expect(r.out).toContain('lanes: docs/no-tests (empty lane): docs/guide.md');
expect(r.out).not.toMatch(/docs\/no-tests \(empty lane\):.*diagram\.svg/);
expect(r.out).toMatch(/docs\/no-tests: 1 documentation file\(s\)/);
});
test('without a pin file, a template source and an unread test-tree Markdown file are still unmapped (the no-policy warning, never docs); a renamed or deleted Markdown file is docs', () => {
const repo = fixture(null);
repo.git('checkout', '-q', 'main');
commit(repo, { 'notes/old.md': '# old\n', 'notes/gone.md': '# gone\n' }, 'notes');
repo.git('push', '-q', 'origin', 'main');
repo.git('checkout', '-q', 'feature');
repo.git('merge', '-q', '--ff-only', 'main');
repo.git('mv', 'notes/old.md', 'notes/new.md');
commit(repo, { 'notes/gone.md': null }, 'rename and delete');
const docs = lanes(repo);
expect(docs.status).toBe(EXIT.ok);
expect(docs.out).toContain('notes/new.md: docs/no-tests (empty lane)');
expect(docs.out).toContain('notes/gone.md: docs/no-tests (empty lane)');
commit(repo, { 'skills/review/SKILL.md.tmpl': '# {{PREAMBLE}}\n', 'test/notes.md': '# notes no test reads\n' });
const r = lanes(repo);
expect(r.err).not.toContain('PREGATE_NO_LANE');
expect(r.out).toContain('(PREGATE_NO_POLICY)');
expect(r.out).toContain('no policy: skills/review/SKILL.md.tmpl test/notes.md have no test lane');
expect(r.out).not.toContain('skills/review/SKILL.md.tmpl: docs/no-tests');
expect(r.out).not.toContain('test/notes.md: docs/no-tests');
});
test('empty_lanes is read from the base branch only: the same key added on the branch does not clear a file', () => {
const repo = fixture({ ...BASE_PREGATE, empty_lanes: ['**'] });
commit(repo, { 'scripts/gen.sh': '#!/bin/sh\necho gen\n' });
const code = lanes(repo);
expect(code.status).toBe(EXIT.fail);
expect(code.out).toContain('FAIL lanes — scripts/gen.sh has no test lane');
const branch = fixture(BASE_PREGATE);
commit(branch, { '.gstack/pregate.json': JSON.stringify({ ...BASE_PREGATE, empty_lanes: ['notes/**/*.txt'] }, null, 2) + '\n', 'notes/sketch.txt': 'not markdown\n' });
const r = lanes(branch);
expect(r.status).toBe(EXIT.fail);
expect(r.out).toContain('FAIL lanes — notes/sketch.txt has no test lane');
});
});