mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 22:45:02 +01:00
* 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>
112 lines
4.7 KiB
TypeScript
112 lines
4.7 KiB
TypeScript
/**
|
|
* pregate/types — the shapes every pre-gate check shares (plan C8). One check
|
|
* per file under lib/pregate/checks/, registered in lib/pregate/index.ts.
|
|
*/
|
|
import type { PregateCheck } from '../headless-artifacts';
|
|
import type { Registry } from '../regen';
|
|
import type { PregateConfig } from './config';
|
|
import type { Lane } from './workflows';
|
|
|
|
export type CheckStatus = PregateCheck['status'];
|
|
export type Stage = 'preflight' | 'tests';
|
|
|
|
export interface TouchedFile {
|
|
path: string;
|
|
/** A = added, M = modified, D = deleted, R = renamed (from `renamedFrom`). */
|
|
status: 'A' | 'M' | 'D' | 'R';
|
|
renamedFrom?: string;
|
|
/** True when the change is only in the working tree (uncommitted or untracked). */
|
|
uncommitted: boolean;
|
|
}
|
|
|
|
export interface PregateContext {
|
|
repoRoot: string;
|
|
/** Base branch name without `origin/`. */
|
|
base: string;
|
|
baseRef: string;
|
|
baseSha: string;
|
|
/** merge-base of origin/<base> and HEAD: the diff anchor. */
|
|
mergeBase: string;
|
|
head: string;
|
|
/** Working-tree content fingerprint at the start of the run (bin/gstack-wtree). */
|
|
wtree: string;
|
|
touched: TouchedFile[];
|
|
config: PregateConfig;
|
|
configLabel: string;
|
|
/** Where the policy came from: the base branch, the unreviewed head, the user-owned file, or nowhere (defaults). */
|
|
configKind: 'base' | 'head' | 'user' | 'none';
|
|
/** This run's scratch directory under the state root (lib/pregate/run-scratch.ts): logs and scan files, never os.tmpdir(). */
|
|
scratch: string;
|
|
/** The policy file to edit: the user-owned path when it is the active policy, else `.gstack/pregate.json`. */
|
|
configFile: string;
|
|
/** sha256 of the user-owned policy's bytes when it is active; recorded as `inputs.policy` (a base copy is pinned by `inputs.base`). */
|
|
configDigest?: string;
|
|
/** The user-owned policy path looked for and absent under the defaults; recorded so verify can tell when one appears. */
|
|
absentUserPolicy?: string;
|
|
registry: Registry;
|
|
registryLabel: string;
|
|
lanes: Lane[];
|
|
platform: NodeJS.Platform;
|
|
allowRepoCommands: boolean;
|
|
timeoutMs: number;
|
|
/** No output for this long kills a test-tier process group (`--stall-timeout`, `stall_timeout_s`); 0 disables. */
|
|
stallTimeoutMs: number;
|
|
/** Live stderr lines: per-file progress, heartbeats, the stage result. */
|
|
progress: (line: string) => void;
|
|
/** CPU count and `gstack-config test_backend`: under 8 cores a configured remote gate records an obligation instead of running locally. */
|
|
cores: number;
|
|
testBackend: string;
|
|
policyFrom: 'base' | 'head';
|
|
/** `--remote`: run the declared remote gate now, under this spend cap. */
|
|
remote: { spendCapUsd: number } | null;
|
|
env: NodeJS.ProcessEnv;
|
|
/** `--explain`: checks append provenance lines here. */
|
|
explain: string[];
|
|
}
|
|
|
|
export interface RemoteObligation {
|
|
/** `<workflow file>/<job id>`. */
|
|
lane: string;
|
|
platform: string;
|
|
workflow: string;
|
|
job: string;
|
|
/** Test files this machine cannot run that the touched files select. */
|
|
tests: string[];
|
|
/** What clears it: a run URL or an evidence-bundle lane label, recorded by `gstack-pregate clear`. */
|
|
receipt: string | null;
|
|
cleared_by?: string;
|
|
/** The exact command that discharges the obligation (a small machine's remote gate). */
|
|
command?: string;
|
|
}
|
|
|
|
export interface CheckResult extends PregateCheck {
|
|
stage: Stage;
|
|
/** The input identity the check ran against (tree, selection, registry source). */
|
|
inputs: Record<string, string>;
|
|
/** Exact command or edit an author runs; present on fail/warn/incomplete. */
|
|
fix?: string;
|
|
/** Result code the detail line ends with. */
|
|
code?: string;
|
|
/** Per-file or per-item lines for the table body. */
|
|
lines?: string[];
|
|
remote?: RemoteObligation[];
|
|
/** With `cause: 'environment'` (declared on PregateCheck): the kind and the line that proves it. */
|
|
environment?: EnvironmentCause;
|
|
}
|
|
|
|
export type EnvironmentKind = 'missing-binary' | 'network' | 'quota' | 'disk' | 'runtime' | 'tool';
|
|
/** How the cause was established: `launch` (the command could not start, its binary is off PATH, the runtime or a gstack tool refused), or `output` (a rule matched the command's text before any test was counted, which the diff's own output could have written). */
|
|
export type EnvironmentProof = 'launch' | 'output';
|
|
export interface EnvironmentCause { kind: EnvironmentKind; evidence: string; proof: EnvironmentProof }
|
|
|
|
export interface CheckDef {
|
|
id: string;
|
|
stage: Stage;
|
|
tier: 1 | 2;
|
|
/** `secrets` and required lanes can never be downgraded to warn. */
|
|
downgradable: boolean;
|
|
/** True when the check executes commands the repo declares (pin files or its test runner). */
|
|
needsRepoCommands: boolean;
|
|
run(ctx: PregateContext): CheckResult | Promise<CheckResult>;
|
|
}
|