mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-17 15:20:11 +02:00
feat(redact): gstack-config keys redact_repo_visibility + redact_prepush_hook
redact_repo_visibility (public|private|unknown) is a LOCAL override for repos gh/glab can't read; it lives in ~/.gstack/config.yaml so it can't weaken the gate repo-wide for other contributors. redact_prepush_hook (true|false) toggles the opt-in pre-push hook. No block_private key — HIGH blocks both visibilities unconditionally. Value-domain validation + 6 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,8 @@ lookup_default() {
|
||||
cross_project_learnings) echo "" ;; # intentionally empty → unset triggers first-time prompt
|
||||
artifacts_sync_mode) echo "off" ;;
|
||||
artifacts_sync_mode_prompted) echo "false" ;;
|
||||
redact_repo_visibility) echo "" ;; # empty → fall through to gh/glab detection
|
||||
redact_prepush_hook) echo "false" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
@@ -143,6 +145,17 @@ case "${1:-}" in
|
||||
echo "Warning: artifacts_sync_mode '$VALUE' not recognized. Valid values: off, artifacts-only, full. Using off." >&2
|
||||
VALUE="off"
|
||||
fi
|
||||
# redact_repo_visibility: a LOCAL override for repos gh/glab can't read (e.g.
|
||||
# self-hosted GitLab). It lives in ~/.gstack/config.yaml (never committed), so
|
||||
# it can't be used to weaken the gate repo-wide for other contributors.
|
||||
if [ "$KEY" = "redact_repo_visibility" ] && [ "$VALUE" != "public" ] && [ "$VALUE" != "private" ] && [ "$VALUE" != "unknown" ]; then
|
||||
echo "Warning: redact_repo_visibility '$VALUE' not recognized. Valid values: public, private, unknown. Using unknown." >&2
|
||||
VALUE="unknown"
|
||||
fi
|
||||
if [ "$KEY" = "redact_prepush_hook" ] && [ "$VALUE" != "true" ] && [ "$VALUE" != "false" ]; then
|
||||
echo "Warning: redact_prepush_hook '$VALUE' not recognized. Valid values: true, false. Using false." >&2
|
||||
VALUE="false"
|
||||
fi
|
||||
mkdir -p "$STATE_DIR"
|
||||
# Write annotated header on first creation
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Config keys for redaction (T12). Verifies gstack-config knows the two new
|
||||
* keys, validates their value domains, and does NOT expose a block_private key
|
||||
* (HIGH blocks both visibilities unconditionally — locked decision).
|
||||
*/
|
||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
|
||||
const CONFIG = path.resolve(import.meta.dir, "..", "bin", "gstack-config");
|
||||
let home: string;
|
||||
|
||||
function cfg(args: string[]): { code: number; out: string; err: string } {
|
||||
const r = spawnSync(CONFIG, args, {
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, GSTACK_HOME: home },
|
||||
});
|
||||
return { code: r.status ?? 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
home = fs.mkdtempSync(path.join(os.tmpdir(), "cfg-"));
|
||||
});
|
||||
afterEach(() => {
|
||||
fs.rmSync(home, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("redact config keys", () => {
|
||||
test("redact_repo_visibility default is empty (falls through to detection)", () => {
|
||||
expect(cfg(["get", "redact_repo_visibility"]).out).toBe("");
|
||||
});
|
||||
test("redact_prepush_hook default is false", () => {
|
||||
expect(cfg(["get", "redact_prepush_hook"]).out).toBe("false");
|
||||
});
|
||||
test("set + get round-trips a valid visibility", () => {
|
||||
cfg(["set", "redact_repo_visibility", "private"]);
|
||||
expect(cfg(["get", "redact_repo_visibility"]).out).toBe("private");
|
||||
});
|
||||
test("invalid visibility is rejected to unknown with a warning", () => {
|
||||
const r = cfg(["set", "redact_repo_visibility", "bogus"]);
|
||||
expect(r.err).toContain("not recognized");
|
||||
expect(cfg(["get", "redact_repo_visibility"]).out).toBe("unknown");
|
||||
});
|
||||
test("invalid prepush flag is rejected to false", () => {
|
||||
cfg(["set", "redact_prepush_hook", "maybe"]);
|
||||
expect(cfg(["get", "redact_prepush_hook"]).out).toBe("false");
|
||||
});
|
||||
test("no block_private key (HIGH blocks both visibilities unconditionally)", () => {
|
||||
// The default for an unknown key is empty string — there is no such key.
|
||||
expect(cfg(["get", "redact_prepush_hook_block_private"]).out).toBe("");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user