@lannguyensi/harness 0.53.0 → 0.55.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 +70 -1
- package/dist/cli/delegate/index.d.ts +15 -2
- package/dist/cli/delegate/index.js +128 -4
- package/dist/cli/delegate/index.js.map +1 -1
- package/dist/cli/doctor/bypass-without-auto-approve.js +10 -4
- package/dist/cli/doctor/bypass-without-auto-approve.js.map +1 -1
- package/dist/cli/dry-run.js +32 -1
- package/dist/cli/dry-run.js.map +1 -1
- package/dist/cli/index.js +13 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/profiles.js +89 -8
- package/dist/cli/init/profiles.js.map +1 -1
- package/dist/cli/init/templates.js +163 -3
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +217 -95
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/record/index.d.ts +8 -0
- package/dist/cli/record/index.js +55 -7
- package/dist/cli/record/index.js.map +1 -1
- package/dist/cli/validate/checks.js +61 -9
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/extract.d.ts +29 -0
- package/dist/io/extract.js +36 -0
- package/dist/io/extract.js.map +1 -1
- package/dist/io/read-regular-file.d.ts +33 -2
- package/dist/io/read-regular-file.js +41 -8
- package/dist/io/read-regular-file.js.map +1 -1
- package/dist/policies/index.d.ts +1 -1
- package/dist/policies/index.js +1 -1
- package/dist/policies/index.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +71 -4
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +75 -4
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +9 -3
- package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +1 -1
- package/dist/runtime/destructive-shell-floor.d.ts +19 -0
- package/dist/runtime/destructive-shell-floor.js +597 -0
- package/dist/runtime/destructive-shell-floor.js.map +1 -0
- package/dist/runtime/intercept.d.ts +10 -7
- package/dist/runtime/intercept.js +108 -9
- package/dist/runtime/intercept.js.map +1 -1
- package/dist/runtime/read-only-bash.d.ts +54 -0
- package/dist/runtime/read-only-bash.js +775 -7
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/runtime/risk-classifier.js +72 -13
- package/dist/runtime/risk-classifier.js.map +1 -1
- package/dist/runtime/when-eval.d.ts +18 -5
- package/dist/runtime/when-eval.js +64 -11
- package/dist/runtime/when-eval.js.map +1 -1
- package/dist/runtime/workflow-policies.d.ts +43 -1
- package/dist/runtime/workflow-policies.js +158 -1
- package/dist/runtime/workflow-policies.js.map +1 -1
- package/dist/schema/extract.d.ts +4 -0
- package/dist/schema/extract.js +64 -1
- package/dist/schema/extract.js.map +1 -1
- package/dist/schema/index.d.ts +13 -0
- package/dist/schema/policies.d.ts +19 -0
- package/dist/schema/policies.js +17 -1
- package/dist/schema/policies.js.map +1 -1
- package/package.json +1 -1
|
@@ -213,8 +213,8 @@ hooks:
|
|
|
213
213
|
# f2d2a29): every \`harness policy intercept\` hook below down through
|
|
214
214
|
# \`risk-gate\` carries \`budget_ms: 15000\`, i.e. a Claude Code outer
|
|
215
215
|
# kill-timeout of \`ceil(15000/1000) = 15\` seconds (generate-settings.ts's
|
|
216
|
-
# \`hookTimeoutSeconds\`). This is deliberately UNIFORM across all
|
|
217
|
-
# them, for two independent reasons:
|
|
216
|
+
# \`hookTimeoutSeconds\`). This is deliberately UNIFORM across all thirteen
|
|
217
|
+
# of them, for two independent reasons:
|
|
218
218
|
#
|
|
219
219
|
# 1. FAIL-CLOSED MARGIN. \`harness policy intercept\` evaluates its FULL
|
|
220
220
|
# \`policies:\` list against the incoming event (src/runtime/intercept.ts,
|
|
@@ -257,7 +257,7 @@ hooks:
|
|
|
257
257
|
# and reintroduce exactly the redundant-invocation cost the dedup
|
|
258
258
|
# exists to avoid — on top of leaving whichever entry keeps a low
|
|
259
259
|
# timeout still exposed to the fail-open risk above. Keeping all
|
|
260
|
-
#
|
|
260
|
+
# thirteen at the identical 15000ms budget_ms preserves the existing
|
|
261
261
|
# one-invocation-per-matcher-group collapse (previously they all
|
|
262
262
|
# collapsed onto the shared 2s floor; now they collapse onto 15s).
|
|
263
263
|
- name: require-review-evidence
|
|
@@ -281,6 +281,31 @@ hooks:
|
|
|
281
281
|
blocking: hard
|
|
282
282
|
budget_ms: 15000
|
|
283
283
|
|
|
284
|
+
# The two OTHER agent-tasks verbs that land a PR (task 2699b476).
|
|
285
|
+
# \`pull_requests_merge\` is not the only merge surface the MCP server
|
|
286
|
+
# exposes: \`task_merge\` merges the PR attached to a task, and
|
|
287
|
+
# \`task_finish\` with \`autoMerge: true\` merges as part of finishing
|
|
288
|
+
# (both of its auto-merge modes, the soloMode work claim and the review
|
|
289
|
+
# claim + approve). Each needs its OWN hook entry because a hook's
|
|
290
|
+
# \`match\` is what \`harness apply\` projects into settings.json's
|
|
291
|
+
# tool-name matcher, and a matcher for \`pull_requests_merge\` never
|
|
292
|
+
# spawns \`harness policy intercept\` for either of these. Same
|
|
293
|
+
# 15000ms budget_ms as every other blocking intercept hook, for the
|
|
294
|
+
# same two reasons the note above gives.
|
|
295
|
+
- name: require-review-evidence-task-merge
|
|
296
|
+
event: PreToolUse
|
|
297
|
+
match: "mcp__agent-tasks__task_merge"
|
|
298
|
+
command: harness policy intercept
|
|
299
|
+
blocking: hard
|
|
300
|
+
budget_ms: 15000
|
|
301
|
+
|
|
302
|
+
- name: require-review-evidence-task-finish
|
|
303
|
+
event: PreToolUse
|
|
304
|
+
match: "mcp__agent-tasks__task_finish"
|
|
305
|
+
command: harness policy intercept
|
|
306
|
+
blocking: hard
|
|
307
|
+
budget_ms: 15000
|
|
308
|
+
|
|
284
309
|
- name: require-dogfood-evidence
|
|
285
310
|
event: PreToolUse
|
|
286
311
|
match: "Bash"
|
|
@@ -472,6 +497,74 @@ policies:
|
|
|
472
497
|
run:
|
|
473
498
|
- 'harness record review --pr <pr> "<summary>"'
|
|
474
499
|
|
|
500
|
+
# The two agent-tasks verbs that ALSO land a PR (task 2699b476, closing
|
|
501
|
+
# the residual 99f47307 Slice 1 named twice in the CHANGELOG). Same
|
|
502
|
+
# shape as review-before-merge, three scope notes:
|
|
503
|
+
# 1. Tag shape: \`review:\${TASK_ID}\` rather than \`review:\${PR_NUMBER}\`.
|
|
504
|
+
# Both verbs are task-scoped: they derive owner/repo/PR number from
|
|
505
|
+
# the task, so \`taskId\` is the only identifier in the tool payload.
|
|
506
|
+
# \`harness record review --pr <pr> --task <id> "<summary>"\` writes
|
|
507
|
+
# the PR, branch, base AND task tags in ONE ledger fact, so a single
|
|
508
|
+
# recorded review satisfies all four merge gates at once.
|
|
509
|
+
# 2. task_finish is gated ONLY in its auto-merge mode. The verb is
|
|
510
|
+
# polymorphic: a plain \`task_finish\` advances the task and merges
|
|
511
|
+
# nothing, while \`autoMerge: true\` merges the PR (both modes do,
|
|
512
|
+
# the soloMode work claim and the review claim + approve). Gating
|
|
513
|
+
# the whole verb would block the ordinary finish call for no reason;
|
|
514
|
+
# \`input_match\` narrows the trigger to the merging mode alone.
|
|
515
|
+
# A missing \`autoMerge\` never matches, so an omitted argument
|
|
516
|
+
# leaves the gate out of the way rather than arming it.
|
|
517
|
+
# 3. These sit ALONGSIDE review-before-merge / -bash, same rationale
|
|
518
|
+
# as the bash variant above: an operator may use any of the four
|
|
519
|
+
# merge surfaces, and each carries its own tag shape.
|
|
520
|
+
- name: review-before-task-merge
|
|
521
|
+
description: Block agent-tasks task_merge unless a ledger entry tagged review:<task-id> exists for this session.
|
|
522
|
+
trigger:
|
|
523
|
+
event: PreToolUse
|
|
524
|
+
match: "mcp__agent-tasks__task_merge"
|
|
525
|
+
extract:
|
|
526
|
+
TASK_ID: "toolArgs.taskId"
|
|
527
|
+
requires:
|
|
528
|
+
ledger_tag: "review:\${TASK_ID}"
|
|
529
|
+
hook: require-review-evidence-task-merge
|
|
530
|
+
enforcement: block
|
|
531
|
+
producers:
|
|
532
|
+
- kind: mcp
|
|
533
|
+
verb: mcp__grounding-mcp__ledger_add
|
|
534
|
+
example: '{sessionId:"\${SESSION_ID}", type:"fact", content:"review:\${TASK_ID}: <verdict + key findings + nits>", source:"Agent(general-purpose) review"}'
|
|
535
|
+
description: Spawn a review subagent against the PR diff, capture its verdict, then persist a ledger entry tagged with the task id. Mirror of the review-before-merge producer for the task-scoped merge surface.
|
|
536
|
+
ux:
|
|
537
|
+
cannot: "You cannot merge the PR for task \${TASK_ID} yet."
|
|
538
|
+
required:
|
|
539
|
+
- "a recorded review of task \${TASK_ID}"
|
|
540
|
+
run:
|
|
541
|
+
- 'harness record review --pr <pr> --task \${TASK_ID} "<summary>"'
|
|
542
|
+
|
|
543
|
+
- name: review-before-task-finish-automerge
|
|
544
|
+
description: 'Block agent-tasks task_finish with autoMerge: true unless a ledger entry tagged review:<task-id> exists for this session.'
|
|
545
|
+
trigger:
|
|
546
|
+
event: PreToolUse
|
|
547
|
+
match: "mcp__agent-tasks__task_finish"
|
|
548
|
+
input_match:
|
|
549
|
+
toolArgs.autoMerge: true
|
|
550
|
+
extract:
|
|
551
|
+
TASK_ID: "toolArgs.taskId"
|
|
552
|
+
requires:
|
|
553
|
+
ledger_tag: "review:\${TASK_ID}"
|
|
554
|
+
hook: require-review-evidence-task-finish
|
|
555
|
+
enforcement: block
|
|
556
|
+
producers:
|
|
557
|
+
- kind: mcp
|
|
558
|
+
verb: mcp__grounding-mcp__ledger_add
|
|
559
|
+
example: '{sessionId:"\${SESSION_ID}", type:"fact", content:"review:\${TASK_ID}: <verdict + key findings + nits>", source:"Agent(general-purpose) review"}'
|
|
560
|
+
description: Spawn a review subagent against the PR diff, capture its verdict, then persist a ledger entry tagged with the task id. Same evidence the task_merge gate reads, so one recorded review opens both.
|
|
561
|
+
ux:
|
|
562
|
+
cannot: "You cannot finish task \${TASK_ID} with autoMerge yet."
|
|
563
|
+
required:
|
|
564
|
+
- "a recorded review of task \${TASK_ID}"
|
|
565
|
+
run:
|
|
566
|
+
- 'harness record review --pr <pr> --task \${TASK_ID} "<summary>"'
|
|
567
|
+
|
|
475
568
|
- name: dogfood-before-release
|
|
476
569
|
description: Block npm publish / git tag v* without a recent dogfood ledger entry.
|
|
477
570
|
trigger:
|
|
@@ -1080,6 +1173,73 @@ risk:
|
|
|
1080
1173
|
- pattern: 'terraform(?:\\s+-\\S+(?:\\s+(?!destroy\\b)(?!-)\\S+)?)*\\s+destroy'
|
|
1081
1174
|
categories: [destructive, infrastructure_change]
|
|
1082
1175
|
severity: critical
|
|
1176
|
+
# Task 2929c5b7: unclassified commands no longer trivially
|
|
1177
|
+
# satisfy risk.severity_at_least: critical (see when-eval.ts and
|
|
1178
|
+
# docs/risk-gate.md's "Unclassified actions and the fail-close
|
|
1179
|
+
# rule") — kept in lockstep with docs/examples/full-manifest.yaml
|
|
1180
|
+
# by tests/cli/init-full-template-parity.test.ts.
|
|
1181
|
+
#
|
|
1182
|
+
# These patterns are the OPERATOR-EDITABLE MIRROR of the built-in
|
|
1183
|
+
# destructive floor (src/runtime/destructive-shell-floor.ts),
|
|
1184
|
+
# not the only line of defence: the floor ships in the binary and
|
|
1185
|
+
# already classifies these heads for an EXISTING manifest that
|
|
1186
|
+
# never adopts the patterns below. Edit, narrow, or raise these
|
|
1187
|
+
# freely: an operator pattern composes with the floor under
|
|
1188
|
+
# highest-severity-wins, so it can only add. The floor is
|
|
1189
|
+
# argv-aware where a regex cannot be (path-qualified and wrapped
|
|
1190
|
+
# spellings: /bin/dd, sudo dd, sh -c "dd ...", git -C <dir> push
|
|
1191
|
+
# -f), so a few spellings are caught by the floor alone; the
|
|
1192
|
+
# parity test in tests/runtime/destructive-shell-floor.test.ts
|
|
1193
|
+
# pins that everything caught HERE is also caught THERE, at the
|
|
1194
|
+
# same severity or higher.
|
|
1195
|
+
- pattern: '\\bdd\\s[^\\n]*\\bof='
|
|
1196
|
+
categories: [destructive, data_loss]
|
|
1197
|
+
severity: critical
|
|
1198
|
+
- pattern: '\\btruncate\\b[^\\n]*(\\s-[a-zA-Z]*s|--size)'
|
|
1199
|
+
categories: [destructive, data_loss]
|
|
1200
|
+
severity: critical
|
|
1201
|
+
- pattern: '\\bshred\\b'
|
|
1202
|
+
categories: [destructive, data_loss, irreversible_action]
|
|
1203
|
+
severity: critical
|
|
1204
|
+
- pattern: '\\bmkfs(\\.\\w+)?\\b'
|
|
1205
|
+
categories: [destructive, data_loss, infrastructure_change]
|
|
1206
|
+
severity: critical
|
|
1207
|
+
- pattern: '\\bfind\\b[^\\n]*-delete\\b'
|
|
1208
|
+
categories: [destructive, data_loss]
|
|
1209
|
+
severity: critical
|
|
1210
|
+
- pattern: '\\bfind\\b[^\\n]*-exec(dir)?\\s+rm\\b'
|
|
1211
|
+
categories: [destructive, data_loss]
|
|
1212
|
+
severity: critical
|
|
1213
|
+
- pattern: '\\bgit\\s+reset\\b[^\\n]*--hard\\b'
|
|
1214
|
+
categories: [destructive, data_loss]
|
|
1215
|
+
severity: high
|
|
1216
|
+
- pattern: '\\bgit\\s+push\\b[^\\n]*(--force(-with-lease)?\\b|\\s-f\\b)'
|
|
1217
|
+
categories: [destructive, production_mutation, deployment_change]
|
|
1218
|
+
severity: high
|
|
1219
|
+
- pattern: '\\bgit\\s+clean\\b[^\\n]*(--force\\b|\\s-[a-zA-Z]*f[a-zA-Z]*\\b)'
|
|
1220
|
+
categories: [destructive, data_loss]
|
|
1221
|
+
severity: high
|
|
1222
|
+
- pattern: '\\bgit\\s+checkout\\s+--\\s+\\.'
|
|
1223
|
+
categories: [destructive, data_loss]
|
|
1224
|
+
severity: high
|
|
1225
|
+
- pattern: '\\bgit\\s+restore\\s+\\.(\\s|$)'
|
|
1226
|
+
categories: [destructive, data_loss]
|
|
1227
|
+
severity: high
|
|
1228
|
+
- pattern: '\\b(chmod|chown)\\b[^\\n]*(\\s-[a-zA-Z]*R|--recursive\\b)'
|
|
1229
|
+
categories: [mass_update]
|
|
1230
|
+
severity: high
|
|
1231
|
+
- pattern: '\\bcurl\\b[^\\n]*(-X\\s*|--request[\\s=])(?![Gg][Ee][Tt]\\b)(?![Hh][Ee][Aa][Dd]\\b)[A-Za-z]'
|
|
1232
|
+
categories: [production_mutation, network_exfiltration]
|
|
1233
|
+
severity: high
|
|
1234
|
+
- pattern: '\\bcurl\\b[^\\n]*(\\s-[a-zA-Z]*[dFT]|--data\\b|--json\\b|--form(-string)?\\b|--upload-file\\b)'
|
|
1235
|
+
categories: [production_mutation, network_exfiltration]
|
|
1236
|
+
severity: high
|
|
1237
|
+
- pattern: '\\bcurl\\b[^\\n]*(\\s-[a-zA-Z]*[oODcK]|--output(-dir)?\\b|--remote-name\\b|--remote-header-name\\b|--dump-header\\b|--cookie-jar\\b|--config\\b|--create-dirs\\b|--etag-save\\b|--trace(-ascii)?\\b|--stderr\\b|(\\s-[a-zA-Z]*w\\b|--write-out\\b)[^\\n]*%output)'
|
|
1238
|
+
categories: [destructive, data_loss]
|
|
1239
|
+
severity: high
|
|
1240
|
+
- pattern: '\\bsed\\b[^\\n]*(\\s-[a-zA-Z]*i[a-zA-Z]*\\b|--in-place\\b)'
|
|
1241
|
+
categories: [destructive, data_loss]
|
|
1242
|
+
severity: high
|
|
1083
1243
|
|
|
1084
1244
|
environments:
|
|
1085
1245
|
resolvers:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/cli/init/templates.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wEAAwE;AACxE,mEAAmE;AACnE,kEAAkE;AAClE,sEAAsE;AACtE,0DAA0D;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,sEAAsE,CAAC;AAEhH,uEAAuE;AACvE,kEAAkE;AAClE,MAAM,oBAAoB,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqB/B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/cli/init/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,14 +36,29 @@
|
|
|
36
36
|
// failed capture, through the same `persistStdinReport` the operator
|
|
37
37
|
// path uses. That log is what the block envelope's malformed-sections
|
|
38
38
|
// notice is rendered from, so the per-capture write is deliberate.
|
|
39
|
+
//
|
|
40
|
+
// FOLLOW-UP (agent-tasks 49d1ee41) adds the second report channel the
|
|
41
|
+
// ADR's "Report capture under `-p`" fallback describes: a report-bound
|
|
42
|
+
// delegation (one `harness delegate --report <path>` minted) is verified
|
|
43
|
+
// against the CONVENTIONAL launcher-report path
|
|
44
|
+
// (`delegationReportPathFor`, a plain function of the child session id,
|
|
45
|
+
// never a value read from the payload), and on success that file's
|
|
46
|
+
// content is persisted the same way a transcript hit is: adopted once
|
|
47
|
+
// (this time keyed by the report's CONTENT hash, in the same
|
|
48
|
+
// `.delegation-adoptions/` ledger, so the same launcher report cannot
|
|
49
|
+
// mint a second marker once the first one expires), persisted `pending`
|
|
50
|
+
// via `persistStdinReport`, and left for the auto path below to consume.
|
|
51
|
+
// The transcript scan never runs for a report-bound delegation: the two
|
|
52
|
+
// channels are mutually exclusive per delegation, decided by whether
|
|
53
|
+
// `verifyDelegation` returns a `reportPathHash`.
|
|
39
54
|
import { appendFileSync, existsSync, lstatSync, mkdirSync } from "node:fs";
|
|
40
55
|
import * as path from "node:path";
|
|
41
56
|
import { readRegularFileRejectingSymlink } from "../../io/read-regular-file.js";
|
|
42
57
|
import { queryLedgerByTag, } from "../../policies/index.js";
|
|
43
58
|
import { renderProducers } from "../../policies/producers.js";
|
|
44
|
-
import { ADOPTION_LEDGER_DIRNAME, CLAUDE_CODE_HARNESS, checkOperatorApprovalMarkers, checkPersistedReport, defaultReportsDir, delegationMarkerPathFor, harnessAllowed, listPersistedReports, matchLedgerEntries, parseAutoApprove, readActiveClaim, recordPermissionModeObservation, selectNewestStrictSessionReport, verifyDelegation, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
59
|
+
import { ADOPTION_LEDGER_DIRNAME, CLAUDE_CODE_HARNESS, checkOperatorApprovalMarkers, checkPersistedReport, defaultReportsDir, delegationMarkerPathFor, delegationReportPathFor, harnessAllowed, listPersistedReports, matchLedgerEntries, parseAutoApprove, readActiveClaim, recordPermissionModeObservation, selectNewestStrictSessionReport, verifyDelegation, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
45
60
|
import { resolveMode, toPackageMode, } from "../../policy-packs/builtin/understanding-before-execution.js";
|
|
46
|
-
import { signingKeyExists } from "../../runtime/approval-signing.js";
|
|
61
|
+
import { sha256Hex, signingKeyExists } from "../../runtime/approval-signing.js";
|
|
47
62
|
import { findLatestParseError, renderMalformedSectionsNotice } from "../approve/understanding.js";
|
|
48
63
|
import { persistStdinReport } from "../approve/stdin-report.js";
|
|
49
64
|
import { attemptAutoApproval, AUTO_APPROVE_LEDGER_SOURCE } from "./auto-approve-path.js";
|
|
@@ -719,6 +734,14 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
719
734
|
stderr.write("harness pack hook: delegation present but signing key absent; not verified\n");
|
|
720
735
|
}
|
|
721
736
|
else {
|
|
737
|
+
// The conventional launcher-report path (a plain function of
|
|
738
|
+
// `childSessionId`, never a value read from the payload or from
|
|
739
|
+
// the delegation file itself): offered unconditionally, exactly
|
|
740
|
+
// as `verifyDelegation` expects. It only affects the outcome when
|
|
741
|
+
// the delegation actually binds a `report=` segment; for every
|
|
742
|
+
// other delegation it is read and then never consulted (see the
|
|
743
|
+
// `reportPathHash !== undefined` guard inside `verifyDelegation`).
|
|
744
|
+
const launcherReportPath = delegationReportPathFor(generatedDir, childSessionId);
|
|
722
745
|
const verified = verifyDelegation({
|
|
723
746
|
generatedDir,
|
|
724
747
|
childSessionId,
|
|
@@ -735,31 +758,39 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
735
758
|
// so there is no resolved value to reuse without widening that
|
|
736
759
|
// shared runtime's return shape for one caller.
|
|
737
760
|
taskId: readActiveClaim(generatedDir),
|
|
761
|
+
launcherReportPath,
|
|
738
762
|
});
|
|
739
763
|
if (!verified.ok) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
764
|
+
// An absent, moved, or content-modified launcher report surfaces
|
|
765
|
+
// here through the verifier's own distinct reasons
|
|
766
|
+
// (`report_missing` / `report_path_mismatch` /
|
|
767
|
+
// `report_content_mismatch`), same as every other refusal: no
|
|
768
|
+
// special-casing needed now that this hook actually checks the
|
|
769
|
+
// fallback shape against a file. `verified.detail` names the
|
|
770
|
+
// expected path for the three file-state cases this hook can
|
|
771
|
+
// actually reach (absent / wrong path / wrong bytes). Two of
|
|
772
|
+
// `verifyDelegation`'s binding-shape branches are unreachable
|
|
773
|
+
// from here for different reasons, so their details are worth
|
|
774
|
+
// spelling out separately rather than lumping them together:
|
|
775
|
+
// "no report path offered" never fires from this call site,
|
|
776
|
+
// since this hook always offers the conventional path
|
|
777
|
+
// (`launcherReportPath` above is unconditional); "no
|
|
778
|
+
// reportContentHash to check against" IS reachable here, just
|
|
779
|
+
// not from a delegation this hook itself minted, since
|
|
780
|
+
// `writeDelegationMarker` refuses to produce that half-binding
|
|
781
|
+
// shape (a hand-signed marker can still carry it, as the
|
|
782
|
+
// half-binding test in
|
|
783
|
+
// understanding-before-execution-delegation.test.ts shows), and
|
|
784
|
+
// its detail names the delegation marker's own path, not the
|
|
785
|
+
// report path, since there is no report path yet to name.
|
|
786
|
+
stderr.write(`harness pack hook: delegation for ${childSessionId} refused: ${verified.reason}: ${verified.detail}\n`);
|
|
756
787
|
}
|
|
757
788
|
else {
|
|
758
789
|
delegation = { parentSessionId: verified.parentSessionId };
|
|
759
790
|
// Key two: the child's own report. Already on disk and pending
|
|
760
791
|
// (the child was denied once and its report has since been
|
|
761
|
-
// captured) means there is nothing to scan for, so
|
|
762
|
-
//
|
|
792
|
+
// captured) means there is nothing to scan for, so neither
|
|
793
|
+
// channel below is read at all.
|
|
763
794
|
// `childSessionId`, not the outer `sessionId`: this whole block
|
|
764
795
|
// decides the PAYLOAD's session, and the two only coincide
|
|
765
796
|
// because the env fallback never wins on a path that carries a
|
|
@@ -767,93 +798,184 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
767
798
|
// branch looks for bound to the same session the delegation,
|
|
768
799
|
// the scan and the adoption ledger are all keyed by.
|
|
769
800
|
const existing = selectNewestStrictSessionReport(listPersistedReports(reportsDir), childSessionId);
|
|
801
|
+
// A report-bound delegation (the `--report` fallback shape,
|
|
802
|
+
// `verified.reportPathHash !== undefined`) never falls through
|
|
803
|
+
// to the transcript scan: the launcher already supplied and
|
|
804
|
+
// proved (via the verify call above) the report the child would
|
|
805
|
+
// otherwise have to emit into its own transcript, so scanning
|
|
806
|
+
// for a second copy would be pointless and would let a
|
|
807
|
+
// report-bound delegation ALSO be satisfied by whatever the
|
|
808
|
+
// child happens to write, which defeats the point of the
|
|
809
|
+
// launcher fixing the content.
|
|
770
810
|
if (existing === null || existing.approvalStatus !== "pending") {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const recorded = recordAdoptedEntry(adoptedPath, scan.entryId);
|
|
811
|
-
if (!recorded.ok) {
|
|
812
|
-
stderr.write(`harness pack hook: could not record transcript entry ${scan.entryId} as adopted for session ${childSessionId} (${recorded.detail}); nothing was persisted\n`);
|
|
811
|
+
if (verified.reportPathHash !== undefined) {
|
|
812
|
+
// The bytes `verifyDelegation` already proved present,
|
|
813
|
+
// path-matched, and content-matched against the bound
|
|
814
|
+
// hash, returned verbatim on its ok shape. There is NO
|
|
815
|
+
// second read of `launcherReportPath` here on purpose: a
|
|
816
|
+
// second read would be a second trust decision, reopening
|
|
817
|
+
// the exact race (a rewrite between verification and
|
|
818
|
+
// persist) that returning the verified bytes closes
|
|
819
|
+
// structurally instead of by re-checking a hash.
|
|
820
|
+
const reportContent = verified.reportContent;
|
|
821
|
+
if (reportContent === undefined) {
|
|
822
|
+
// Cannot happen given the invariant `reportContent` is
|
|
823
|
+
// set whenever `reportPathHash` is (see the type's own
|
|
824
|
+
// doc comment); fail closed rather than persist nothing
|
|
825
|
+
// we can vouch for.
|
|
826
|
+
stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} at ${launcherReportPath} was verified but no content was returned; nothing was persisted\n`);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
const contentHash = sha256Hex(reportContent);
|
|
830
|
+
// Adoption keyed by the report's CONTENT hash, in the
|
|
831
|
+
// SAME once-per-session ledger the transcript scan uses,
|
|
832
|
+
// namespaced with a `report:` prefix so the two id shapes
|
|
833
|
+
// (`uuid:...` / `digest:...` for a transcript entry,
|
|
834
|
+
// `report:...` here) can never collide. Without this a
|
|
835
|
+
// launcher report could mint a fresh marker every time an
|
|
836
|
+
// earlier one expired, for as long as the delegation
|
|
837
|
+
// itself stayed valid: the delegation's TTL would
|
|
838
|
+
// silently become the report's own re-mint budget instead
|
|
839
|
+
// of a one-shot authorization.
|
|
840
|
+
const adoptedPath = adoptedEntriesPathFor(generatedDir, childSessionId);
|
|
841
|
+
const adopted = readAdoptedEntries(adoptedPath);
|
|
842
|
+
const entryId = `report:${contentHash}`;
|
|
843
|
+
if (!adopted.ok) {
|
|
844
|
+
// Fail closed: without the ledger we cannot tell a
|
|
845
|
+
// fresh report from one this session already spent.
|
|
846
|
+
stderr.write(`harness pack hook: the adopted-entry ledger at ${adoptedPath} could not be read (${adopted.detail}); refusing to adopt a launcher-supplied report that may already have been adopted for session ${childSessionId}\n`);
|
|
847
|
+
}
|
|
848
|
+
else if (adopted.ids.has(entryId)) {
|
|
849
|
+
stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} was already adopted by an earlier mint; refusing to re-mint from the same report (issue a fresh delegation with a new report to authorize another mint)\n`);
|
|
813
850
|
}
|
|
814
851
|
else {
|
|
815
|
-
//
|
|
816
|
-
//
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
//
|
|
820
|
-
//
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
markdown: scan.markdown,
|
|
825
|
-
reportsDir,
|
|
826
|
-
sessionId: childSessionId,
|
|
827
|
-
now: new Date(),
|
|
828
|
-
mode: toPackageMode(resolveMode(declared).mode),
|
|
829
|
-
});
|
|
830
|
-
if (persisted.ok) {
|
|
831
|
-
stderr.write(`harness pack hook: captured the Understanding Report for session ${childSessionId} from its own transcript (line ${scan.lineIndex}, after ${scan.waitedMs}ms) and persisted it pending at ${persisted.filePath}\n`);
|
|
852
|
+
// RECORD THE ADOPTION FIRST, then persist, same ordering
|
|
853
|
+
// and same reasoning as the transcript-scan path below:
|
|
854
|
+
// a failed ledger write costs one blocked call and
|
|
855
|
+
// leaves the report re-adoptable, while the reverse
|
|
856
|
+
// order would leave a persisted, mintable report behind
|
|
857
|
+
// an unrecorded adoption.
|
|
858
|
+
const recorded = recordAdoptedEntry(adoptedPath, entryId);
|
|
859
|
+
if (!recorded.ok) {
|
|
860
|
+
stderr.write(`harness pack hook: could not record the launcher-supplied report as adopted for session ${childSessionId} (${recorded.detail}); nothing was persisted\n`);
|
|
832
861
|
}
|
|
833
862
|
else {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
863
|
+
const persisted = persistStdinReport({
|
|
864
|
+
markdown: reportContent,
|
|
865
|
+
reportsDir,
|
|
866
|
+
sessionId: childSessionId,
|
|
867
|
+
now: new Date(),
|
|
868
|
+
mode: toPackageMode(resolveMode(declared).mode),
|
|
869
|
+
});
|
|
870
|
+
if (persisted.ok) {
|
|
871
|
+
stderr.write(`harness pack hook: adopted the launcher-supplied report for session ${childSessionId} from ${launcherReportPath} and persisted it pending at ${persisted.filePath}\n`);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
// The entry stays adopted: the launcher's report is
|
|
875
|
+
// fixed content, so re-reading it would fail to
|
|
876
|
+
// parse identically forever. Unlike the transcript
|
|
877
|
+
// path this is not recoverable by a child retry
|
|
878
|
+
// (the child does not control the launcher's file),
|
|
879
|
+
// so no retry instruction is set here.
|
|
880
|
+
stderr.write(`harness pack hook: the launcher-supplied report for session ${childSessionId} did not parse (${persisted.reason}); nothing was persisted\n`);
|
|
881
|
+
}
|
|
844
882
|
}
|
|
845
883
|
}
|
|
846
884
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
885
|
+
// The transcript scan below never runs for a report-bound
|
|
886
|
+
// delegation: this `if` branch is its own complete
|
|
887
|
+
// report-capture attempt.
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
const transcriptPath = typeof event.transcript_path === "string" && event.transcript_path.length > 0
|
|
891
|
+
? event.transcript_path
|
|
892
|
+
: null;
|
|
893
|
+
// The once-per-session adoption ledger, read only on the path
|
|
894
|
+
// that can actually capture something.
|
|
895
|
+
const adoptedPath = adoptedEntriesPathFor(generatedDir, childSessionId);
|
|
896
|
+
const adopted = transcriptPath === null
|
|
897
|
+
? { ok: true, ids: new Set() }
|
|
898
|
+
: readAdoptedEntries(adoptedPath);
|
|
899
|
+
if (transcriptPath === null) {
|
|
900
|
+
stderr.write(`harness pack hook: delegation for ${childSessionId} is valid but the payload carries no transcript_path; the child's report cannot be captured\n`);
|
|
901
|
+
}
|
|
902
|
+
else if (!adopted.ok) {
|
|
903
|
+
// Fail closed: without the ledger we cannot tell a fresh
|
|
904
|
+
// report from one this session already spent.
|
|
905
|
+
stderr.write(`harness pack hook: the adopted-entry ledger at ${adoptedPath} could not be read (${adopted.detail}); refusing to capture a transcript entry that may already have been adopted for session ${childSessionId}\n`);
|
|
852
906
|
}
|
|
853
907
|
else {
|
|
854
|
-
|
|
908
|
+
const scan = await scanTranscriptForReport({
|
|
909
|
+
transcriptPath,
|
|
910
|
+
sessionId: childSessionId,
|
|
911
|
+
maxWaitMs: autoCfg.reportScan.maxWaitMs,
|
|
912
|
+
adopted: adopted.ids,
|
|
913
|
+
...(opts.reportScanClock?.now !== undefined
|
|
914
|
+
? { now: opts.reportScanClock.now }
|
|
915
|
+
: {}),
|
|
916
|
+
...(opts.reportScanClock?.sleep !== undefined
|
|
917
|
+
? { sleep: opts.reportScanClock.sleep }
|
|
918
|
+
: {}),
|
|
919
|
+
...(opts.reportScanClock?.pollMs !== undefined
|
|
920
|
+
? { pollMs: opts.reportScanClock.pollMs }
|
|
921
|
+
: {}),
|
|
922
|
+
});
|
|
923
|
+
if (scan.found) {
|
|
924
|
+
// RECORD THE ADOPTION FIRST, then persist. In this order a
|
|
925
|
+
// failed ledger write costs one blocked call and leaves the
|
|
926
|
+
// entry re-scannable, while the reverse order would leave a
|
|
927
|
+
// persisted, mintable report behind an unrecorded adoption:
|
|
928
|
+
// exactly the replay this ledger exists to stop.
|
|
929
|
+
const recorded = recordAdoptedEntry(adoptedPath, scan.entryId);
|
|
930
|
+
if (!recorded.ok) {
|
|
931
|
+
stderr.write(`harness pack hook: could not record transcript entry ${scan.entryId} as adopted for session ${childSessionId} (${recorded.detail}); nothing was persisted\n`);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
// Reuses the SAME persister `harness approve
|
|
935
|
+
// understanding` uses for a heredoc-attached report, so
|
|
936
|
+
// the capture is session-bound, `pending`, and validated
|
|
937
|
+
// by one parser rather than a second one written here.
|
|
938
|
+
// A real `new Date()` on purpose: the persisted report
|
|
939
|
+
// must sort as the newest strict-session report against
|
|
940
|
+
// whatever is already on disk, which a caller-injected
|
|
941
|
+
// clock could not guarantee.
|
|
942
|
+
const persisted = persistStdinReport({
|
|
943
|
+
markdown: scan.markdown,
|
|
944
|
+
reportsDir,
|
|
945
|
+
sessionId: childSessionId,
|
|
946
|
+
now: new Date(),
|
|
947
|
+
mode: toPackageMode(resolveMode(declared).mode),
|
|
948
|
+
});
|
|
949
|
+
if (persisted.ok) {
|
|
950
|
+
stderr.write(`harness pack hook: captured the Understanding Report for session ${childSessionId} from its own transcript (line ${scan.lineIndex}, after ${scan.waitedMs}ms) and persisted it pending at ${persisted.filePath}\n`);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
// The entry stays adopted: re-reading a report that
|
|
954
|
+
// does not parse would fail identically forever, so
|
|
955
|
+
// the child needs a NEW one, which is what the block's
|
|
956
|
+
// retry instruction asks for. Set the same flag the
|
|
957
|
+
// timeout branch sets, for that reason: the retry
|
|
958
|
+
// sentence reads correctly for this case too, and
|
|
959
|
+
// without it the block below would ask for a retry
|
|
960
|
+
// implicitly while never saying so.
|
|
961
|
+
reportScanTimedOut = true;
|
|
962
|
+
stderr.write(`harness pack hook: the transcript report for session ${childSessionId} did not parse (${persisted.reason}); nothing was persisted\n`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
else if (scan.reason === "timeout") {
|
|
967
|
+
reportScanTimedOut = true;
|
|
968
|
+
stderr.write(scan.adoptedOnly === true
|
|
969
|
+
? `harness pack hook: the newest Understanding Report entry in the transcript for session ${childSessionId} was already adopted; emit a fresh report\n`
|
|
970
|
+
: `harness pack hook: no Understanding Report for session ${childSessionId} reached its transcript within ${autoCfg.reportScan.maxWaitMs}ms (waited ${scan.waitedMs}ms); blocking and asking the child to re-emit and retry\n`);
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
stderr.write(`harness pack hook: the transcript at ${transcriptPath} exists but could not be read; the report for session ${childSessionId} cannot be captured\n`);
|
|
974
|
+
}
|
|
855
975
|
}
|
|
856
976
|
}
|
|
977
|
+
// Closes the `if (verified.reportPathHash !== undefined) { ... }
|
|
978
|
+
// else { <transcript scan> }` branch opened above.
|
|
857
979
|
}
|
|
858
980
|
}
|
|
859
981
|
}
|