fix(pregate): sealed labels the checked commit, not "pushed" (#3565) (#3566)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-10-29 13:56:33 +00:00
1 parent 1bd2330350
commit ad8e12dff7
8 files changed
+35 -6

No files matched your search

+11
View File
@@ -1,5 +1,16 @@
# Changelog
## [1.91.319.0] - 2026-10-29
**`gstack-pregate sealed` no longer prints a line that reads like a push confirmation.** On gstack-evals#29 the lane read `pushed aeb2413` in the `SEALED:` line as the push landing, while the remote stayed at 9036ab1 (#3565). `sealed` never pushes: it checks the commits a push would publish. `pushed <sha>` was its label for the commit whose `sealed` policy it read beside the base branch's.
### Fixed
- **`lib/sealed-guard.ts`**: that label now reads `commit <sha12>` (`policy=origin/main + commit aeb2413f0ca1`), in the `SEALED:` line, in the managed pre-push hook's messages and in the unreadable-policy error.
- **`gstack-pregate --help`** and **docs/pregate.md#sealed** say the command never pushes and that `git ls-remote <remote> <ref>` confirms a push.
### Tests
- `test/sealed-guard.test.ts`: `gstack-pregate sealed` names the checked commit, prints no `pushed`, and leaves the remote's refs exactly as they were. It failed before the fix on `policy=origin/main + pushed <sha>`.
## [1.91.318.0] - 2026-10-29
**/office-hours now has eval tasks for the shared skill eval, and /write-skill's diet locks its tasks before a paid run.** gstack-evals' Lane A will re-check the office-hours diet from #3525. To do that it needs office-hours' tasks in gstack's contract format, a sidecar for what Lane A adds, and a diet step that commits the lock a paid run requires. #3525 never committed the task set GSTA-134 ran, so these tasks rebuild it from the pilot's written summary.
+1 -1
View File
@@ -1 +1 @@
1.91.318.0
1.91.319.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.91.318.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.91.319.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
+2 -1
View File
@@ -57,7 +57,8 @@ const HELP = `gstack-pregate — cheap checks before anything paid or remote (do
validate | init the working-tree ${PREGATE_CONFIG_PATH}
sealed [--range <a..b>] [--remote <name>] [--base <branch>]
the sealed-output guard (docs/pregate.md#sealed) over the commits <remote> lacks, or the range:
run it before any push or PR creation in a repo with a \`sealed\` config; exit 1 refuses (SEALED_OUTPUT_REFUSED)
run it before any push or PR creation in a repo with a \`sealed\` config; exit 1 refuses (SEALED_OUTPUT_REFUSED);
it never pushes: policy=<base> + commit <sha> names where the policy was read
regate --against <gated-head> --bundle <file> [--require <lanes>] [--pr <n>] [--predecessor-pr <n> | --predecessor-ref <ref>]
a fresh tree receipt, each lane's reuse verdict, migration collisions (base branch and open PRs), then
REGATE: needed|not-needed (docs/pregate.md#regate); \`gstack-pregate --against <head>\` is the same verb; exit 1 = needed
+4 -1
View File
@@ -825,7 +825,10 @@ gstack-pregate sealed [--range <a..b>] [--remote origin] [--base <branch>] [--js
`.gstack/ship-policy.json` to that release before landing either key.
- **Policy.** The union of the destination base branch's `sealed` and the
pushed commit's: a push can tighten it, never relax it (`allow_paths` come
from the base branch alone, and a pushed deny glob cannot override one). A malformed or
from the base branch alone, and a pushed deny glob cannot override one). The
`SEALED:` line names both as `policy=<remote>/<base> + commit <sha12>`.
`gstack-pregate sealed` only checks and never pushes, so confirm a push with
`git ls-remote <remote> <ref>`. A malformed or
unreadable policy refuses. Waivers come only from the base branch's
`.gstack/sealed-allow.tsv` (`rule<TAB>path<TAB>sha<TAB>reason`) and are
listed on every push.
+1 -1
View File
@@ -251,7 +251,7 @@ export function evaluateSealed(o: EvaluateOptions): SealedReport {
let policy: SealedPolicy | null = base.policy;
const labels = base.policy ? [base.label] : [];
for (const t of o.targets) {
const pushed = sealedPolicyAt(o.repoRoot, t.pushedSha, `pushed ${t.pushedSha.slice(0, 12)}`);
const pushed = sealedPolicyAt(o.repoRoot, t.pushedSha, `commit ${t.pushedSha.slice(0, 12)}`);
if ('error' in pushed) return { ...blank, status: 'error', error: `the sealed policy in ${pushed.label} is unreadable: ${pushed.error}` };
if (pushed.policy) labels.push(pushed.label);
policy = unionPolicies(policy, pushed.policy);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.91.318",
"version": "1.91.319",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
+14
View File
@@ -358,3 +358,17 @@ describe('visibility (cache under the state root; unknown counts as public)', ()
} finally { fs.rmSync(state, { recursive: true, force: true }); }
});
});
describe('gstack-pregate sealed is a check, not a push (#3565)', () => {
test('the SEALED line names the checked commit, never says pushed, and the remote ref does not move', () => {
const s = scratch({ '.gstack/pregate.json': policy(SEALED) });
const head = commit(s, { 'notes.md': 'ok\n' });
const before = sh(s.work, s.env, ['ls-remote', 'origin']);
const r = spawnSync('bun', [PREGATE, 'sealed'], { cwd: s.work, encoding: 'utf8', timeout: 60_000, env: { ...process.env, ...s.env } });
expect(r.status).toBe(0);
expect(r.stdout).toContain(`policy=origin/main + commit ${head.slice(0, 12)}`);
expect(`${r.stdout}${r.stderr}`).not.toMatch(/\bpushed\b/);
expect(sh(s.work, s.env, ['ls-remote', 'origin'])).toBe(before);
expect(before).not.toContain('refs/heads/results');
});
});