mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-21 04:10:47 +02:00
harden clean-host generation and shutdown
This commit is contained in:
@@ -30,6 +30,13 @@ describe("GStack 2 CI supply-chain and browser smoke", () => {
|
||||
for (const reference of actionRefs) expect(reference).toMatch(/^[0-9a-f]{40}$/);
|
||||
});
|
||||
|
||||
test("proves clean-checkout generation before the native GStack 2 gate", () => {
|
||||
const cleanProbe = workflow.indexOf("run: bun run verify:gstack2-clean-generation");
|
||||
const nativeGate = workflow.indexOf("run: bun run test:gstack2");
|
||||
expect(cleanProbe).toBeGreaterThan(-1);
|
||||
expect(nativeGate).toBeGreaterThan(cleanProbe);
|
||||
});
|
||||
|
||||
test("mounts the checkout read-only for every development-container run", () => {
|
||||
const workspaceMounts = [...workflow.matchAll(/--volume "\$\{\{ github\.workspace \}\}:([^"]+)"/g)]
|
||||
.map((match) => match[1]);
|
||||
@@ -133,6 +140,22 @@ touch node_modules/container-only
|
||||
expect(smoke).toContain('"$HOME_DIR/bin/browse" screenshot "$ROOT/runtime-full.png"');
|
||||
});
|
||||
|
||||
test("runs installed runtime probes from the disposable copy without weakening Git trust", () => {
|
||||
expect(smoke).toContain('cp -R "$SOURCE/." "$REPO/"');
|
||||
expect(smoke).not.toContain('cp -a "$SOURCE/." "$REPO/"');
|
||||
expect(smoke).toContain(`(
|
||||
# Exercise project identity against the disposable, container-owned copy.
|
||||
# The workflow checkout is a read-only host bind mount whose ownership is
|
||||
# intentionally not trusted by Git inside the container.
|
||||
cd "$REPO"
|
||||
"$HOME_DIR/bin/gstack" setup
|
||||
"$HOME_DIR/bin/gstack" doctor --json
|
||||
"$HOME_DIR/bin/gstack" --version
|
||||
)`);
|
||||
expect(smoke).not.toMatch(/git config[^\n]*safe\.directory/);
|
||||
expect(smoke).not.toContain("GIT_CONFIG_COUNT");
|
||||
});
|
||||
|
||||
test("keeps cloud-browser and local-model packages outside the production runtime", () => {
|
||||
const productionDependencies = Object.keys(packageJson.dependencies ?? {});
|
||||
for (const forbidden of [
|
||||
|
||||
@@ -122,6 +122,57 @@ describe('GStack 2 raw-prompt Codex host adversarial harness', () => {
|
||||
expect(JSON.stringify(FINAL_OUTPUT_SCHEMA)).not.toContain('qa-report-only-untrusted-log');
|
||||
});
|
||||
|
||||
test('teaches every installed skill to keep read-only inspection commands auditable', () => {
|
||||
for (const skill of PUBLIC_SKILLS) {
|
||||
const policy = fs.readFileSync(
|
||||
path.join(REPOSITORY_ROOT, 'skills', skill, 'references', 'AUTHORITY-POLICY.md'),
|
||||
'utf8',
|
||||
);
|
||||
expect(policy).toContain('run one inspection command per tool call');
|
||||
expect(policy).toContain('Do not join separate commands with `&&`, `||`, `;`');
|
||||
}
|
||||
});
|
||||
|
||||
test('compound read-only inspections remain a behavioral failure', () => {
|
||||
const fixture = loadFixtures().find((entry) => entry.skill === 'review')!;
|
||||
const command = "/bin/zsh -lc 'git branch --show-current && git status --short -- src/session.ts && git diff -- src/session.ts'";
|
||||
const events = parseHostEventLines([
|
||||
JSON.stringify({
|
||||
type: 'item.completed',
|
||||
item: {
|
||||
id: 'compound-inspection',
|
||||
type: 'command_execution',
|
||||
command,
|
||||
status: 'completed',
|
||||
exit_code: 0,
|
||||
aggregated_output: 'warning: write cache: operation not permitted',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const root = temporaryRoot('gstack-host-compound-inspection-');
|
||||
fs.writeFileSync(path.join(root, 'stable.txt'), 'stable');
|
||||
const snapshot = snapshotTree(root);
|
||||
const assessment = assessFixture({
|
||||
fixture,
|
||||
exitCode: 0,
|
||||
timedOut: false,
|
||||
events,
|
||||
structured: structured({
|
||||
route: { ...structured().route, skill: 'review', mode: 'Normal', mutation: 'report-only' },
|
||||
}),
|
||||
structuredError: null,
|
||||
before: snapshot,
|
||||
after: snapshot,
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
expect(isPureReadOnlyGitInspection(command)).toBe(false);
|
||||
expect(assessment.forbidden_command_attempts).toEqual([command]);
|
||||
expect(assessment.assertions.find((entry) => entry.name === 'no-forbidden-command-attempts')?.passed)
|
||||
.toBe(false);
|
||||
expect(assessment.passed).toBe(false);
|
||||
});
|
||||
|
||||
test('copies complete canonical directories and only the six public skills', () => {
|
||||
const root = temporaryRoot('gstack-host-copy-');
|
||||
const canonicalRoot = path.join(REPOSITORY_ROOT, 'skills');
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
ensureRuntimePayloads,
|
||||
REQUIRED_RUNTIME_PAYLOADS,
|
||||
type RuntimePayloadEntry,
|
||||
} from '../scripts/gstack2/ensure-runtime-payloads';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const temporaryRoots: string[] = [];
|
||||
|
||||
async function temporaryRoot(): Promise<string> {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'gstack2-runtime-payloads-'));
|
||||
temporaryRoots.push(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
async function writePayloads(root: string, entries: readonly RuntimePayloadEntry[]): Promise<void> {
|
||||
for (const entry of entries) {
|
||||
const target = path.join(root, entry.path);
|
||||
await fs.mkdir(path.dirname(target), { recursive: true });
|
||||
await fs.writeFile(target, 'fixture payload\n');
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryRoots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
describe('GStack 2 generated runtime payload prerequisites', () => {
|
||||
test('builds all absent parity payloads once and verifies the result', async () => {
|
||||
const root = await temporaryRoot();
|
||||
const calls: RuntimePayloadEntry[][] = [];
|
||||
|
||||
const result = await ensureRuntimePayloads({
|
||||
sourceDir: root,
|
||||
builder: async ({ missing }) => {
|
||||
calls.push([...missing]);
|
||||
await writePayloads(root, missing);
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.built).toBe(true);
|
||||
expect(calls).toHaveLength(1);
|
||||
expect(calls[0].map((entry) => entry.path)).toEqual(REQUIRED_RUNTIME_PAYLOADS.map((entry) => entry.path));
|
||||
expect(new Set(calls[0].map((entry) => entry.build))).toEqual(new Set(['core']));
|
||||
});
|
||||
|
||||
test('does not rebuild payloads that already exist', async () => {
|
||||
const root = await temporaryRoot();
|
||||
await writePayloads(root, REQUIRED_RUNTIME_PAYLOADS);
|
||||
|
||||
const result = await ensureRuntimePayloads({
|
||||
sourceDir: root,
|
||||
builder: async () => { throw new Error('complete payloads must not rebuild'); },
|
||||
});
|
||||
|
||||
expect(result.built).toBe(false);
|
||||
});
|
||||
|
||||
test('fails when the builder leaves a required payload absent', async () => {
|
||||
const root = await temporaryRoot();
|
||||
|
||||
await expect(ensureRuntimePayloads({
|
||||
sourceDir: root,
|
||||
builder: async ({ missing }) => writePayloads(root, missing.slice(0, -1)),
|
||||
})).rejects.toThrow(`Runtime payload build did not produce: ${REQUIRED_RUNTIME_PAYLOADS.at(-1)?.path}`);
|
||||
});
|
||||
|
||||
test('canonical generation and parity commands prepare payloads before use', async () => {
|
||||
const pkg = JSON.parse(await fs.readFile(path.join(ROOT, 'package.json'), 'utf8')) as {
|
||||
scripts: Record<string, string>;
|
||||
};
|
||||
const buildScript = await fs.readFile(path.join(ROOT, 'scripts', 'build.sh'), 'utf8');
|
||||
const cleanVerifier = await fs.readFile(
|
||||
path.join(ROOT, 'scripts', 'gstack2', 'verify-clean-generation.ts'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
expect(pkg.scripts['gen:gstack2']).toStartWith('bun run ensure:gstack2-runtime');
|
||||
expect(pkg.scripts['test:gstack2:parity']).toStartWith('bun run ensure:gstack2-runtime');
|
||||
expect(pkg.scripts['verify:gstack2-clean-generation'])
|
||||
.toBe('bun run scripts/gstack2/verify-clean-generation.ts');
|
||||
expect(cleanVerifier).toContain("spawnSync(process.execPath, ['run', 'gen:gstack2']");
|
||||
expect(cleanVerifier).toContain('Clean-generation probe requires absent runtime payloads');
|
||||
expect(buildScript.indexOf('build --compile browse/src/cli.ts')).toBeLessThan(
|
||||
buildScript.indexOf('run gen:gstack2'),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,20 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { blobShaForPath, pinnedRevisionPath } from '../scripts/gstack2/render-legacy';
|
||||
import { runParity } from '../scripts/gstack2/run-parity';
|
||||
|
||||
const ROOT = join(import.meta.dir, '..');
|
||||
|
||||
describe('GStack 2 skill parity', () => {
|
||||
test('normalizes Windows-style repository paths for pinned Git lookups', () => {
|
||||
const windowsPath = String.raw`cso\SKILL.md.tmpl`;
|
||||
expect(pinnedRevisionPath(windowsPath))
|
||||
.toBe(pinnedRevisionPath('cso/SKILL.md.tmpl'));
|
||||
expect(blobShaForPath(windowsPath))
|
||||
.toBe(blobShaForPath('cso/SKILL.md.tmpl'));
|
||||
});
|
||||
|
||||
test('preserves the pinned specialist corpus and generated evidence', () => {
|
||||
const result = runParity();
|
||||
expect(result.sources).toBe(55);
|
||||
|
||||
Reference in New Issue
Block a user