@lannguyensi/harness 0.44.0 → 0.46.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/CHANGELOG.md +1245 -0
- package/dist/cli/adopt/derive.d.ts +16 -7
- package/dist/cli/adopt/derive.js +19 -2
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.js +18 -3
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/apply.d.ts +1 -0
- package/dist/cli/apply/apply.js +62 -6
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +5 -6
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
- package/dist/cli/apply/generate-opencode-config.js +210 -0
- package/dist/cli/apply/generate-opencode-config.js.map +1 -0
- package/dist/cli/apply/generate-settings.d.ts +90 -32
- package/dist/cli/apply/generate-settings.js +133 -10
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/apply/index.d.ts +2 -1
- package/dist/cli/apply/index.js +2 -1
- package/dist/cli/apply/index.js.map +1 -1
- package/dist/cli/apply/next-steps.d.ts +20 -0
- package/dist/cli/apply/next-steps.js +60 -0
- package/dist/cli/apply/next-steps.js.map +1 -1
- package/dist/cli/approve/stdin-report.d.ts +15 -0
- package/dist/cli/approve/stdin-report.js +1 -1
- package/dist/cli/approve/stdin-report.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +75 -0
- package/dist/cli/approve/understanding.js +150 -2
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/audit.d.ts +1 -1
- package/dist/cli/audit.js +3 -2
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +19 -1
- package/dist/cli/doctor/claude-mcp.js +36 -10
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex.d.ts +3 -1
- package/dist/cli/doctor/codex.js +6 -9
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +94 -8
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +23 -0
- package/dist/cli/doctor/index.js +106 -2
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/opencode.d.ts +24 -0
- package/dist/cli/doctor/opencode.js +107 -0
- package/dist/cli/doctor/opencode.js.map +1 -0
- package/dist/cli/doctor/target-checks.d.ts +11 -0
- package/dist/cli/doctor/target-checks.js +25 -0
- package/dist/cli/doctor/target-checks.js.map +1 -0
- package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
- package/dist/cli/doctor/toolchain-parity.js +154 -0
- package/dist/cli/doctor/toolchain-parity.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +85 -1
- package/dist/cli/doctor/types.js +1 -1
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
- package/dist/cli/doctor/understanding-mode-env.js +83 -0
- package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
- package/dist/cli/dry-run.js +32 -17
- package/dist/cli/dry-run.js.map +1 -1
- package/dist/cli/explain.d.ts +1 -1
- package/dist/cli/explain.js +1 -1
- package/dist/cli/explain.js.map +1 -1
- package/dist/cli/index.js +64 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.js +36 -13
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +7 -4
- package/dist/cli/init/interactive.js +79 -27
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +32 -1
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +2 -1
- package/dist/cli/init/templates.js +130 -20
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/approve-escape.js +69 -8
- package/dist/cli/pack/approve-escape.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.js +1 -1
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-post-merge-gate.js +1 -1
- package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +91 -4
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance.js +19 -2
- package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
- package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
- package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
- package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
- package/dist/cli/pause/index.js +1 -1
- package/dist/cli/pause/index.js.map +1 -1
- package/dist/cli/policy/intercept.d.ts +148 -0
- package/dist/cli/policy/intercept.js +379 -48
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/cli/session-start/index.d.ts +12 -3
- package/dist/cli/session-start/index.js +67 -11
- package/dist/cli/session-start/index.js.map +1 -1
- package/dist/cli/session-start/stale-base-check.d.ts +71 -0
- package/dist/cli/session-start/stale-base-check.js +379 -0
- package/dist/cli/session-start/stale-base-check.js.map +1 -0
- package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
- package/dist/cli/session-start/toolchain-parity.js +244 -36
- package/dist/cli/session-start/toolchain-parity.js.map +1 -1
- package/dist/cli/smoke/assertions.d.ts +9 -2
- package/dist/cli/smoke/assertions.js +9 -2
- package/dist/cli/smoke/assertions.js.map +1 -1
- package/dist/cli/uninstall/index.js +18 -2
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +3 -1
- package/dist/cli/validate/checks.js +298 -3
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +122 -1
- package/dist/io/claude-mcp.js +214 -12
- package/dist/io/claude-mcp.js.map +1 -1
- package/dist/io/duration.js.map +1 -0
- package/dist/io/expand-home.js.map +1 -0
- package/dist/io/extract.js.map +1 -0
- package/dist/io/harness-lock.js +1 -1
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/{runtime → io}/ledger-record.d.ts +1 -1
- package/dist/io/ledger-record.js.map +1 -0
- package/dist/policies/index.d.ts +2 -2
- package/dist/policies/index.js +2 -2
- package/dist/policies/index.js.map +1 -1
- package/dist/policies/ledger-client.js +10 -6
- package/dist/policies/ledger-client.js.map +1 -1
- package/dist/policies/producers.js +1 -1
- package/dist/policies/producers.js.map +1 -1
- package/dist/policies/requires.js +2 -2
- package/dist/policies/requires.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.js +28 -3
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate.js +45 -9
- package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +139 -8
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +246 -12
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
- package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
- package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
- package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
- package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +11 -0
- package/dist/policy-packs/registry.js +1 -1
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/runtime.d.ts +1 -1
- package/dist/policy-packs/runtime.js +10 -1
- package/dist/policy-packs/runtime.js.map +1 -1
- package/dist/runtime/agent-facing.js +1 -1
- package/dist/runtime/agent-facing.js.map +1 -1
- package/dist/runtime/bash-match-facts.d.ts +9 -7
- package/dist/runtime/bash-match-facts.js +9 -7
- package/dist/runtime/bash-match-facts.js.map +1 -1
- package/dist/runtime/bash-match-registry.d.ts +75 -38
- package/dist/runtime/bash-match-registry.js +279 -232
- package/dist/runtime/bash-match-registry.js.map +1 -1
- package/dist/runtime/bash-prefix-parse.d.ts +7 -0
- package/dist/runtime/bash-prefix-parse.js +205 -17
- package/dist/runtime/bash-prefix-parse.js.map +1 -1
- package/dist/runtime/command-normalize.d.ts +112 -0
- package/dist/runtime/command-normalize.js +411 -38
- package/dist/runtime/command-normalize.js.map +1 -1
- package/dist/runtime/hermetic-spawn-guard.js +6 -0
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +2 -2
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/intercept.d.ts +78 -3
- package/dist/runtime/intercept.js +151 -39
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/ledger-add.js +1 -1
- package/dist/runtime/ledger-add.js.map +1 -1
- package/dist/runtime/read-only-bash.js +303 -24
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/schema/doctor.d.ts +25 -0
- package/dist/schema/doctor.js +23 -0
- package/dist/schema/doctor.js.map +1 -0
- package/dist/schema/extract.js +1 -1
- package/dist/schema/extract.js.map +1 -1
- package/dist/schema/index.d.ts +76 -0
- package/dist/schema/index.js +9 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/requires.js +2 -2
- package/dist/schema/requires.js.map +1 -1
- package/dist/schema/risk.d.ts +7 -0
- package/dist/schema/risk.js +23 -0
- package/dist/schema/risk.js.map +1 -1
- package/dist/schema/stale-base-check.d.ts +18 -0
- package/dist/schema/stale-base-check.js +37 -0
- package/dist/schema/stale-base-check.js.map +1 -0
- package/dist/schema/toolchain-parity.d.ts +3 -0
- package/dist/schema/toolchain-parity.js +13 -0
- package/dist/schema/toolchain-parity.js.map +1 -1
- package/package.json +3 -2
- package/dist/policies/duration.js.map +0 -1
- package/dist/policies/extract.js.map +0 -1
- package/dist/runtime/expand-home.js.map +0 -1
- package/dist/runtime/ledger-record.js.map +0 -1
- /package/dist/{policies → io}/duration.d.ts +0 -0
- /package/dist/{policies → io}/duration.js +0 -0
- /package/dist/{runtime → io}/expand-home.d.ts +0 -0
- /package/dist/{runtime → io}/expand-home.js +0 -0
- /package/dist/{policies → io}/extract.d.ts +0 -0
- /package/dist/{policies → io}/extract.js +0 -0
- /package/dist/{runtime → io}/ledger-record.js +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Session-scoped approval marker file (filesystem source of the
|
|
2
|
+
// two-source approval check), split out of the former monolithic
|
|
3
|
+
// understanding-before-execution-runtime.ts (structural concentration
|
|
4
|
+
// slice 2, agent-tasks 348a4d42). Pure move: see
|
|
5
|
+
// src/policy-packs/builtin/understanding-before-execution/index.ts for
|
|
6
|
+
// the re-exported public surface.
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { atomicWriteFile } from "../../../io/atomic-write.js";
|
|
10
|
+
import { readRegularFileRejectingSymlink } from "../../../io/read-regular-file.js";
|
|
11
|
+
import { signMarker, verifyMarkerSignature } from "../../../runtime/approval-signing.js";
|
|
12
|
+
import { rejectMalformedSessionId } from "../../../runtime/reject-malformed-session-id.js";
|
|
13
|
+
import { safeJsonParse } from "./persisted-reports.js";
|
|
14
|
+
export const APPROVAL_MARKER_DIRNAME = ".approvals";
|
|
15
|
+
/** Filesystem path of the per-session approval marker. */
|
|
16
|
+
export function approvalMarkerPathFor(generatedDir, sessionId) {
|
|
17
|
+
rejectMalformedSessionId(sessionId);
|
|
18
|
+
return path.join(generatedDir, APPROVAL_MARKER_DIRNAME, sessionId);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Operator-side: write the marker file the gate consults. Atomic so a
|
|
22
|
+
* crash mid-write cannot leave a half-empty file the gate would accept
|
|
23
|
+
* as approved. Caller is `harness approve understanding`, which the
|
|
24
|
+
* operator runs from their un-hooked shell; if the agent could call
|
|
25
|
+
* this path the gate's value would collapse, so it lives behind the
|
|
26
|
+
* approve CLI rather than as a generally importable verb.
|
|
27
|
+
*
|
|
28
|
+
* The marker is HMAC-signed (harness/f9485cc7) over (sessionId,
|
|
29
|
+
* approvedAt, approvedBy, reportContentHash) using an operator-side key
|
|
30
|
+
* lazily generated at `<generatedDir>/.approval-signing.key` — see
|
|
31
|
+
* `src/runtime/approval-signing.ts` for the key-management contract and
|
|
32
|
+
* the honest trust model. Mode 0600 on the marker file itself, best-
|
|
33
|
+
* effort (matches the signing key's own permission convention); this is
|
|
34
|
+
* defense-in-depth alongside the signature, not a substitute for it.
|
|
35
|
+
*/
|
|
36
|
+
export function writeApprovalMarker(generatedDir, sessionId, marker) {
|
|
37
|
+
const filePath = approvalMarkerPathFor(generatedDir, sessionId);
|
|
38
|
+
const signed = signMarker(generatedDir, sessionId, marker);
|
|
39
|
+
atomicWriteFile(filePath, `${JSON.stringify(signed, null, 2)}\n`, { mode: 0o600 });
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Gate-side: is the per-session marker file present, readable, AND
|
|
44
|
+
* validly signed? (harness/f9485cc7 — this replaced the earlier
|
|
45
|
+
* "existence is enough" contract: a marker's mere presence used to
|
|
46
|
+
* satisfy the gate even with a malformed or corrupted body, on the
|
|
47
|
+
* theory that Edit/Write/Bash being gated meant only the operator could
|
|
48
|
+
* ever create the file. That theory holds only as long as no OTHER
|
|
49
|
+
* write primitive reaches `harness.generated/` — exactly the invariant
|
|
50
|
+
* this signing scheme no longer has to trust blindly.) A marker with no
|
|
51
|
+
* `signature`, a wrong `alg`, or a signature that fails to verify is
|
|
52
|
+
* REJECTED — see `MarkerCheck.forged` — with the SAME `matched: false`
|
|
53
|
+
* outcome as no marker at all, but a distinct, forgery-specific detail
|
|
54
|
+
* string so callers can tell the two apart for audit.
|
|
55
|
+
*
|
|
56
|
+
* `opts.maxAgeMs` (agent-tasks/d8ee60ca): when set, a validly-signed
|
|
57
|
+
* marker whose `approvedAt` is older than the cutoff returns
|
|
58
|
+
* `matched:false` with an "expired" detail so the agent gets the same
|
|
59
|
+
* "no approval" UX as a never-approved session and must re-approve.
|
|
60
|
+
* Expiry is only evaluated AFTER signature verification succeeds — an
|
|
61
|
+
* unsigned/forged marker is rejected outright, never "expired".
|
|
62
|
+
*/
|
|
63
|
+
export function checkApprovalMarker(generatedDir, sessionId, opts = {}) {
|
|
64
|
+
// Construct the path defensively: a malformed sessionId must fail CLOSED
|
|
65
|
+
// here (no valid marker, so the gate blocks and demands approval), not
|
|
66
|
+
// throw out of the gate hook, which the top-level handler turns into a
|
|
67
|
+
// non-blocking exit that would let the gated tool proceed.
|
|
68
|
+
let filePath;
|
|
69
|
+
try {
|
|
70
|
+
filePath = approvalMarkerPathFor(generatedDir, sessionId);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
matched: false,
|
|
75
|
+
detail: `invalid sessionId for approval marker: ${err instanceof Error ? err.message : String(err)}`,
|
|
76
|
+
marker: null,
|
|
77
|
+
expired: false,
|
|
78
|
+
forged: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Shared symlink-rejecting read (src/io/read-regular-file.ts): the lstat
|
|
82
|
+
// reject is defense-in-depth against a planted symlink
|
|
83
|
+
// (agent-tasks/d39f160e); each failure mode keeps its distinct detail.
|
|
84
|
+
const read = readRegularFileRejectingSymlink(filePath);
|
|
85
|
+
if (read.kind === "missing") {
|
|
86
|
+
return {
|
|
87
|
+
matched: false,
|
|
88
|
+
detail: `no approval marker at ${filePath}`,
|
|
89
|
+
marker: null,
|
|
90
|
+
expired: false,
|
|
91
|
+
forged: false,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (read.kind === "symlink") {
|
|
95
|
+
return {
|
|
96
|
+
matched: false,
|
|
97
|
+
detail: `approval marker is a symlink, refusing for safety: ${filePath}`,
|
|
98
|
+
marker: null,
|
|
99
|
+
expired: false,
|
|
100
|
+
forged: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (read.kind === "not-regular") {
|
|
104
|
+
return {
|
|
105
|
+
matched: false,
|
|
106
|
+
detail: `approval marker path is not a regular file: ${filePath}`,
|
|
107
|
+
marker: null,
|
|
108
|
+
expired: false,
|
|
109
|
+
forged: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (read.kind === "unreadable") {
|
|
113
|
+
// A genuine I/O error reading an existing regular file (permissions
|
|
114
|
+
// changed mid-flight, etc.) — not evidence of tampering, but its
|
|
115
|
+
// signature cannot be proven either, so fail closed. Kept distinct
|
|
116
|
+
// from `forged` (no claim of an active forgery attempt).
|
|
117
|
+
return {
|
|
118
|
+
matched: false,
|
|
119
|
+
detail: `approval marker at ${filePath} exists but could not be read (I/O error); treating as unapproved since its signature cannot be verified`,
|
|
120
|
+
marker: null,
|
|
121
|
+
expired: false,
|
|
122
|
+
forged: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// read.kind === "ok"
|
|
126
|
+
const parsed = safeJsonParse(read.content);
|
|
127
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
128
|
+
return {
|
|
129
|
+
matched: false,
|
|
130
|
+
detail: `forged/unsigned marker rejected: marker body at ${filePath} is not a JSON object`,
|
|
131
|
+
marker: null,
|
|
132
|
+
expired: false,
|
|
133
|
+
forged: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const obj = parsed;
|
|
137
|
+
const verification = verifyMarkerSignature(generatedDir, sessionId, obj);
|
|
138
|
+
if (!verification.ok) {
|
|
139
|
+
if (verification.kind === "key-unavailable") {
|
|
140
|
+
// Fail-closed I/O problem (permission error / disk issue reading or
|
|
141
|
+
// creating the signing key), NOT evidence of an active forgery
|
|
142
|
+
// attempt — a marker written by the operator with a perfectly valid
|
|
143
|
+
// signature would also fail this exact check if the key file became
|
|
144
|
+
// unreadable. Kept distinct from `forged` (review LOW 1,
|
|
145
|
+
// harness/f9485cc7) so a broken key file doesn't read as an attack
|
|
146
|
+
// in audit output, mirroring the `read.kind === "unreadable"` branch
|
|
147
|
+
// above.
|
|
148
|
+
return {
|
|
149
|
+
matched: false,
|
|
150
|
+
detail: `approval marker at ${filePath} could not be verified: ${verification.reason}; treating as unapproved`,
|
|
151
|
+
marker: null,
|
|
152
|
+
expired: false,
|
|
153
|
+
forged: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
matched: false,
|
|
158
|
+
detail: `forged/unsigned marker rejected: ${verification.reason} (${filePath})`,
|
|
159
|
+
marker: null,
|
|
160
|
+
expired: false,
|
|
161
|
+
forged: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const approvedAt = obj["approvedAt"];
|
|
165
|
+
const approvedBy = obj["approvedBy"];
|
|
166
|
+
const reportContentHash = typeof obj["reportContentHash"] === "string" ? obj["reportContentHash"] : null;
|
|
167
|
+
const marker = { approvedAt, approvedBy, reportContentHash };
|
|
168
|
+
if (opts.maxAgeMs !== undefined) {
|
|
169
|
+
const approvedAtMs = Date.parse(approvedAt);
|
|
170
|
+
if (Number.isFinite(approvedAtMs)) {
|
|
171
|
+
const nowMs = (opts.now ?? new Date()).getTime();
|
|
172
|
+
const ageMs = nowMs - approvedAtMs;
|
|
173
|
+
if (ageMs > opts.maxAgeMs) {
|
|
174
|
+
const ageMin = Math.round(ageMs / 60_000);
|
|
175
|
+
const maxMin = Math.round(opts.maxAgeMs / 60_000);
|
|
176
|
+
return {
|
|
177
|
+
matched: false,
|
|
178
|
+
detail: `approval marker ${path.basename(filePath)} expired: age ${ageMin}m > max ${maxMin}m (approved at ${approvedAt})`,
|
|
179
|
+
marker,
|
|
180
|
+
expired: true,
|
|
181
|
+
forged: false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
matched: true,
|
|
188
|
+
detail: `approved via marker ${path.basename(filePath)}: approved at ${approvedAt} by ${approvedBy}, signature verified`,
|
|
189
|
+
marker,
|
|
190
|
+
expired: false,
|
|
191
|
+
forged: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Clear the per-session marker (used by `harness approve --revoke` and tests). */
|
|
195
|
+
export function clearApprovalMarker(generatedDir, sessionId) {
|
|
196
|
+
try {
|
|
197
|
+
fs.rmSync(approvalMarkerPathFor(generatedDir, sessionId));
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
/* already gone */
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=markers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markers.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/markers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export interface PersistedReport {
|
|
2
|
+
filePath: string;
|
|
3
|
+
sessionId: string | null;
|
|
4
|
+
approvalStatus: string | null;
|
|
5
|
+
approvedAt: string | null;
|
|
6
|
+
/**
|
|
7
|
+
* ISO timestamp the producer stamped when it wrote the report; null
|
|
8
|
+
* for legacy reports without the field.
|
|
9
|
+
*/
|
|
10
|
+
createdAt: string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Effective creation time in epoch ms, resolved `createdAt` →
|
|
13
|
+
* filename ISO prefix → file mtime. Unlike mtime alone this survives
|
|
14
|
+
* the approval rewrite (which bumps mtime and would otherwise make a
|
|
15
|
+
* weeks-old report sort as the freshest, harness-discovery C1).
|
|
16
|
+
*/
|
|
17
|
+
createdAtMs: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Env var the persisted-report directory can be set from. Honored by
|
|
21
|
+
* harness (`defaultReportsDir` below + emitted by `harness apply` onto
|
|
22
|
+
* the pack-contributed hook commands) AND by `@lannguyensi/understanding-gate`
|
|
23
|
+
* (its `core/persistence.js:resolveReportDir` reads the same name), so
|
|
24
|
+
* the three actors that touch the directory — Stop hook (package),
|
|
25
|
+
* PreToolUse blocker (harness), `harness approve understanding` — can
|
|
26
|
+
* agree on the path regardless of each process's cwd.
|
|
27
|
+
*/
|
|
28
|
+
export declare const REPORTS_DIR_ENV = "UNDERSTANDING_GATE_REPORT_DIR";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the persisted-report directory. Precedence:
|
|
31
|
+
* 1. `UNDERSTANDING_GATE_REPORT_DIR` (taken verbatim — apply emits an
|
|
32
|
+
* absolute path, operator-exported values are shell-expanded before
|
|
33
|
+
* we see them).
|
|
34
|
+
* 2. `<cwd>/.understanding-gate/reports` — backward-compat fallback.
|
|
35
|
+
* Callers that have a stable anchor (the manifest directory) pass
|
|
36
|
+
* it as `cwd` so the fallback agrees with whatever path apply
|
|
37
|
+
* baked into the hook commands.
|
|
38
|
+
*/
|
|
39
|
+
export declare function defaultReportsDir(cwd?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Project root anchor for the reports directory: `<dir-of-manifest>/.understanding-gate/reports`.
|
|
42
|
+
* Used by `harness apply` to bake an absolute, manifest-anchored value into
|
|
43
|
+
* the pack-contributed hook commands' env, and by `harness approve` as the
|
|
44
|
+
* fallback when `UNDERSTANDING_GATE_REPORT_DIR` is unset.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reportsDirForManifest(manifestPath: string): string;
|
|
47
|
+
export declare function safeJsonParse(text: string): unknown;
|
|
48
|
+
/**
|
|
49
|
+
* List persisted reports under `dir`, newest-first by creation time
|
|
50
|
+
* (JSON `createdAt`, falling back to the filename ISO prefix, falling
|
|
51
|
+
* back to mtime). Missing directory returns []. Any I/O error on a
|
|
52
|
+
* single file is silently skipped; the caller falls through to the
|
|
53
|
+
* ledger result.
|
|
54
|
+
*
|
|
55
|
+
* Creation time, NOT mtime, is the sort key: `harness approve
|
|
56
|
+
* understanding` rewrites the report it flips, which bumps mtime and
|
|
57
|
+
* made an old just-approved report sort as the freshest
|
|
58
|
+
* (harness-discovery C1). mtime survives only as the last-resort
|
|
59
|
+
* fallback for files that carry neither timestamp.
|
|
60
|
+
*/
|
|
61
|
+
export declare function listPersistedReports(dir: string): PersistedReport[];
|
|
62
|
+
/**
|
|
63
|
+
* Maximum age a sessionId-null report may have for the tolerant
|
|
64
|
+
* fallback to adopt it on the `harness approve understanding` path.
|
|
65
|
+
* Sized for the real flow (Stop hook persists at turn end, operator
|
|
66
|
+
* approves within minutes) with slack for a slow read-through. Live
|
|
67
|
+
* repro that motivated it: a 17-day-old pending report got adopted,
|
|
68
|
+
* validated, and stamped for a fresh session because the producer had
|
|
69
|
+
* silently failed to persist the fresh report (harness-discovery C1,
|
|
70
|
+
* friction-log #67).
|
|
71
|
+
*/
|
|
72
|
+
export declare const TOLERANT_FALLBACK_MAX_AGE_MS: number;
|
|
73
|
+
/**
|
|
74
|
+
* Tolerance for a sessionId-less candidate whose `createdAt` lies in
|
|
75
|
+
* the FUTURE relative to the approve-time clock. A future creation
|
|
76
|
+
* time is suspect either way: a forged `createdAt` (the producer's
|
|
77
|
+
* Metadata block lets the agent author it) or serious clock skew.
|
|
78
|
+
* Beyond this skew the fallback rejects the candidate just like a
|
|
79
|
+
* stale one rather than trusting a timestamp that cannot be right.
|
|
80
|
+
*/
|
|
81
|
+
export declare const TOLERANT_FALLBACK_FUTURE_SKEW_MS: number;
|
|
82
|
+
export interface FindReportOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Behaviour of the sessionId-null tolerant fallback (older Stop-hook
|
|
85
|
+
* package versions write reports without a `sessionId` field):
|
|
86
|
+
* - `"any"` (default): adopt the freshest sessionId-null report
|
|
87
|
+
* regardless of its `approvalStatus`. The gate read path
|
|
88
|
+
* (`checkPersistedReport`) and post-tool-use expiry
|
|
89
|
+
* (`expirePersistedReport`) rely on this so they keep finding the
|
|
90
|
+
* session's own report.
|
|
91
|
+
* - `"uncompleted"`: skip sessionId-null reports whose
|
|
92
|
+
* `approvalStatus` is a terminal `approved` / `expired`. Such a
|
|
93
|
+
* report belongs to a prior, finished approval cycle (often from
|
|
94
|
+
* a different task days ago) and must not be silently re-adopted
|
|
95
|
+
* as the current session's approval. `harness approve
|
|
96
|
+
* understanding` passes this so it never flips a stale unrelated
|
|
97
|
+
* report into the live session (harness/0dce3880 friction #1).
|
|
98
|
+
*/
|
|
99
|
+
tolerantFallback?: "any" | "uncompleted";
|
|
100
|
+
/**
|
|
101
|
+
* Maximum age (relative to `now`) of a sessionId-null candidate the
|
|
102
|
+
* tolerant fallback may adopt; older candidates are skipped and
|
|
103
|
+
* surfaced via `FindReportSelection.staleRejected`. Strict sessionId
|
|
104
|
+
* matches are never age-limited. Unset means no limit (the legacy
|
|
105
|
+
* gate-read / expiry contract).
|
|
106
|
+
*/
|
|
107
|
+
maxFallbackAgeMs?: number;
|
|
108
|
+
/** Clock anchor for the age computation; defaults to the wall clock. */
|
|
109
|
+
now?: Date;
|
|
110
|
+
}
|
|
111
|
+
export interface FindReportSelection {
|
|
112
|
+
report: PersistedReport | null;
|
|
113
|
+
/**
|
|
114
|
+
* True when `report` was adopted via the sessionId-null tolerant
|
|
115
|
+
* fallback rather than a strict sessionId match. Callers that bind
|
|
116
|
+
* the report to a session (the approve flow) surface this loudly so
|
|
117
|
+
* the operator can verify the adoption.
|
|
118
|
+
*/
|
|
119
|
+
fallbackAdopted: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* sessionId-null candidates skipped for exceeding `maxFallbackAgeMs`,
|
|
122
|
+
* newest first. Lets the caller distinguish "no report at all" from
|
|
123
|
+
* "only stale candidates existed", which are different failures: the
|
|
124
|
+
* latter usually means the producer failed to persist the fresh
|
|
125
|
+
* report (harness-discovery C1).
|
|
126
|
+
*/
|
|
127
|
+
staleRejected: PersistedReport[];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Select the freshest report for a given session_id, or the freshest
|
|
131
|
+
* applicable report when the persisted file lacks a sessionId field
|
|
132
|
+
* (older package versions). `report: null` when nothing matches.
|
|
133
|
+
*
|
|
134
|
+
* The strict (sessionId-equals) match always wins. The tolerant
|
|
135
|
+
* fallback's appetite is controlled by `opts.tolerantFallback` and
|
|
136
|
+
* `opts.maxFallbackAgeMs` — see `FindReportOptions`. The selection
|
|
137
|
+
* result carries enough context (`fallbackAdopted`, `staleRejected`)
|
|
138
|
+
* for the caller to be loud about non-strict adoptions.
|
|
139
|
+
*/
|
|
140
|
+
export declare function selectReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): FindReportSelection;
|
|
141
|
+
/**
|
|
142
|
+
* Back-compat wrapper around `selectReportForSession` for callers that
|
|
143
|
+
* only need the report (the gate read and expiry paths).
|
|
144
|
+
*/
|
|
145
|
+
export declare function findLatestReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): PersistedReport | null;
|
|
146
|
+
export interface PersistedReportApprovalCheck {
|
|
147
|
+
approved: boolean;
|
|
148
|
+
detail: string;
|
|
149
|
+
report: PersistedReport | null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Flip the latest matching persisted report's approvalStatus to
|
|
153
|
+
* `expired` so it no longer satisfies the gate's persisted-report
|
|
154
|
+
* fallback (harness/1ee26e77 follow-up: post-tool-use expiry was
|
|
155
|
+
* marker-only; the persisted report at .understanding-gate/reports/
|
|
156
|
+
* silently kept satisfying the gate even after task_finish deleted
|
|
157
|
+
* the marker).
|
|
158
|
+
*
|
|
159
|
+
* Atomic rewrite. Preserves the rest of the report body so the audit
|
|
160
|
+
* trail (the operator's actual Understanding text + previous approval
|
|
161
|
+
* timestamps) stays intact; only the status fields change.
|
|
162
|
+
*
|
|
163
|
+
* Returns `{ ok: true, filePath, previousStatus }` on success,
|
|
164
|
+
* `{ ok: false, reason }` when no matching report exists or rewrite
|
|
165
|
+
* failed. Non-throwing: caller (post-tool-use hook) uses this as a
|
|
166
|
+
* best-effort cleanup so a missing report dir or unrelated I/O issue
|
|
167
|
+
* does not escalate into a session-breaking hook failure.
|
|
168
|
+
*/
|
|
169
|
+
export declare function expirePersistedReport(reportsDir: string, sessionId: string, now?: Date): {
|
|
170
|
+
ok: true;
|
|
171
|
+
filePath: string;
|
|
172
|
+
previousStatus: string | null;
|
|
173
|
+
} | {
|
|
174
|
+
ok: false;
|
|
175
|
+
reason: string;
|
|
176
|
+
};
|
|
177
|
+
export declare function checkPersistedReport(reportsDir: string, sessionId: string): PersistedReportApprovalCheck;
|