karajan-code 4.25.0 → 4.27.0
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/package.json +1 -1
- package/packages/hu-board/public/app.js +37 -43
- package/packages/hu-board/public/index.html +15 -11
- package/packages/hu-board/public/styles.css +29 -14
- package/packages/hu-board/public/utils/graph-view.js +1 -1
- package/packages/hu-board/public/utils/init-listeners.js +1 -14
- package/packages/hu-board/public/utils/maggle-mode.js +10 -4
- package/packages/hu-board/public/utils/project-actions.js +4 -5
- package/packages/hu-board/public/utils/project-picker-view.js +1 -1
- package/packages/hu-board/src/db.js +13 -0
- package/packages/hu-board/src/sync.js +6 -1
- package/src/cli/register-meta.js +4 -0
- package/src/commands/harden.js +22 -0
- package/src/commands/policy.js +26 -8
- package/src/commands/review-gate.js +20 -5
- package/src/harden/harden-engine.js +21 -1
- package/src/harden/hook-templates.js +12 -2
- package/src/harden/sentinel-hooks.js +46 -31
- package/src/harden/supervisor-commit.js +147 -0
- package/src/harden/workflow-templates.js +3 -1
- package/src/policy/engine.js +3 -1
- package/src/policy/exceptions.js +49 -13
- package/src/policy/supervisor-verify.js +79 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-BUG-0161 / ADR 0009, pieza 3 — exención ESTRUCTURAL del supervisor:
|
|
3
|
+
* un fichero de .karajan/hooks solo deja de ser violación si su contenido
|
|
4
|
+
* coincide (sha256) con la provenance sellada Y con el render canónico
|
|
5
|
+
* recomputado. No confía: recomputa. Una coma manual sigue denegada.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
9
|
+
import { resolve, sep } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { renderCanonicalHook } from "../harden/harden-engine.js";
|
|
12
|
+
import { PROVENANCE_FILE } from "../harden/supervisor-commit.js";
|
|
13
|
+
|
|
14
|
+
const HOOKS_PREFIX = ".karajan/hooks/";
|
|
15
|
+
// globalHooksDir es el único texto libre que se interpola en el render:
|
|
16
|
+
// vocabulario cerrado de ruta o la provenance no verifica NADA.
|
|
17
|
+
const SAFE_DIR = /^(\$HOME)?(\/[\w.@%+~-]+)+$/;
|
|
18
|
+
|
|
19
|
+
const sha256 = (buf) => createHash("sha256").update(buf).digest("hex");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @returns {{files: Set<string>, reason?: string}} rutas repo-relativas de
|
|
23
|
+
* supervisor cuyo estado actual está respaldado por la provenance sellada.
|
|
24
|
+
*/
|
|
25
|
+
export function verifiedSupervisorFiles({ projectDir }) {
|
|
26
|
+
let prov;
|
|
27
|
+
try {
|
|
28
|
+
prov = JSON.parse(readFileSync(resolve(projectDir, PROVENANCE_FILE), "utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return { files: new Set(), reason: "sin provenance" };
|
|
31
|
+
}
|
|
32
|
+
const gen = prov?.generation ?? {};
|
|
33
|
+
if (gen.globalHooksDir != null && !SAFE_DIR.test(gen.globalHooksDir)) {
|
|
34
|
+
return { files: new Set(), reason: "globalHooksDir no verificable en la provenance" };
|
|
35
|
+
}
|
|
36
|
+
const ok = new Set();
|
|
37
|
+
const entries = Array.isArray(prov?.files) ? prov.files : [];
|
|
38
|
+
const hooksRoot = resolve(projectDir, HOOKS_PREFIX);
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (typeof entry?.file !== "string" || !entry.file.startsWith(HOOKS_PREFIX)) continue;
|
|
41
|
+
// Contención por resolución, no por prefijo textual (catch de codex).
|
|
42
|
+
const abs = resolve(projectDir, entry.file);
|
|
43
|
+
if (!abs.startsWith(hooksRoot + sep)) continue;
|
|
44
|
+
let st = null;
|
|
45
|
+
try { st = lstatSync(abs); } catch { /* no hay objeto en la ruta */ }
|
|
46
|
+
if (entry.deleted === true) {
|
|
47
|
+
if (st === null) ok.add(entry.file);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (typeof entry.sha256 !== "string" || !st?.isFile()) continue;
|
|
51
|
+
if (sha256(readFileSync(abs)) !== entry.sha256) continue;
|
|
52
|
+
let canonical;
|
|
53
|
+
try {
|
|
54
|
+
canonical = renderCanonicalHook(entry.file.slice(HOOKS_PREFIX.length), gen);
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (sha256(Buffer.from(canonical, "utf8")) === entry.sha256) ok.add(entry.file);
|
|
59
|
+
}
|
|
60
|
+
// `complete` = CADA entrada verificó: solo entonces la provenance se
|
|
61
|
+
// describe con honestidad y su propio diff puede alzarse.
|
|
62
|
+
const complete = entries.length > 0 && entries.every((e) => typeof e?.file === "string" && ok.has(e.file));
|
|
63
|
+
return { files: ok, complete };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Filtra violaciones de supervisor respaldadas — {violations, lifted}. */
|
|
67
|
+
export function liftSealedSupervisorViolations({ projectDir, violations }) {
|
|
68
|
+
const RULE = "defaults.supervisor.write";
|
|
69
|
+
if (!violations.some((v) => v.rule_id === RULE && v.file)) return { violations, lifted: 0 };
|
|
70
|
+
const sealed = verifiedSupervisorFiles({ projectDir });
|
|
71
|
+
// La violación sobre la PROPIA provenance solo se alza si la provenance
|
|
72
|
+
// entera verificó — a medias no describe la verdad y sigue denegada.
|
|
73
|
+
const liftable = (v) =>
|
|
74
|
+
v.rule_id === RULE
|
|
75
|
+
&& v.file
|
|
76
|
+
&& (sealed.files.has(v.file) || (v.file === PROVENANCE_FILE && sealed.complete === true));
|
|
77
|
+
const kept = violations.filter((v) => !liftable(v));
|
|
78
|
+
return { violations: kept, lifted: violations.length - kept.length };
|
|
79
|
+
}
|