karajan-code 4.65.1 → 4.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kj-hook.js +13 -0
- package/package.json +2 -1
- package/scripts/esbuild-sea.config.mjs +5 -1
- package/src/checks/guarantee-level.js +5 -2
- package/src/cli/register-meta.js +13 -5
- package/src/harden/advisory.js +6 -3
- package/src/harden/harness-hooks.js +86 -37
- package/src/harden/hook-templates.js +5 -0
- package/src/harden/sentinel/posttooluse.mjs +211 -0
- package/src/harden/sentinel/pretooluse-sentinel.mjs +731 -0
- package/src/harden/sentinel/pretooluse.mjs +19 -0
- package/src/harden/sentinel/sentinel-lib.mjs +114 -0
- package/src/harden/sentinel/sessionstart.mjs +11 -1
- package/src/harden/sentinel/stop.mjs +116 -0
- package/src/harden/sentinel-guards.js +28 -0
- package/src/harden/sentinel-hooks.js +63 -1238
- package/src/harden/sign-changes.js +4 -1
- package/src/harden/supervisor-commit.js +15 -5
- package/src/policy/supervisor-verify.js +6 -2
|
@@ -61,7 +61,10 @@ const cut = (text, limit) => {
|
|
|
61
61
|
*/
|
|
62
62
|
export function describeSupervisorChanges({ projectDir, files, gitFn = defaultGit(projectDir), scan = scanText }) {
|
|
63
63
|
let budget = DIFF_LIMIT_TOTAL;
|
|
64
|
-
return files.map(({ file, deleted }) => {
|
|
64
|
+
return files.map(({ file, deleted, local }) => {
|
|
65
|
+
// KJC-TSK-1207: a machine-local file (ignored host settings) is sealed by its hash
|
|
66
|
+
// alone; its content never leaves the machine, whatever the scan would say of it.
|
|
67
|
+
if (local === true) return { file, status: "local", added: 0, removed: 0, summary: "machine-local, sealed by its hash", diff: "[content not sent: machine-local file, sealed by its hash only]", truncated: false };
|
|
65
68
|
const tracked = inHead(gitFn, file);
|
|
66
69
|
const present = !deleted && existsSync(join(projectDir, file));
|
|
67
70
|
let status = "modified";
|
|
@@ -33,8 +33,12 @@ export const GOVERNANCE_FILES = [".karajan/policy.yml", ".karajan/rules.yml"];
|
|
|
33
33
|
// KJC-BUG-0400: the CI workflows are sealed the same way, by hash: a PR could
|
|
34
34
|
// otherwise rewrite the check that judges it and pass with a forged seal.
|
|
35
35
|
const WORKFLOW_FILE = /^\.github\/workflows\/[^/]+\.ya?ml$/;
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// KJC-TSK-1207 (ADR 0030): with the guards run from the installed kj, the host
|
|
37
|
+
// settings decide which guard runs. Committed where the project versions them;
|
|
38
|
+
// where it ignores .claude/ (machine-local, like the guards) only their hash travels.
|
|
39
|
+
export const HOST_SETTINGS = ".claude/settings.json";
|
|
40
|
+
/** A file the seal covers by its hash alone: the governance config, every CI workflow and the host settings. */
|
|
41
|
+
export const isSealedConfig = (file) => GOVERNANCE_FILES.includes(file) || WORKFLOW_FILE.test(file) || file === HOST_SETTINGS;
|
|
38
42
|
|
|
39
43
|
const sha256 = (abs) => createHash("sha256").update(readFileSync(abs)).digest("hex");
|
|
40
44
|
|
|
@@ -97,9 +101,14 @@ export async function commitSupervisorRegeneration({
|
|
|
97
101
|
// Capa 4 (test adversarial 6-sep: un huérfano a init con pty falso y
|
|
98
102
|
// prompts a ciegas llegó hasta aquí): nonce aleatorio tecleado de vuelta.
|
|
99
103
|
seal.confirm(deps.confirm);
|
|
100
|
-
const tracked = new Set(run(["ls-files", "--", ...GOVERNANCE_FILES, ".github/workflows"]).split("\n").filter(Boolean));
|
|
104
|
+
const tracked = new Set(run(["ls-files", "--", ...GOVERNANCE_FILES, HOST_SETTINGS, ".github/workflows"]).split("\n").filter(Boolean));
|
|
105
|
+
// Local only when git ignores it; untracked but not ignored, it is committed with the seal.
|
|
106
|
+
const gitIgnores = (file) => { try { run(["check-ignore", "-q", "--", file]); return true; } catch { return false; } };
|
|
107
|
+
const settingsHere = !tracked.has(HOST_SETTINGS) && existsSync(join(projectDir, HOST_SETTINGS));
|
|
108
|
+
const localSettings = settingsHere && gitIgnores(HOST_SETTINGS) ? [HOST_SETTINGS] : [];
|
|
109
|
+
const newSettings = settingsHere && localSettings.length === 0 ? [HOST_SETTINGS] : [];
|
|
101
110
|
const onDisk = (() => { try { return readdirSync(join(projectDir, ".github", "workflows")).map((f) => `.github/workflows/${f}`); } catch { return []; } })();
|
|
102
|
-
const candidates = [...new Set([...GOVERNANCE_FILES, ...onDisk, ...tracked])].filter(isSealedConfig).sort();
|
|
111
|
+
const candidates = [...new Set([...GOVERNANCE_FILES, ...onDisk, ...tracked, ...newSettings])].filter(isSealedConfig).sort();
|
|
103
112
|
const governance = candidates.filter((f) => existsSync(join(projectDir, f)) || tracked.has(f));
|
|
104
113
|
const governanceDrift = governance.length === 0 ? [] : run(["status", "--porcelain", "--", ...governance]).split("\n").map((l) => l.slice(3).trim()).filter(Boolean);
|
|
105
114
|
const drift = [...supervisorDrift({ projectDir, gitFn: run }), ...governanceDrift];
|
|
@@ -110,6 +119,7 @@ export async function commitSupervisorRegeneration({
|
|
|
110
119
|
...readdirSync(join(projectDir, HOOKS_PREFIX)).map((f) => HOOKS_PREFIX + f),
|
|
111
120
|
...harnessGuards(projectDir),
|
|
112
121
|
...governance.filter((f) => existsSync(join(projectDir, f))),
|
|
122
|
+
...localSettings,
|
|
113
123
|
].sort();
|
|
114
124
|
// KJC-BUG-0197: un guardia BORRADO se anota como borrado, igual que un hook.
|
|
115
125
|
// No hacía falta para desencadenar el re-sello (la lista encoge, así que la
|
|
@@ -124,7 +134,7 @@ export async function commitSupervisorRegeneration({
|
|
|
124
134
|
.map((f) => f?.file)
|
|
125
135
|
.filter((f) => typeof f === "string" && f.startsWith(HARNESS_PREFIX) && !existsSync(join(projectDir, f)));
|
|
126
136
|
const hashed = [
|
|
127
|
-
...current.map((file) => ({ file, sha256: sha256(join(projectDir, file)) })),
|
|
137
|
+
...current.map((file) => ({ file, sha256: sha256(join(projectDir, file)), ...(localSettings.includes(file) ? { local: true } : {}) })),
|
|
128
138
|
...drift.filter((f) => !existsSync(join(projectDir, f))).map((file) => ({ file, deleted: true })),
|
|
129
139
|
...goneGuards.map((file) => ({ file, deleted: true })),
|
|
130
140
|
];
|
|
@@ -11,7 +11,7 @@ import { resolve, sep } from "node:path";
|
|
|
11
11
|
import { renderCanonicalHook } from "../harden/harden-engine.js";
|
|
12
12
|
import { provenanceSignature } from "../harden/phone-sign.js";
|
|
13
13
|
import { canonicalHarnessBody } from "../harden/sentinel-hooks.js";
|
|
14
|
-
import { isSealedConfig, PROVENANCE_FILE } from "../harden/supervisor-commit.js";
|
|
14
|
+
import { HOST_SETTINGS, isSealedConfig, PROVENANCE_FILE } from "../harden/supervisor-commit.js";
|
|
15
15
|
|
|
16
16
|
const HOOKS_PREFIX = ".karajan/hooks/";
|
|
17
17
|
const HARNESS_PREFIX = ".karajan/harness/";
|
|
@@ -73,7 +73,11 @@ export function verifiedSupervisorFiles({ projectDir, trackedOnly = false }) {
|
|
|
73
73
|
const abs = resolve(projectDir, entry.file);
|
|
74
74
|
let st = null;
|
|
75
75
|
try { st = lstatSync(abs); } catch { /* absent */ }
|
|
76
|
-
|
|
76
|
+
// KJC-TSK-1207: host settings sealed as ignored by git (local) live only where
|
|
77
|
+
// they were sealed; absent from this checkout they cannot be judged here (CI,
|
|
78
|
+
// another clone). Versioned ones travel with the repo, so absent they fail.
|
|
79
|
+
if (st === null && entry.local === true && entry.file === HOST_SETTINGS) unjudgeable.add(entry.file);
|
|
80
|
+
else if (entry.deleted === true ? st === null : st?.isFile() && sha256(readFileSync(abs)) === entry.sha256) ok.add(entry.file);
|
|
77
81
|
continue;
|
|
78
82
|
}
|
|
79
83
|
const kind = ROOTS.find(([prefix]) => entry.file.startsWith(prefix));
|