@lannguyensi/harness 0.40.0 → 0.42.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 +68 -0
- package/README.md +9 -0
- package/dist/cli/adopt/derive.d.ts +15 -0
- package/dist/cli/adopt/derive.js +34 -7
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/adopt/index.d.ts +30 -0
- package/dist/cli/adopt/index.js +23 -3
- package/dist/cli/adopt/index.js.map +1 -1
- package/dist/cli/apply/generate-settings.d.ts +14 -1
- package/dist/cli/apply/generate-settings.js +7 -3
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/approve/stdin-report.js +2 -0
- package/dist/cli/approve/stdin-report.js.map +1 -1
- package/dist/cli/approve/understanding.js +28 -0
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/doctor/claude-mcp.d.ts +50 -0
- package/dist/cli/doctor/claude-mcp.js +170 -0
- package/dist/cli/doctor/claude-mcp.js.map +1 -0
- package/dist/cli/doctor/codex.d.ts +8 -0
- package/dist/cli/doctor/codex.js +8 -1
- package/dist/cli/doctor/codex.js.map +1 -1
- package/dist/cli/doctor/format.js +60 -11
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +46 -0
- package/dist/cli/doctor/index.js +155 -29
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/npm-bin-path.js +42 -0
- package/dist/cli/doctor/npm-bin-path.js.map +1 -1
- package/dist/cli/doctor/types.d.ts +37 -0
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/dry-run.js +5 -0
- package/dist/cli/dry-run.js.map +1 -1
- package/dist/cli/explain.js +16 -3
- package/dist/cli/explain.js.map +1 -1
- package/dist/cli/index.js +328 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/agent-tasks-auth.js +32 -0
- package/dist/cli/init/agent-tasks-auth.js.map +1 -1
- package/dist/cli/init/composer.js +13 -40
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/dependencies.js +21 -0
- package/dist/cli/init/dependencies.js.map +1 -1
- package/dist/cli/init/detect.d.ts +20 -3
- package/dist/cli/init/detect.js +34 -36
- package/dist/cli/init/detect.js.map +1 -1
- package/dist/cli/init/index.d.ts +18 -0
- package/dist/cli/init/index.js +28 -2
- package/dist/cli/init/index.js.map +1 -1
- package/dist/cli/init/interactive.d.ts +41 -0
- package/dist/cli/init/interactive.js +421 -6
- package/dist/cli/init/interactive.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +10 -1
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.d.ts +1 -1
- package/dist/cli/init/templates.js +217 -8
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/list.js +3 -1
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/main.js +10 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.d.ts +24 -0
- package/dist/cli/pack/hook-bootstrap.js +47 -1
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.js +9 -1
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/hook-codex-post-tool-use.d.ts +25 -0
- package/dist/cli/pack/hook-codex-post-tool-use.js +160 -0
- package/dist/cli/pack/hook-codex-post-tool-use.js.map +1 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +1 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.js +43 -10
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-codex-stop.js +1 -8
- package/dist/cli/pack/hook-codex-stop.js.map +1 -1
- package/dist/cli/pack/hook-post-merge-gate-record.d.ts +30 -0
- package/dist/cli/pack/hook-post-merge-gate-record.js +153 -0
- package/dist/cli/pack/hook-post-merge-gate-record.js.map +1 -0
- package/dist/cli/pack/hook-post-merge-gate.d.ts +28 -0
- package/dist/cli/pack/hook-post-merge-gate.js +244 -0
- package/dist/cli/pack/hook-post-merge-gate.js.map +1 -0
- package/dist/cli/pack/hook-post-tool-use.js +16 -131
- package/dist/cli/pack/hook-post-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +54 -2
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js +148 -2
- package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +1 -1
- package/dist/cli/pack/hook-stay-in-scope.js +63 -20
- package/dist/cli/pack/hook-stay-in-scope.js.map +1 -1
- package/dist/cli/pack/hook-track-active-claim.js +33 -9
- package/dist/cli/pack/hook-track-active-claim.js.map +1 -1
- package/dist/cli/pack/index.d.ts +2 -1
- package/dist/cli/pack/index.js +2 -1
- package/dist/cli/pack/index.js.map +1 -1
- package/dist/cli/pack/mutate.d.ts +19 -0
- package/dist/cli/pack/mutate.js +38 -0
- package/dist/cli/pack/mutate.js.map +1 -1
- package/dist/cli/pack/reseed.d.ts +19 -0
- package/dist/cli/pack/reseed.js +118 -0
- package/dist/cli/pack/reseed.js.map +1 -0
- package/dist/cli/pack/understanding-report-schema-hint.js +1 -1
- package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
- package/dist/cli/pause/index.js +12 -3
- package/dist/cli/pause/index.js.map +1 -1
- package/dist/cli/record/index.d.ts +64 -0
- package/dist/cli/record/index.js +211 -0
- package/dist/cli/record/index.js.map +1 -0
- package/dist/cli/session-start/index.d.ts +21 -0
- package/dist/cli/session-start/index.js +160 -25
- package/dist/cli/session-start/index.js.map +1 -1
- package/dist/cli/session-start/toolchain-parity.d.ts +149 -0
- package/dist/cli/session-start/toolchain-parity.js +618 -0
- package/dist/cli/session-start/toolchain-parity.js.map +1 -0
- package/dist/cli/uninstall/index.d.ts +73 -1
- package/dist/cli/uninstall/index.js +134 -2
- package/dist/cli/uninstall/index.js.map +1 -1
- package/dist/cli/validate/checks.js +8 -0
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/claude-mcp.d.ts +258 -0
- package/dist/io/claude-mcp.js +544 -0
- package/dist/io/claude-mcp.js.map +1 -0
- package/dist/policies/ledger-client.d.ts +34 -1
- package/dist/policies/ledger-client.js +21 -16
- package/dist/policies/ledger-client.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -2
- package/dist/policy-packs/builtin/branch-protection-runtime.js +4 -2
- package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.d.ts +10 -1
- package/dist/policy-packs/builtin/branch-protection.js +17 -0
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +195 -0
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js +290 -0
- package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -0
- package/dist/policy-packs/builtin/post-merge-gate.d.ts +56 -0
- package/dist/policy-packs/builtin/post-merge-gate.js +291 -0
- package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -0
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +28 -13
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +197 -24
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +342 -40
- package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +26 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js +302 -17
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/policy-packs/registry.d.ts +23 -2
- package/dist/policy-packs/registry.js +31 -2
- package/dist/policy-packs/registry.js.map +1 -1
- package/dist/policy-packs/ux-compare.d.ts +14 -0
- package/dist/policy-packs/ux-compare.js +58 -0
- package/dist/policy-packs/ux-compare.js.map +1 -0
- package/dist/policy-packs/ux-drift-check.d.ts +27 -0
- package/dist/policy-packs/ux-drift-check.js +78 -0
- package/dist/policy-packs/ux-drift-check.js.map +1 -0
- package/dist/probes/mcp.d.ts +13 -0
- package/dist/probes/mcp.js +57 -0
- package/dist/probes/mcp.js.map +1 -1
- package/dist/runtime/approval-signing.d.ts +82 -0
- package/dist/runtime/approval-signing.js +275 -0
- package/dist/runtime/approval-signing.js.map +1 -0
- package/dist/runtime/git-context.d.ts +50 -0
- package/dist/runtime/git-context.js +108 -1
- package/dist/runtime/git-context.js.map +1 -1
- package/dist/runtime/hermetic-spawn-guard.d.ts +25 -0
- package/dist/runtime/hermetic-spawn-guard.js +136 -0
- package/dist/runtime/hermetic-spawn-guard.js.map +1 -0
- package/dist/runtime/intercept.js +62 -5
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/ledger-writer.d.ts +27 -0
- package/dist/runtime/ledger-writer.js +46 -0
- package/dist/runtime/ledger-writer.js.map +1 -0
- package/dist/runtime/pause-sentinel.js +14 -3
- package/dist/runtime/pause-sentinel.js.map +1 -1
- package/dist/runtime/read-only-bash.js +118 -0
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/runtime/recovery-git-commit.d.ts +22 -0
- package/dist/runtime/recovery-git-commit.js +284 -0
- package/dist/runtime/recovery-git-commit.js.map +1 -0
- package/dist/runtime/tool-name-aliases.d.ts +1 -0
- package/dist/runtime/tool-name-aliases.js +7 -1
- package/dist/runtime/tool-name-aliases.js.map +1 -1
- package/dist/schema/index.d.ts +94 -42
- package/dist/schema/index.js +5 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/policies.d.ts +56 -44
- package/dist/schema/policies.js +79 -9
- package/dist/schema/policies.js.map +1 -1
- package/dist/schema/toolchain-parity.d.ts +18 -0
- package/dist/schema/toolchain-parity.js +35 -0
- package/dist/schema/toolchain-parity.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// harness/f9485cc7 — HMAC signing for Understanding Gate / branch-protection
|
|
2
|
+
// approval markers.
|
|
3
|
+
//
|
|
4
|
+
// BACKGROUND: the marker at `harness.generated/.approvals/<id>` is the
|
|
5
|
+
// canonical "operator said go" signal (agent-tasks/88ca4bb3). Its integrity
|
|
6
|
+
// has rested entirely on an UNENFORCED invariant: "no configured MCP exposes
|
|
7
|
+
// a filesystem-write primitive" (understanding-before-execution.md
|
|
8
|
+
// "Approval state"). Edit / Write / Bash are gated by the same PreToolUse
|
|
9
|
+
// hook this marker satisfies, so today the agent has no path to WRITE the
|
|
10
|
+
// marker file. But nothing technical stops a future MCP tool with local
|
|
11
|
+
// file-write from silently reopening that hole: its tool name would not
|
|
12
|
+
// match the pack's `Edit|Write|Bash` blocker matcher, so a write through it
|
|
13
|
+
// would land un-gated. Before this module, the marker's contract was
|
|
14
|
+
// "existence is enough" (see the removed `checkApprovalMarker` comment) —
|
|
15
|
+
// exactly the shape a forger only needs a bare filesystem-write for. This
|
|
16
|
+
// module makes forging also require the operator-side HMAC key, so a marker
|
|
17
|
+
// planted through any write-primitive the blocker does not enumerate still
|
|
18
|
+
// fails the gate unless it carries a valid signature.
|
|
19
|
+
//
|
|
20
|
+
// HONEST TRUST MODEL (read this before assuming more than it delivers):
|
|
21
|
+
//
|
|
22
|
+
// - This does NOT create a read boundary. The `understanding-before-
|
|
23
|
+
// execution` PreToolUse blocker matches tool names `Edit|Write|Bash`
|
|
24
|
+
// only — it does not gate `Read`, and no permission profile in this
|
|
25
|
+
// repo scopes `Read` away from any path (see
|
|
26
|
+
// `src/policy-packs/permission-translator.ts`: `read` maps to
|
|
27
|
+
// `["Read", "Glob", "Grep"]` with `allow:true` in every v1 profile).
|
|
28
|
+
// So ANY file on local disk — including the signing key, wherever it
|
|
29
|
+
// lives — is readable by the ordinary, always-available `Read` tool,
|
|
30
|
+
// approved or not. Storing the key outside `harness.generated/` would
|
|
31
|
+
// not change this; the write barrier is per-tool-name, not per-path.
|
|
32
|
+
// - The residual this leaves: an agent (or anything running as the same
|
|
33
|
+
// OS user) that can (a) read arbitrary local files — true today, by
|
|
34
|
+
// design, via `Read` — AND (b) write to `harness.generated/.approvals/`
|
|
35
|
+
// through some primitive NOT covered by the `Edit|Write|Bash` blocker
|
|
36
|
+
// matcher, CAN forge a validly-signed marker: it reads the key with
|
|
37
|
+
// Read, computes the HMAC itself, and writes the result through the
|
|
38
|
+
// uncovered write path. Signing does not close this combined case; it
|
|
39
|
+
// closes the narrower (and, before this, the ONLY defended) case where
|
|
40
|
+
// the forger has a write primitive but no matching ability to also
|
|
41
|
+
// recompute a valid signature for it (e.g. a narrow single-purpose
|
|
42
|
+
// write channel, or a human/script that copies bytes without reading
|
|
43
|
+
// back the operator's key file), and it makes ANY forgery attempt one
|
|
44
|
+
// step more expensive and forensically visible (a key read is a
|
|
45
|
+
// distinguishable event; a bare unsigned-JSON write no longer is).
|
|
46
|
+
// - Given the agent and operator share one OS user account on this
|
|
47
|
+
// machine, this is deliberately pragmatic defense-in-depth, not a hard
|
|
48
|
+
// authorization boundary. It is documented here in full rather than
|
|
49
|
+
// oversold.
|
|
50
|
+
//
|
|
51
|
+
// KEY STORAGE: `<generatedDir>/.approval-signing.key` — a raw 32-byte
|
|
52
|
+
// secret, mode 0600, generated lazily on first use (by `harness approve
|
|
53
|
+
// understanding` / `harness approve branch-protection`, or by `harness
|
|
54
|
+
// init` — see src/cli/init/index.ts). Colocated with `harness.generated/`
|
|
55
|
+
// (not a separate home-dir path) so:
|
|
56
|
+
// - it inherits the SAME test-isolation guarantee every marker/report
|
|
57
|
+
// path already has (every call site here already threads an explicit,
|
|
58
|
+
// test-injectable `generatedDir` — no new parameter, no new way to
|
|
59
|
+
// leak into a real `~/.harness/` during a test run);
|
|
60
|
+
// - it is gitignored by the SAME existing convention as the rest of
|
|
61
|
+
// `harness.generated/` (docs/ARCHITECTURE.md: "harness.generated/ ...
|
|
62
|
+
// is .gitignore'd", unlike `harness.yaml` itself, which operators
|
|
63
|
+
// often check into a dotfiles repo) — a home-dir-anchored path could
|
|
64
|
+
// land inside a version-controlled directory when the operator
|
|
65
|
+
// resolves `harness.yaml` via `--config` into a repo tree; this can't;
|
|
66
|
+
// - `harness apply` never wipes it (apply only ever writes its own known
|
|
67
|
+
// files under `harness.generated/`; unknown siblings survive
|
|
68
|
+
// byte-for-byte — the same guarantee `.approvals/` and
|
|
69
|
+
// `.pending-approval` already rely on).
|
|
70
|
+
// `harness uninstall` DOES remove it (it rm -rf's the whole
|
|
71
|
+
// `generatedDir`) — a deliberate, rare, operator-initiated teardown, so
|
|
72
|
+
// losing the key (and therefore invalidating every marker it signed) is
|
|
73
|
+
// an acceptable side effect, not a silent regression.
|
|
74
|
+
//
|
|
75
|
+
// ROTATION: delete `<generatedDir>/.approval-signing.key` (or call
|
|
76
|
+
// `rotateSigningKey`). The next `writeApprovalMarker` /
|
|
77
|
+
// `writeBranchProtectionMarker` / any signature check lazily regenerates
|
|
78
|
+
// it. Every marker signed under the OLD key immediately fails verification
|
|
79
|
+
// (forged:true) and the gate demands re-approval — this is the intended,
|
|
80
|
+
// documented blast radius of a rotation, not a bug.
|
|
81
|
+
//
|
|
82
|
+
// BACK-COMPAT: strict, no migration window. A marker written before this
|
|
83
|
+
// feature shipped (or hand-written without the key) carries no `signature`
|
|
84
|
+
// field and is REJECTED (see `verifyMarkerSignature`), exactly like a
|
|
85
|
+
// forgery. Operator impact: upgrading harness on a machine with a live,
|
|
86
|
+
// previously-approved session invalidates that approval; the operator runs
|
|
87
|
+
// `harness approve understanding` (and/or `harness approve
|
|
88
|
+
// branch-protection`) once more. Chosen over a grace-period migration
|
|
89
|
+
// because a migration window is, by construction, a window where the
|
|
90
|
+
// exact vulnerability this task closes (an unsigned marker satisfies the
|
|
91
|
+
// gate) still holds.
|
|
92
|
+
import * as crypto from "node:crypto";
|
|
93
|
+
import * as fs from "node:fs";
|
|
94
|
+
import * as path from "node:path";
|
|
95
|
+
/** Versioned algorithm tag, so a future re-key scheme can reject mismatches explicitly rather than guess. */
|
|
96
|
+
export const SIGNING_ALG = "hmac-sha256-v1";
|
|
97
|
+
/** Basename of the signing-key file, a sibling of `.approvals/` under `generatedDir`. */
|
|
98
|
+
export const SIGNING_KEY_BASENAME = ".approval-signing.key";
|
|
99
|
+
const KEY_BYTES = 32;
|
|
100
|
+
/** Filesystem path of the signing key for a given `generatedDir`. */
|
|
101
|
+
export function signingKeyPathFor(generatedDir) {
|
|
102
|
+
return path.join(generatedDir, SIGNING_KEY_BASENAME);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Read the signing key, generating one (0600, `crypto.randomBytes(32)`) on
|
|
106
|
+
* first use. Race-safe-ish: a concurrent create loses the `wx` (exclusive
|
|
107
|
+
* create) race and falls back to reading whatever the winner wrote, rather
|
|
108
|
+
* than throwing or clobbering it. A key file shorter than `KEY_BYTES`
|
|
109
|
+
* (truncated / corrupted) is treated as absent and regenerated, since a
|
|
110
|
+
* short key would only weaken every future signature.
|
|
111
|
+
*/
|
|
112
|
+
export function getOrCreateSigningKey(generatedDir) {
|
|
113
|
+
const filePath = signingKeyPathFor(generatedDir);
|
|
114
|
+
fs.mkdirSync(generatedDir, { recursive: true });
|
|
115
|
+
let fileExisted = false;
|
|
116
|
+
try {
|
|
117
|
+
const existing = fs.readFileSync(filePath);
|
|
118
|
+
fileExisted = true;
|
|
119
|
+
if (existing.length >= KEY_BYTES) {
|
|
120
|
+
return { key: existing, filePath, created: false };
|
|
121
|
+
}
|
|
122
|
+
// Falls through: truncated/corrupt key file, regenerate below. `flag:
|
|
123
|
+
// "w"` (not "wx") is used below precisely BECAUSE the file already
|
|
124
|
+
// exists here — an exclusive create would collide with it and, on
|
|
125
|
+
// EEXIST, re-read the SAME truncated bytes back, silently failing to
|
|
126
|
+
// ever repair a corrupt key.
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err.code !== "ENOENT")
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
const fresh = crypto.randomBytes(KEY_BYTES);
|
|
133
|
+
if (fileExisted) {
|
|
134
|
+
// Known-corrupt/truncated file: overwrite unconditionally. No
|
|
135
|
+
// meaningful create-race to defend against — the file is already
|
|
136
|
+
// known bad, so unconditionally replacing it can only improve on
|
|
137
|
+
// the previous (unusable) contents.
|
|
138
|
+
fs.writeFileSync(filePath, fresh, { mode: 0o600 });
|
|
139
|
+
try {
|
|
140
|
+
fs.chmodSync(filePath, 0o600);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
/* best-effort */
|
|
144
|
+
}
|
|
145
|
+
return { key: fresh, filePath, created: true };
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
fs.writeFileSync(filePath, fresh, { mode: 0o600, flag: "wx" });
|
|
149
|
+
return { key: fresh, filePath, created: true };
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err.code === "EEXIST") {
|
|
153
|
+
// Lost the create race to a concurrent caller; use what they wrote.
|
|
154
|
+
return { key: fs.readFileSync(filePath), filePath, created: false };
|
|
155
|
+
}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Force a fresh key, overwriting any existing one. Every marker signed
|
|
161
|
+
* under the old key stops verifying immediately (see module doc). Not
|
|
162
|
+
* currently wired to a CLI verb (documented as "delete the file" for v1);
|
|
163
|
+
* exported so a future `harness approve rotate-key`-style verb, or a test,
|
|
164
|
+
* can call it directly without re-deriving the path convention.
|
|
165
|
+
*/
|
|
166
|
+
export function rotateSigningKey(generatedDir) {
|
|
167
|
+
const filePath = signingKeyPathFor(generatedDir);
|
|
168
|
+
fs.mkdirSync(generatedDir, { recursive: true });
|
|
169
|
+
const fresh = crypto.randomBytes(KEY_BYTES);
|
|
170
|
+
fs.writeFileSync(filePath, fresh, { mode: 0o600 });
|
|
171
|
+
try {
|
|
172
|
+
fs.chmodSync(filePath, 0o600);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
/* best-effort on platforms where chmod is a no-op (e.g. some Windows fs) */
|
|
176
|
+
}
|
|
177
|
+
return { key: fresh, filePath, created: true };
|
|
178
|
+
}
|
|
179
|
+
/** sha256 hex digest of a string, used to bind a marker to a persisted report's content. */
|
|
180
|
+
export function sha256Hex(content) {
|
|
181
|
+
return crypto.createHash("sha256").update(content, "utf8").digest("hex");
|
|
182
|
+
}
|
|
183
|
+
function canonicalPayload(markerId, approvedAt, approvedBy, reportContentHash) {
|
|
184
|
+
// Fixed key order + JSON string-escaping makes this injective: there is
|
|
185
|
+
// no (markerId, approvedAt, approvedBy, reportContentHash) tuple that
|
|
186
|
+
// collides with a different tuple's encoding, unlike naive delimiter
|
|
187
|
+
// concatenation (e.g. "a"+"bc" vs "ab"+"c").
|
|
188
|
+
return JSON.stringify({ markerId, approvedAt, approvedBy, reportContentHash });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Sign a marker's fields for `markerId` (the marker's lookup key — a raw
|
|
192
|
+
* sessionId for the understanding-gate session marker, `task-<id>` for a
|
|
193
|
+
* task-scoped marker, `branch-protection-<sessionId>` for the
|
|
194
|
+
* branch-protection twin). Binding `markerId` into the signed payload is
|
|
195
|
+
* what stops a validly-signed marker from being copied/renamed onto a
|
|
196
|
+
* DIFFERENT id and still verifying — verification always recomputes the
|
|
197
|
+
* HMAC using the id the caller is checking, not one read back out of the
|
|
198
|
+
* marker body.
|
|
199
|
+
*/
|
|
200
|
+
export function signMarker(generatedDir, markerId, marker) {
|
|
201
|
+
const { key } = getOrCreateSigningKey(generatedDir);
|
|
202
|
+
const reportContentHash = marker.reportContentHash ?? null;
|
|
203
|
+
const signature = crypto
|
|
204
|
+
.createHmac("sha256", key)
|
|
205
|
+
.update(canonicalPayload(markerId, marker.approvedAt, marker.approvedBy, reportContentHash))
|
|
206
|
+
.digest("hex");
|
|
207
|
+
return {
|
|
208
|
+
approvedAt: marker.approvedAt,
|
|
209
|
+
approvedBy: marker.approvedBy,
|
|
210
|
+
reportContentHash,
|
|
211
|
+
alg: SIGNING_ALG,
|
|
212
|
+
signature,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Verify a parsed marker JSON body against `markerId`. Never throws: any
|
|
217
|
+
* failure to obtain the signing key (permission error, disk issue) is
|
|
218
|
+
* treated as a verification failure, not an exception — the gate-side
|
|
219
|
+
* caller must fail closed (not approved) rather than crash the hook
|
|
220
|
+
* process, which the surrounding PreToolUse blocker would otherwise turn
|
|
221
|
+
* into an uncontrolled failure mode. That specific failure mode is tagged
|
|
222
|
+
* `kind: "key-unavailable"` so the caller can tell "we couldn't even check"
|
|
223
|
+
* apart from "we checked and it's invalid".
|
|
224
|
+
*/
|
|
225
|
+
export function verifyMarkerSignature(generatedDir, markerId, payload) {
|
|
226
|
+
const approvedAt = payload["approvedAt"];
|
|
227
|
+
const approvedBy = payload["approvedBy"];
|
|
228
|
+
const signature = payload["signature"];
|
|
229
|
+
const alg = payload["alg"];
|
|
230
|
+
if (typeof approvedAt !== "string" || approvedAt.length === 0) {
|
|
231
|
+
return { ok: false, reason: "missing approvedAt" };
|
|
232
|
+
}
|
|
233
|
+
if (typeof approvedBy !== "string" || approvedBy.length === 0) {
|
|
234
|
+
return { ok: false, reason: "missing approvedBy" };
|
|
235
|
+
}
|
|
236
|
+
if (typeof signature !== "string" || signature.length === 0) {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
reason: "missing signature (legacy pre-signing marker, or forged file)",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (alg !== SIGNING_ALG) {
|
|
243
|
+
return { ok: false, reason: `unrecognized or missing alg (got ${JSON.stringify(alg)})` };
|
|
244
|
+
}
|
|
245
|
+
const reportContentHash = typeof payload["reportContentHash"] === "string"
|
|
246
|
+
? payload["reportContentHash"]
|
|
247
|
+
: null;
|
|
248
|
+
let key;
|
|
249
|
+
try {
|
|
250
|
+
({ key } = getOrCreateSigningKey(generatedDir));
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
reason: `signing key unavailable (${err instanceof Error ? err.message : String(err)})`,
|
|
256
|
+
kind: "key-unavailable",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const expected = crypto
|
|
260
|
+
.createHmac("sha256", key)
|
|
261
|
+
.update(canonicalPayload(markerId, approvedAt, approvedBy, reportContentHash))
|
|
262
|
+
.digest();
|
|
263
|
+
// No try/catch around Buffer.from(signature, "hex"): unlike JSON.parse,
|
|
264
|
+
// Buffer.from with a "hex" encoding never throws on malformed input — it
|
|
265
|
+
// silently stops decoding at the first invalid pair and returns whatever
|
|
266
|
+
// it managed to decode so far (review LOW 3). The length-mismatch check
|
|
267
|
+
// below is what actually rejects a truncated/malformed decode; relying on
|
|
268
|
+
// an exception here would have been dead code.
|
|
269
|
+
const actual = Buffer.from(signature, "hex");
|
|
270
|
+
if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
|
|
271
|
+
return { ok: false, reason: "signature verification failed (tampered or forged)" };
|
|
272
|
+
}
|
|
273
|
+
return { ok: true };
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=approval-signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-signing.js","sourceRoot":"","sources":["../../src/runtime/approval-signing.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,oBAAoB;AACpB,EAAE;AACF,uEAAuE;AACvE,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,0EAA0E;AAC1E,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,4EAA4E;AAC5E,qEAAqE;AACrE,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,sDAAsD;AACtD,EAAE;AACF,wEAAwE;AACxE,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,wEAAwE;AACxE,iDAAiD;AACjD,kEAAkE;AAClE,yEAAyE;AACzE,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,yEAAyE;AACzE,0EAA0E;AAC1E,wEAAwE;AACxE,4EAA4E;AAC5E,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,0EAA0E;AAC1E,2EAA2E;AAC3E,uEAAuE;AACvE,uEAAuE;AACvE,yEAAyE;AACzE,0EAA0E;AAC1E,oEAAoE;AACpE,uEAAuE;AACvE,qEAAqE;AACrE,2EAA2E;AAC3E,wEAAwE;AACxE,gBAAgB;AAChB,EAAE;AACF,sEAAsE;AACtE,wEAAwE;AACxE,uEAAuE;AACvE,0EAA0E;AAC1E,qCAAqC;AACrC,wEAAwE;AACxE,0EAA0E;AAC1E,uEAAuE;AACvE,yDAAyD;AACzD,sEAAsE;AACtE,0EAA0E;AAC1E,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,2EAA2E;AAC3E,2EAA2E;AAC3E,iEAAiE;AACjE,2DAA2D;AAC3D,4CAA4C;AAC5C,8DAA8D;AAC9D,0EAA0E;AAC1E,0EAA0E;AAC1E,wDAAwD;AACxD,EAAE;AACF,mEAAmE;AACnE,wDAAwD;AACxD,yEAAyE;AACzE,2EAA2E;AAC3E,yEAAyE;AACzE,oDAAoD;AACpD,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,sEAAsE;AACtE,wEAAwE;AACxE,2EAA2E;AAC3E,2DAA2D;AAC3D,sEAAsE;AACtE,qEAAqE;AACrE,yEAAyE;AACzE,qBAAqB;AAErB,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,6GAA6G;AAC7G,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE5C,yFAAyF;AACzF,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB,qEAAqE;AACrE,MAAM,UAAU,iBAAiB,CAAC,YAAoB;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAC;AACvD,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;IACjD,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC3C,WAAW,GAAG,IAAI,CAAC;QACnB,IAAI,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YACjC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACrD,CAAC;QACD,sEAAsE;QACtE,mEAAmE;QACnE,kEAAkE;QAClE,qEAAqE;QACrE,6BAA6B;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,WAAW,EAAE,CAAC;QAChB,8DAA8D;QAC9D,iEAAiE;QACjE,iEAAiE;QACjE,oCAAoC;QACpC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB;QACnB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjD,CAAC;IACD,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,oEAAoE;YACpE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACtE,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;IACjD,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAC5C,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;IAC9E,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,gBAAgB,CACvB,QAAgB,EAChB,UAAkB,EAClB,UAAkB,EAClB,iBAAgC;IAEhC,wEAAwE;IACxE,sEAAsE;IACtE,qEAAqE;IACrE,6CAA6C;IAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAC;AACjF,CAAC;AAWD;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,YAAoB,EACpB,QAAgB,EAChB,MAAqF;IAErF,MAAM,EAAE,GAAG,EAAE,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,IAAI,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM;SACrB,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC;SACzB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;SAC3F,MAAM,CAAC,KAAK,CAAC,CAAC;IACjB,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,iBAAiB;QACjB,GAAG,EAAE,WAAW;QAChB,SAAS;KACV,CAAC;AACJ,CAAC;AAoBD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,YAAoB,EACpB,QAAgB,EAChB,OAAgC;IAEhC,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,+DAA+D;SACxE,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC3F,CAAC;IACD,MAAM,iBAAiB,GACrB,OAAO,OAAO,CAAC,mBAAmB,CAAC,KAAK,QAAQ;QAC9C,CAAC,CAAE,OAAO,CAAC,mBAAmB,CAAY;QAC1C,CAAC,CAAC,IAAI,CAAC;IACX,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,CAAC,EAAE,GAAG,EAAE,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;YACvF,IAAI,EAAE,iBAAiB;SACxB,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM;SACpB,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC;SACzB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,CAAC,CAAC;SAC7E,MAAM,EAAE,CAAC;IACZ,wEAAwE;IACxE,yEAAyE;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,0EAA0E;IAC1E,+CAA+C;IAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oDAAoD,EAAE,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -17,6 +17,27 @@ export interface GitRepoContext {
|
|
|
17
17
|
*/
|
|
18
18
|
sha: string;
|
|
19
19
|
}
|
|
20
|
+
export interface GitEntry {
|
|
21
|
+
/** Directory that contains the `.git` entry (the work-tree root). */
|
|
22
|
+
worktreeRoot: string;
|
|
23
|
+
/** Resolved git directory — for a `.git` file, its `gitdir:` target. */
|
|
24
|
+
gitDir: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Walk up from `startDir` looking for a `.git` entry. Handles both the
|
|
28
|
+
* common `.git` directory and the `.git` *file* form used by linked
|
|
29
|
+
* worktrees and submodules. The walk is bounded so a pathologically
|
|
30
|
+
* deep cwd cannot spin.
|
|
31
|
+
*
|
|
32
|
+
* Exported (task T-001, record-verbs) so `cli/record/index.ts` can
|
|
33
|
+
* locate the same `.git` directory this module resolves `repo`/
|
|
34
|
+
* `branch`/`sha` from, without re-walking the tree with duplicate
|
|
35
|
+
* logic — its base-branch resolution needs the raw git directory (to
|
|
36
|
+
* read `refs/remotes/origin/HEAD` / `packed-refs`), which
|
|
37
|
+
* `resolveGitContext`'s return shape does not expose. Behavior is
|
|
38
|
+
* unchanged; this is a visibility-only change.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findGitEntry(startDir: string): GitEntry | null;
|
|
20
41
|
/**
|
|
21
42
|
* Resolve `{ repo, branch, sha }` for a working directory. Returns empty
|
|
22
43
|
* strings (never throws) when `cwd` is not inside a git work tree, or
|
|
@@ -24,3 +45,32 @@ export interface GitRepoContext {
|
|
|
24
45
|
* fall through to their own behaviour.
|
|
25
46
|
*/
|
|
26
47
|
export declare function resolveGitContext(cwd: string): GitRepoContext;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the remote's default branch name from `<gitDir>/refs/remotes/
|
|
50
|
+
* origin/HEAD`. Loose symbolic ref first (the normal shape: `ref: refs/
|
|
51
|
+
* remotes/origin/<name>`, written by `git clone` / `git remote set-head
|
|
52
|
+
* origin -a`). When that loose file is absent, falls back to
|
|
53
|
+
* `packed-refs`: some git versions / tooling pack `refs/remotes/origin/
|
|
54
|
+
* HEAD` as a plain `<sha> <ref>` entry instead of a symref, which loses
|
|
55
|
+
* the branch NAME directly — recovered here by matching that sha
|
|
56
|
+
* against another packed `refs/remotes/origin/<name>` entry that shares
|
|
57
|
+
* it (mirrors the loose-then-packed shape `resolveBranchSha` uses
|
|
58
|
+
* above, adapted since packed-refs has no symref concept). Returns null
|
|
59
|
+
* when neither source resolves a name.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveOriginHeadBase(gitDir: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the actual shared git directory for `gitDir`, following the
|
|
64
|
+
* `commondir` file linked worktrees write. `git worktree add` gives each
|
|
65
|
+
* worktree its own private `.git` FILE pointing at `<main>/.git/
|
|
66
|
+
* worktrees/<name>/` (what `findGitEntry` returns as `gitDir`), but
|
|
67
|
+
* `refs/remotes/origin/HEAD` and `packed-refs` are NOT duplicated there
|
|
68
|
+
* — they live only in the shared common dir, reachable via that
|
|
69
|
+
* per-worktree directory's own `commondir` file (a path, normally
|
|
70
|
+
* `../..`, relative to the per-worktree directory itself; see
|
|
71
|
+
* `git-worktree(1)`). Without this indirection, `resolveOriginHeadBase`
|
|
72
|
+
* would look for those refs in the empty per-worktree directory and
|
|
73
|
+
* always miss. Returns `gitDir` unchanged when no `commondir` file
|
|
74
|
+
* exists (the normal, non-worktree case).
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveCommonDir(gitDir: string): string;
|
|
@@ -35,8 +35,16 @@ const SHA_RE = /^[0-9a-f]{40}$/;
|
|
|
35
35
|
* common `.git` directory and the `.git` *file* form used by linked
|
|
36
36
|
* worktrees and submodules. The walk is bounded so a pathologically
|
|
37
37
|
* deep cwd cannot spin.
|
|
38
|
+
*
|
|
39
|
+
* Exported (task T-001, record-verbs) so `cli/record/index.ts` can
|
|
40
|
+
* locate the same `.git` directory this module resolves `repo`/
|
|
41
|
+
* `branch`/`sha` from, without re-walking the tree with duplicate
|
|
42
|
+
* logic — its base-branch resolution needs the raw git directory (to
|
|
43
|
+
* read `refs/remotes/origin/HEAD` / `packed-refs`), which
|
|
44
|
+
* `resolveGitContext`'s return shape does not expose. Behavior is
|
|
45
|
+
* unchanged; this is a visibility-only change.
|
|
38
46
|
*/
|
|
39
|
-
function findGitEntry(startDir) {
|
|
47
|
+
export function findGitEntry(startDir) {
|
|
40
48
|
let dir = path.resolve(startDir);
|
|
41
49
|
for (let depth = 0; depth < 128; depth++) {
|
|
42
50
|
const dotGit = path.join(dir, ".git");
|
|
@@ -143,4 +151,103 @@ export function resolveGitContext(cwd) {
|
|
|
143
151
|
}
|
|
144
152
|
return { repo, branch, sha };
|
|
145
153
|
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Default-branch resolution (offline, no `gh`/`git` subprocess).
|
|
156
|
+
//
|
|
157
|
+
// Originally written for `harness record review`'s `--base` fallback
|
|
158
|
+
// (task T-001, record-verbs) and lived only in `cli/record/index.ts`.
|
|
159
|
+
// Exported here (task post-merge-gate, T-001) so
|
|
160
|
+
// `policy-packs/builtin/post-merge-gate-runtime.ts` can resolve the same
|
|
161
|
+
// "what's the default branch to switch back to" answer for its deny
|
|
162
|
+
// message without a policy-pack module reaching into `cli/`. Behavior is
|
|
163
|
+
// unchanged; this is a visibility/location move, not a rewrite — see
|
|
164
|
+
// `findGitEntry`'s doc comment above for the identical precedent
|
|
165
|
+
// (record/index.ts reusing this module's git-dir walk instead of
|
|
166
|
+
// duplicating it).
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// `.git/refs/remotes/origin/HEAD` on a normal clone: a symbolic ref
|
|
169
|
+
// pointing at the remote's default branch.
|
|
170
|
+
const ORIGIN_HEAD_REF_RE = /^ref:\s*refs\/remotes\/origin\/(.+)$/;
|
|
171
|
+
const ORIGIN_HEAD_REF_PATH = "refs/remotes/origin/HEAD";
|
|
172
|
+
const ORIGIN_REMOTE_PREFIX = "refs/remotes/origin/";
|
|
173
|
+
/**
|
|
174
|
+
* Resolve the remote's default branch name from `<gitDir>/refs/remotes/
|
|
175
|
+
* origin/HEAD`. Loose symbolic ref first (the normal shape: `ref: refs/
|
|
176
|
+
* remotes/origin/<name>`, written by `git clone` / `git remote set-head
|
|
177
|
+
* origin -a`). When that loose file is absent, falls back to
|
|
178
|
+
* `packed-refs`: some git versions / tooling pack `refs/remotes/origin/
|
|
179
|
+
* HEAD` as a plain `<sha> <ref>` entry instead of a symref, which loses
|
|
180
|
+
* the branch NAME directly — recovered here by matching that sha
|
|
181
|
+
* against another packed `refs/remotes/origin/<name>` entry that shares
|
|
182
|
+
* it (mirrors the loose-then-packed shape `resolveBranchSha` uses
|
|
183
|
+
* above, adapted since packed-refs has no symref concept). Returns null
|
|
184
|
+
* when neither source resolves a name.
|
|
185
|
+
*/
|
|
186
|
+
export function resolveOriginHeadBase(gitDir) {
|
|
187
|
+
try {
|
|
188
|
+
const raw = fs
|
|
189
|
+
.readFileSync(path.join(gitDir, "refs", "remotes", "origin", "HEAD"), "utf8")
|
|
190
|
+
.trim();
|
|
191
|
+
const match = ORIGIN_HEAD_REF_RE.exec(raw);
|
|
192
|
+
if (match)
|
|
193
|
+
return match[1].trim();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
/* loose symref missing — try packed-refs */
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const packed = fs.readFileSync(path.join(gitDir, "packed-refs"), "utf8");
|
|
200
|
+
let headSha = null;
|
|
201
|
+
const entries = [];
|
|
202
|
+
for (const rawLine of packed.split("\n")) {
|
|
203
|
+
const line = rawLine.trim();
|
|
204
|
+
if (line === "" || line.startsWith("#") || line.startsWith("^"))
|
|
205
|
+
continue;
|
|
206
|
+
const parts = line.split(/\s+/);
|
|
207
|
+
const sha = parts[0];
|
|
208
|
+
const ref = parts[1];
|
|
209
|
+
if (!sha || !ref || !SHA_RE.test(sha))
|
|
210
|
+
continue;
|
|
211
|
+
if (ref === ORIGIN_HEAD_REF_PATH)
|
|
212
|
+
headSha = sha;
|
|
213
|
+
else
|
|
214
|
+
entries.push({ sha, ref });
|
|
215
|
+
}
|
|
216
|
+
if (headSha) {
|
|
217
|
+
const match = entries.find((e) => e.sha === headSha && e.ref.startsWith(ORIGIN_REMOTE_PREFIX));
|
|
218
|
+
if (match)
|
|
219
|
+
return match.ref.slice(ORIGIN_REMOTE_PREFIX.length);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
/* packed-refs missing too — caller treats null as "unresolvable" */
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolve the actual shared git directory for `gitDir`, following the
|
|
229
|
+
* `commondir` file linked worktrees write. `git worktree add` gives each
|
|
230
|
+
* worktree its own private `.git` FILE pointing at `<main>/.git/
|
|
231
|
+
* worktrees/<name>/` (what `findGitEntry` returns as `gitDir`), but
|
|
232
|
+
* `refs/remotes/origin/HEAD` and `packed-refs` are NOT duplicated there
|
|
233
|
+
* — they live only in the shared common dir, reachable via that
|
|
234
|
+
* per-worktree directory's own `commondir` file (a path, normally
|
|
235
|
+
* `../..`, relative to the per-worktree directory itself; see
|
|
236
|
+
* `git-worktree(1)`). Without this indirection, `resolveOriginHeadBase`
|
|
237
|
+
* would look for those refs in the empty per-worktree directory and
|
|
238
|
+
* always miss. Returns `gitDir` unchanged when no `commondir` file
|
|
239
|
+
* exists (the normal, non-worktree case).
|
|
240
|
+
*/
|
|
241
|
+
export function resolveCommonDir(gitDir) {
|
|
242
|
+
try {
|
|
243
|
+
const raw = fs.readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
|
|
244
|
+
if (raw.length > 0) {
|
|
245
|
+
return path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
/* no commondir file — gitDir already IS the common dir */
|
|
250
|
+
}
|
|
251
|
+
return gitDir;
|
|
252
|
+
}
|
|
146
253
|
//# sourceMappingURL=git-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-context.js","sourceRoot":"","sources":["../../src/runtime/git-context.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,aAAa;AACb,EAAE;AACF,uEAAuE;AACvE,gEAAgE;AAChE,uEAAuE;AACvE,uEAAuE;AACvE,sEAAsE;AACtE,gEAAgE;AAChE,EAAE;AACF,mEAAmE;AACnE,mEAAmE;AACnE,sEAAsE;AACtE,gEAAgE;AAChE,sCAAsC;AACtC,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,mEAAmE;AACnE,gEAAgE;AAChE,mDAAmD;AAEnD,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAsBlC,MAAM,KAAK,GAAmB,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAEhE,uEAAuE;AACvE,yBAAyB;AACzB,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,uEAAuE;AACvE,oDAAoD;AACpD,MAAM,WAAW,GAAG,4BAA4B,CAAC;AACjD,sEAAsE;AACtE,MAAM,MAAM,GAAG,gBAAgB,CAAC;AAShC
|
|
1
|
+
{"version":3,"file":"git-context.js","sourceRoot":"","sources":["../../src/runtime/git-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link assertNoRealSpawnInTests} when a real-spawn code path
|
|
3
|
+
* runs under vitest without a test-injected fake. Callers whose
|
|
4
|
+
* surrounding try/catch degrades ordinary runner failures to a warning
|
|
5
|
+
* MUST re-throw instances of this class past that catch — never let it
|
|
6
|
+
* be treated as an ordinary (optional, warn-and-continue) failure.
|
|
7
|
+
*/
|
|
8
|
+
export declare class HermeticSpawnViolationError extends Error {
|
|
9
|
+
constructor(binaryLabel: string, hint: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Guard for a "real spawn" implementation: throws
|
|
13
|
+
* {@link HermeticSpawnViolationError} when called under vitest, so an
|
|
14
|
+
* accidental real spawn in a test fails hard instead of silently
|
|
15
|
+
* running. No-op outside of vitest (production behavior is unchanged),
|
|
16
|
+
* and no-op when `HARNESS_ALLOW_REAL_SPAWN=1` is set (see module doc) —
|
|
17
|
+
* the first such no-op under vitest prints a one-time stderr warning so
|
|
18
|
+
* the disabled guard is visible rather than silent.
|
|
19
|
+
*
|
|
20
|
+
* @param binaryLabel human-readable name of what would be spawned (e.g.
|
|
21
|
+
* `"npx orchestrator-workflow init"`), used in the error message.
|
|
22
|
+
* @param hint one line telling the test author what to do instead (e.g.
|
|
23
|
+
* "inject a fake `owInitSpawn` runner").
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertNoRealSpawnInTests(binaryLabel: string, hint: string): void;
|