@lannguyensi/harness 0.56.0 → 0.58.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/README.md +1 -1
  3. package/dist/cli/apply/apply.js +4 -3
  4. package/dist/cli/apply/apply.js.map +1 -1
  5. package/dist/cli/apply/generate-codex-config.js +3 -3
  6. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  7. package/dist/cli/apply/generate-memory-index.js +20 -10
  8. package/dist/cli/apply/generate-memory-index.js.map +1 -1
  9. package/dist/cli/apply/generate-settings.d.ts +7 -0
  10. package/dist/cli/apply/generate-settings.js +10 -1
  11. package/dist/cli/apply/generate-settings.js.map +1 -1
  12. package/dist/cli/approve/understanding.d.ts +4 -4
  13. package/dist/cli/doctor/format.js +68 -2
  14. package/dist/cli/doctor/format.js.map +1 -1
  15. package/dist/cli/doctor/index.d.ts +6 -0
  16. package/dist/cli/doctor/index.js +214 -28
  17. package/dist/cli/doctor/index.js.map +1 -1
  18. package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +83 -0
  19. package/dist/cli/doctor/session-start-preflight-setup-version.js +114 -0
  20. package/dist/cli/doctor/session-start-preflight-setup-version.js.map +1 -0
  21. package/dist/cli/doctor/types.d.ts +35 -4
  22. package/dist/cli/doctor/types.js +2 -1
  23. package/dist/cli/doctor/types.js.map +1 -1
  24. package/dist/cli/doctor/ug-delegations.js +9 -0
  25. package/dist/cli/doctor/ug-delegations.js.map +1 -1
  26. package/dist/cli/explain-policy.d.ts +67 -0
  27. package/dist/cli/explain-policy.js +140 -3
  28. package/dist/cli/explain-policy.js.map +1 -1
  29. package/dist/cli/gc/index.js +28 -14
  30. package/dist/cli/gc/index.js.map +1 -1
  31. package/dist/cli/index.d.ts +1 -0
  32. package/dist/cli/index.js +10 -7
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init/composer.js +4 -3
  35. package/dist/cli/init/composer.js.map +1 -1
  36. package/dist/cli/init/dependencies.js +15 -9
  37. package/dist/cli/init/dependencies.js.map +1 -1
  38. package/dist/cli/init/templates.d.ts +1 -0
  39. package/dist/cli/init/templates.js +30 -4
  40. package/dist/cli/init/templates.js.map +1 -1
  41. package/dist/cli/list.js +19 -0
  42. package/dist/cli/list.js.map +1 -1
  43. package/dist/cli/loader.js +16 -1
  44. package/dist/cli/loader.js.map +1 -1
  45. package/dist/cli/pack/hook-bootstrap.js +2 -1
  46. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  47. package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -32
  48. package/dist/cli/pack/hook-codex-user-prompt-submit.js +11 -118
  49. package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +1 -1
  50. package/dist/cli/pack/hook-pre-tool-use.js +34 -7
  51. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  52. package/dist/cli/pack/hook-solution-acceptance.d.ts +55 -0
  53. package/dist/cli/pack/hook-solution-acceptance.js +222 -16
  54. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  55. package/dist/cli/pack/hook-stay-in-scope.d.ts +5 -26
  56. package/dist/cli/pack/hook-stay-in-scope.js +62 -274
  57. package/dist/cli/pack/hook-stay-in-scope.js.map +1 -1
  58. package/dist/cli/pack/hook-track-active-claim.d.ts +1 -0
  59. package/dist/cli/pack/hook-track-active-claim.js +107 -43
  60. package/dist/cli/pack/hook-track-active-claim.js.map +1 -1
  61. package/dist/cli/session-start/index.d.ts +32 -1
  62. package/dist/cli/session-start/index.js +142 -12
  63. package/dist/cli/session-start/index.js.map +1 -1
  64. package/dist/cli/validate/checks.d.ts +0 -2
  65. package/dist/cli/validate/checks.js +30 -30
  66. package/dist/cli/validate/checks.js.map +1 -1
  67. package/dist/io/harness-lock.js +11 -1
  68. package/dist/io/harness-lock.js.map +1 -1
  69. package/dist/io/lock.d.ts +26 -0
  70. package/dist/io/lock.js +25 -0
  71. package/dist/io/lock.js.map +1 -1
  72. package/dist/io/project-name.d.ts +115 -0
  73. package/dist/io/project-name.js +158 -0
  74. package/dist/io/project-name.js.map +1 -0
  75. package/dist/io/read-json-dir-entries.js +10 -0
  76. package/dist/io/read-json-dir-entries.js.map +1 -1
  77. package/dist/io/version-compare.d.ts +59 -0
  78. package/dist/io/version-compare.js +67 -0
  79. package/dist/io/version-compare.js.map +1 -1
  80. package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +50 -0
  81. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +151 -0
  82. package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +1 -0
  83. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +43 -4
  84. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +43 -39
  85. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  86. package/dist/policy-packs/builtin/solution-acceptance.js +4 -2
  87. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
  88. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +15 -0
  89. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -1
  90. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +5 -7
  91. package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +1 -1
  92. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +23 -0
  93. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -1
  94. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +8 -17
  95. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -1
  96. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +129 -0
  97. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +166 -0
  98. package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +1 -0
  99. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +129 -0
  100. package/dist/policy-packs/builtin/understanding-before-execution.js +44 -57
  101. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  102. package/dist/policy-packs/version-check.d.ts +8 -1
  103. package/dist/policy-packs/version-check.js +10 -6
  104. package/dist/policy-packs/version-check.js.map +1 -1
  105. package/dist/probes/memory.d.ts +34 -0
  106. package/dist/probes/memory.js +87 -14
  107. package/dist/probes/memory.js.map +1 -1
  108. package/dist/runtime/git-context.d.ts +125 -0
  109. package/dist/runtime/git-context.js +164 -1
  110. package/dist/runtime/git-context.js.map +1 -1
  111. package/dist/runtime/index.d.ts +1 -1
  112. package/dist/runtime/index.js +1 -1
  113. package/dist/runtime/index.js.map +1 -1
  114. package/dist/runtime/task-providers/agent-tasks.d.ts +90 -0
  115. package/dist/runtime/task-providers/agent-tasks.js +231 -0
  116. package/dist/runtime/task-providers/agent-tasks.js.map +1 -0
  117. package/dist/runtime/workflow-policies.d.ts +1 -0
  118. package/dist/runtime/workflow-policies.js +8 -8
  119. package/dist/runtime/workflow-policies.js.map +1 -1
  120. package/dist/schema/index.d.ts +19 -0
  121. package/dist/schema/index.js +5 -0
  122. package/dist/schema/index.js.map +1 -1
  123. package/dist/schema/session-start-preflight.d.ts +10 -0
  124. package/dist/schema/session-start-preflight.js +106 -0
  125. package/dist/schema/session-start-preflight.js.map +1 -0
  126. package/package.json +7 -2
@@ -0,0 +1,114 @@
1
+ // Doctor finding for task 6993d9b5 (tracker id, harness repo): warns
2
+ // when `session_start_preflight.setup: true` is set but the resolved
3
+ // `preflight` binary is below the release that made `--setup` build
4
+ // code, not just install dependencies (agent-preflight 0.6.0, tag
5
+ // v0.6.0). On 0.5.0 `--setup` is dependency-install only, so an
6
+ // operator who enabled the knob against that version pays install time
7
+ // plus the `--setup` trust exposure (see session-start-preflight.ts's
8
+ // SECURITY note) for zero build benefit, with nothing today telling
9
+ // them so.
10
+ //
11
+ // Deliberately independent of the `git-preflight` manifest hook's own
12
+ // `min_version`/`version_command` (checked generically by
13
+ // `checkHookVersion` in ./index.ts): that floor is whatever the
14
+ // operator's own manifest declares, and existing manifests generated
15
+ // before task 6993d9b5 still carry the pre-existing 0.2.0 floor (bumped
16
+ // to `GIT_PREFLIGHT_HOOK_MIN_VERSION`, src/cli/init/templates.ts, in the
17
+ // `init` template only going forward, task 6993d9b5's second commit).
18
+ // This check hardcodes the build-capable floor so an operator on an
19
+ // unregenerated manifest still gets the warning the moment they flip
20
+ // `setup: true`, rather than only after re-running `harness init`.
21
+ //
22
+ // BY DESIGN: on a freshly generated manifest with `setup: true` probed
23
+ // against a stale preflight, this check and the generic `hooks[]`
24
+ // `min_version` walk both fire and both count toward `warningCount` (see
25
+ // `docs/CLI.md`'s VERSION CAVEAT). The two are not a duplicate of the
26
+ // same finding: the hook floor guards the `git-preflight` hook itself,
27
+ // this check guards the `--setup` build-step feature specifically, and
28
+ // a stale preflight breaks both independently.
29
+ //
30
+ // SPLIT FROM THE HOOK FLOOR (task 65952a0c, docs/decisions/2026-09-08-
31
+ // preflight-floors.md): this check's floor,
32
+ // `SESSION_START_PREFLIGHT_SETUP_BUILD_MIN_VERSION`, and the hook
33
+ // floor above, `GIT_PREFLIGHT_HOOK_MIN_VERSION`, were ONE shared
34
+ // constant through task 6993d9b5. Both are `"0.6.0"` today and can
35
+ // diverge on a future bump that applies to only one rationale; see the
36
+ // ADR for the bump rule each carries.
37
+ //
38
+ // Silent (returns `undefined`) whenever `session_start_preflight.setup`
39
+ // is `false`/absent: the trust exposure and the install/build cost only
40
+ // exist once the knob is actually on, mirroring every other advisory
41
+ // doctor check's "no opinion when the feature isn't in use" gate.
42
+ import { parseProbedVersion, compareVersionFloor } from "../../io/version-compare.js";
43
+ import { PREFLIGHT_BIN } from "../session-start/index.js";
44
+ import { SESSION_START_PREFLIGHT_SETUP_BUILD_MIN_VERSION } from "../../schema/session-start-preflight.js";
45
+ /**
46
+ * The probe command run against the `preflight` binary on PATH. Matches
47
+ * the `version_command` the shipped `git-preflight` hook template
48
+ * declares (src/cli/init/templates.ts), same binary and `--version`
49
+ * flag, but is not read FROM the manifest's hook entry: an operator
50
+ * could rename/remove that hook while `session_start_preflight.setup`
51
+ * stays on, and the check should still probe the binary the producer
52
+ * (`harness session-start preflight`) actually spawns.
53
+ */
54
+ export const PREFLIGHT_SETUP_VERSION_COMMAND = [PREFLIGHT_BIN, "--version"];
55
+ /**
56
+ * Pure: manifest + versionProbe in, a finding out (or `undefined` when
57
+ * there is nothing to flag). Mirrors `checkHookVersion`'s probe/parse
58
+ * idiom (src/cli/doctor/index.ts) so the warning vocabulary
59
+ * (`below_floor` / `probe_failed` / `parse_failed`) and version-regex
60
+ * parsing stay consistent across both checks, without importing from
61
+ * index.ts (which would create a cycle back into this module's own
62
+ * caller).
63
+ *
64
+ * `projectName` (task c88461c1, review round 3 residual; task
65
+ * `1c4eb3ea`): OPTIONAL third argument, carried onto every returned finding
66
+ * unchanged (see {@link SessionStartPreflightSetupVersionFinding.projectName}'s
67
+ * doc comment). `doctor()` always passes it (the SAME name it derived
68
+ * for its own scoped load, or `null`); every pre-existing direct call
69
+ * to this pure function in its own unit tests omits it, so those
70
+ * findings' shape is unchanged.
71
+ */
72
+ export function checkSessionStartPreflightSetupVersion(manifest, versionProbe, projectName) {
73
+ if (!manifest.session_start_preflight.setup)
74
+ return undefined;
75
+ const withProjectName = (finding) => projectName !== undefined ? { ...finding, projectName } : finding;
76
+ const required = SESSION_START_PREFLIGHT_SETUP_BUILD_MIN_VERSION;
77
+ const stdout = versionProbe(PREFLIGHT_SETUP_VERSION_COMMAND);
78
+ if (stdout === null) {
79
+ return withProjectName({
80
+ kind: "probe_failed",
81
+ actualVersion: null,
82
+ requiredVersion: required,
83
+ message: `session_start_preflight.setup is enabled but the installed preflight version could not ` +
84
+ `be determined (probe for "${PREFLIGHT_SETUP_VERSION_COMMAND.join(" ")}" failed); the ` +
85
+ `build step needs preflight >= ${required}`,
86
+ });
87
+ }
88
+ // parseProbedVersion/compareVersionFloor (not plain compareNumericVersions):
89
+ // a preflight release candidate of the floor (e.g. "0.6.0-rc.1") must not
90
+ // count as build-capable. See docs/decisions/2026-09-08-preflight-floors.md.
91
+ const parsed = parseProbedVersion(stdout);
92
+ if (!parsed) {
93
+ return withProjectName({
94
+ kind: "parse_failed",
95
+ actualVersion: null,
96
+ requiredVersion: required,
97
+ message: `session_start_preflight.setup is enabled but the installed preflight version could not ` +
98
+ `be parsed from "${stdout.trim()}"; the build step needs preflight >= ${required}`,
99
+ });
100
+ }
101
+ const { version: actual, isPrerelease, token } = parsed;
102
+ if (compareVersionFloor(actual, isPrerelease, required) < 0) {
103
+ return withProjectName({
104
+ kind: "below_floor",
105
+ actualVersion: actual,
106
+ requiredVersion: required,
107
+ message: `session_start_preflight.setup is enabled but installed preflight v${token} < ${required}: ` +
108
+ `--setup on v${token} is dependency-install only (no build step); upgrade preflight ` +
109
+ `(npm i -g @lannguyensi/agent-preflight) or set session_start_preflight.setup: false`,
110
+ });
111
+ }
112
+ return undefined;
113
+ }
114
+ //# sourceMappingURL=session-start-preflight-setup-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start-preflight-setup-version.js","sourceRoot":"","sources":["../../../src/cli/doctor/session-start-preflight-setup-version.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,7 @@ import type { UgInflightSection } from "./ug-inflight.js";
15
15
  import type { SettingsDriftSection } from "./settings-drift.js";
16
16
  import type { AutoApproveModeWarning } from "./auto-approve-mode.js";
17
17
  import type { BypassWithoutAutoApproveFinding } from "./bypass-without-auto-approve.js";
18
+ import type { SessionStartPreflightSetupVersionFinding } from "./session-start-preflight-setup-version.js";
18
19
  import type { CodexConfigDriftSection } from "./codex-config-drift.js";
19
20
  /**
20
21
  * Phase 6 #6 follow-up — doctor target identifier. Distinct from
@@ -84,7 +85,21 @@ export type HookVersionReport = {
84
85
  * classifies its outcome, so there is no warn case without one.
85
86
  */
86
87
  kind: "below_floor" | "probe_failed" | "parse_failed";
87
- /** Parsed installed version, when the probe succeeded. Null when the probe failed or its stdout didn't parse. */
88
+ /**
89
+ * Parsed installed version, when the probe succeeded. Null when
90
+ * the probe failed or its stdout didn't parse. Always the
91
+ * NUMERIC run (`parseProbedVersion`'s `version` field, e.g.
92
+ * "0.6.0"), never the probed prerelease/build suffix: for a
93
+ * `below_floor` finding caused by a prerelease of the floor
94
+ * (e.g. probed "0.6.0-rc.1" against a "0.6.0" `min_version`),
95
+ * `actualVersion` therefore equals `hook.min_version` even
96
+ * though `kind` is `"below_floor"`: the numeric components tie,
97
+ * and `compareVersionFloor`'s prerelease tie-break is what
98
+ * actually failed the floor. `message` carries the full probed
99
+ * token (including the suffix) for the human-facing distinction;
100
+ * this field's JSON shape does not gain a new field for it. See
101
+ * docs/decisions/2026-09-08-preflight-floors.md.
102
+ */
88
103
  actualVersion: string | null;
89
104
  message: string;
90
105
  };
@@ -171,6 +186,12 @@ export interface PolicyPackConfigIssue {
171
186
  export interface PolicyPackVersionGapReport {
172
187
  name: string;
173
188
  declaredMinVersion: string;
189
+ /**
190
+ * Numeric run only (never the prerelease or build suffix), so a
191
+ * `below_floor` gap from a prerelease of the floor shows an
192
+ * `actualVersion` equal to `declaredMinVersion`; `message` carries the
193
+ * full probed token. Same trap as `HookVersionReport.actualVersion`.
194
+ */
174
195
  actualVersion: string | null;
175
196
  message: string;
176
197
  }
@@ -435,6 +456,15 @@ export interface DoctorReport {
435
456
  * bypass-without-auto-approve.ts.
436
457
  */
437
458
  ugBypassWithoutAutoApprove?: BypassWithoutAutoApproveFinding;
459
+ /**
460
+ * `session_start_preflight.setup: true` against a `preflight` binary
461
+ * below the build-capable release (task 6993d9b5). Present only when
462
+ * `setup` is enabled AND the probed/parsed version is below
463
+ * `SESSION_START_PREFLIGHT_SETUP_BUILD_MIN_VERSION` (or the probe
464
+ * failed/could not be parsed); always a warning (`⚠`) when present,
465
+ * see session-start-preflight-setup-version.ts.
466
+ */
467
+ sessionStartPreflightSetupVersion?: SessionStartPreflightSetupVersionFinding;
438
468
  /**
439
469
  * `auto_approve` configured without `mode: grill_me` (agent-tasks
440
470
  * abfad738, follow-up of ADR
@@ -565,14 +595,15 @@ export interface DoctorReport {
565
595
  warningCount: number;
566
596
  }
567
597
  export type { NpmBinReport } from "./npm-bin-path.js";
568
- export type { ClaudeMcpRegistrationSection, ClaudeMcpEntryReport } from "./claude-mcp.js";
598
+ export type { SessionStartPreflightSetupVersionFinding } from "./session-start-preflight-setup-version.js";
599
+ export type { ClaudeMcpRegistrationSection, ClaudeMcpEntryReport, } from "./claude-mcp.js";
569
600
  export type { UnderstandingModeEnvDivergence } from "./understanding-mode-env.js";
570
- export type { UgAutoApprovalsSection, AutoApprovalListingEntry } from "./ug-auto-approvals.js";
601
+ export type { UgAutoApprovalsSection, AutoApprovalListingEntry, } from "./ug-auto-approvals.js";
571
602
  export type { UgDelegationsSection } from "./ug-delegations.js";
572
603
  export type { UgInflightSection } from "./ug-inflight.js";
573
604
  export type { AutoApproveModeWarning } from "./auto-approve-mode.js";
574
605
  export type { BypassWithoutAutoApproveFinding } from "./bypass-without-auto-approve.js";
575
606
  export type { SettingsDriftSection } from "./settings-drift.js";
576
607
  export type { CodexConfigDriftSection } from "./codex-config-drift.js";
577
- export type { ToolchainParitySection, ToolchainParityPeerReport } from "./toolchain-parity.js";
608
+ export type { ToolchainParitySection, ToolchainParityPeerReport, } from "./toolchain-parity.js";
578
609
  export type { Manifest, McpProbeResult, MemoryReport, StaleMemory, CodexTargetReport, RogueLedgerDb, };
@@ -8,6 +8,7 @@
8
8
  */
9
9
  export const KNOWN_DOCTOR_TARGETS = ["codex", "opencode"];
10
10
  export function isDoctorTarget(value) {
11
- return typeof value === "string" && KNOWN_DOCTOR_TARGETS.includes(value);
11
+ return (typeof value === "string" &&
12
+ KNOWN_DOCTOR_TARGETS.includes(value));
12
13
  }
13
14
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cli/doctor/types.ts"],"names":[],"mappings":"AAmBA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAGnE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAClG,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/cli/doctor/types.ts"],"names":[],"mappings":"AAoBA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAU,CAAC;AAGnE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACxB,oBAA0C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC5D,CAAC;AACJ,CAAC"}
@@ -58,6 +58,15 @@ const SESSION_ID_BASENAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
58
58
  export function buildUgDelegations(generatedDir, opts = {}) {
59
59
  const now = opts.now ?? new Date();
60
60
  const dir = path.join(generatedDir, DELEGATION_MARKER_DIRNAME);
61
+ let dirStat;
62
+ try {
63
+ dirStat = fs.lstatSync(dir);
64
+ }
65
+ catch {
66
+ return emptySection();
67
+ }
68
+ if (!dirStat.isDirectory())
69
+ return emptySection();
61
70
  let dirents;
62
71
  try {
63
72
  dirents = fs.readdirSync(dir, { withFileTypes: true });
@@ -1 +1 @@
1
- {"version":3,"file":"ug-delegations.js","sourceRoot":"","sources":["../../../src/cli/doctor/ug-delegations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ug-delegations.js","sourceRoot":"","sources":["../../../src/cli/doctor/ug-delegations.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,44 @@ export interface ExplainPolicyOptions extends EventInputSeams, LoaderOptions {
15
15
  kubeContext?: string;
16
16
  /** Inject the kube namespace (tests); bypasses `~/.kube/config`. */
17
17
  kubeNamespace?: string;
18
+ /**
19
+ * cwd `session_start_preflight.setup`'s per-repo project layer is
20
+ * derived from when `opts.project` is absent (task c88461c1, review
21
+ * round 2, decision D-021b): the same `deriveProjectName` helper
22
+ * `harness session-start preflight` and `harness doctor` also feed
23
+ * their own `loadManifest` call from, so all three consumers agree on
24
+ * which project layer applies to a given cwd instead of `explain-
25
+ * policy` silently reporting the base/machine value for a repo whose
26
+ * producer is actually reading a project layer. Defaults to
27
+ * `process.cwd()`; tests inject a fixture repo dir to stay hermetic
28
+ * against the real cwd.
29
+ */
30
+ cwd?: string;
18
31
  }
32
+ /**
33
+ * Which resolved layer decided `session_start_preflight.setup` (task
34
+ * c88461c1): "project" when the resolved project-override layer
35
+ * itself declares the key, "machine" when a resolved machine-override
36
+ * layer declares it (and no project layer does), "base" otherwise
37
+ * (the base manifest, or the schema default when nothing declares the
38
+ * key at all). Mirrors the loader's own last-wins precedence
39
+ * (`applyLayers(baseRaw, ...machineLayers, projectLayer)`,
40
+ * `src/overrides/merge.ts`): the project layer is checked first since
41
+ * it is the highest-precedence layer, then machine layers from the
42
+ * last-applied (highest-precedence) one back to the first.
43
+ *
44
+ * "unresolvable" (task c88461c1, review round 3 residual; task
45
+ * `1c4eb3ea` of the batch-44 follow-up run): the cwd-derived project-scoped SECOND
46
+ * load itself threw (a malformed project layer, an unreadable file).
47
+ * Deliberately NOT "base": the base/machine values were never actually
48
+ * re-read on this path (the plain load above them; see the round-3
49
+ * comment this residual corrected), so attributing the result to
50
+ * "base" would claim a value the base layer may not carry. `setup`
51
+ * degrades to `false` alongside it, matching the producer's own
52
+ * `setupEnabled` catch (`src/cli/session-start/index.ts`), so the two
53
+ * agree on a scoped-load failure instead of only on a healthy load.
54
+ */
55
+ export type SessionStartPreflightLayerSource = "base" | "machine" | "project" | "unresolvable";
19
56
  interface ExplainPolicyProjection {
20
57
  policy: string;
21
58
  description: string;
@@ -31,6 +68,36 @@ interface ExplainPolicyProjection {
31
68
  /** Static deletion-target verdict (task d03af8f6); null when the
32
69
  * event's command is not a recognized deletion verb. */
33
70
  deletion_target: DeletionTargetVerdict | null;
71
+ /**
72
+ * Resolved `session_start_preflight` config (task 30183330), shown
73
+ * only when the explained policy is one of the init-generated
74
+ * `preflight-before-*` gates (name starts with `preflight-before-`):
75
+ * this is the knob that decides whether the `harness session-start
76
+ * preflight` / `harness preflight` producer passes `--setup` to the
77
+ * `preflight run` invocation whose `ready:true` result these policies
78
+ * gate on. Omitted for every other policy; it has no bearing on
79
+ * their evaluation (review round 3 residual; task `1c4eb3ea`: the
80
+ * scoped SECOND load below this field's assignment site runs ONLY
81
+ * when this condition is already true, so a policy this block is
82
+ * never rendered for never pays for that extra load either).
83
+ * `source` (task c88461c1) names which resolved layer decided the
84
+ * value: `"base"` (also covers an injected `opts.manifest`, which
85
+ * carries no per-layer provenance to attribute), `"machine"`,
86
+ * `"project"` (the cwd-derived per-repo layer), or `"unresolvable"`
87
+ * (review round 3 residual; task `1c4eb3ea`: the scoped load itself
88
+ * threw, `setup` degrades to `false` alongside it). This function
89
+ * derives that project name itself, from `opts.cwd`, via the SAME
90
+ * `deriveProjectName` helper `harness session-start preflight` feeds
91
+ * through `LoaderOptions.project` (review round 2, decision D-021b:
92
+ * without this, an operator running `explain-policy` with no
93
+ * `--project` in a repo that DOES have a project layer would see the
94
+ * base/machine value while the producer itself reads the project
95
+ * layer, see src/cli/session-start/index.ts).
96
+ */
97
+ session_start_preflight?: {
98
+ setup: boolean;
99
+ source: SessionStartPreflightLayerSource;
100
+ };
34
101
  when: {
35
102
  declared: false;
36
103
  } | {
@@ -12,13 +12,63 @@
12
12
  // Distinct from `harness explain <policy> --trace`: that replays the
13
13
  // LAST recorded decision from the evidence ledger; `explain-policy`
14
14
  // evaluates a hypothetical event live and reads nothing from the ledger.
15
- import { stringify as stringifyYaml } from "yaml";
16
- import { classifyRisk, evaluateWhen, policyMatchesEvent, resolveDeletionTarget, resolveEnvironment, resolveKubeContext, } from "../runtime/index.js";
15
+ import * as fs from "node:fs";
16
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
17
+ import { classifyRisk, deriveProjectName, evaluateWhen, policyMatchesEvent, resolveDeletionTarget, resolveEnvironment, resolveKubeContext, } from "../runtime/index.js";
17
18
  import { extractShellCommand } from "../runtime/tool-name-aliases.js";
18
19
  import { DEFAULT_SAFE_DELETION_ROOTS } from "../schema/risk.js";
19
20
  import { loadEventEnvelope } from "./event-input.js";
20
21
  import { EX_USAGE, HarnessExitError } from "./exit-codes.js";
21
22
  import { loadManifest } from "./loader.js";
23
+ /**
24
+ * Does `filePath`'s raw YAML explicitly declare `session_start_preflight.setup`?
25
+ *
26
+ * "Declare" means KEY PRESENCE, not "holds a boolean" (review round 2,
27
+ * finding: a tombstone, `session_start_preflight: null` or `{setup:
28
+ * null}`, both honoured by `mergeValue` in src/overrides/merge.ts as
29
+ * "delete whatever a lower layer set, falling back to the schema
30
+ * default", is exactly as much a deliberate declaration by THIS layer
31
+ * as `setup: true`/`false` is; it just resolves to a different final
32
+ * value. Attributing a tombstoned layer's decision to whichever lower
33
+ * layer happens to also declare the key (or to "base" when none does)
34
+ * would name the WRONG layer as the one that decided the merged
35
+ * result.
36
+ */
37
+ function layerDeclaresSetup(filePath) {
38
+ try {
39
+ const raw = fs.readFileSync(filePath, "utf8");
40
+ const parsed = parseYaml(raw);
41
+ if (parsed === null || typeof parsed !== "object")
42
+ return false;
43
+ if (!("session_start_preflight" in parsed))
44
+ return false;
45
+ const block = parsed.session_start_preflight;
46
+ if (block === null)
47
+ return true; // whole-block tombstone
48
+ if (typeof block !== "object" || Array.isArray(block))
49
+ return false;
50
+ return "setup" in block;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ /**
57
+ * Re-reads the resolved layer files (independent of the already-merged
58
+ * `manifest` object, which no longer carries per-layer provenance once
59
+ * `applyLayers` has folded them together) to attribute the decided
60
+ * `session_start_preflight.setup` value to the layer that set it.
61
+ */
62
+ function resolveSessionStartPreflightSource(resolved) {
63
+ if (resolved.projectLayer !== null && layerDeclaresSetup(resolved.projectLayer)) {
64
+ return "project";
65
+ }
66
+ for (let i = resolved.machineLayers.length - 1; i >= 0; i--) {
67
+ if (layerDeclaresSetup(resolved.machineLayers[i]))
68
+ return "machine";
69
+ }
70
+ return "base";
71
+ }
22
72
  /**
23
73
  * Explain whether `policyName` applies to the event at `opts.eventPath`.
24
74
  *
@@ -27,12 +77,93 @@ import { loadManifest } from "./loader.js";
27
77
  * `loadEventEnvelope`) when the event file is missing or malformed.
28
78
  */
29
79
  export function explainPolicy(policyName, opts) {
30
- const manifest = opts.manifest ?? loadManifest(opts).manifest;
80
+ // `resolved` is only available on the real `loadManifest(opts)` path:
81
+ // an injected `opts.manifest` already IS the merged result and
82
+ // carries no per-layer provenance, so `session_start_preflight.source`
83
+ // falls back to "base" in that case (see the field's doc comment).
84
+ //
85
+ // PLAIN load (task c88461c1, review round 3, decision D-028): every
86
+ // check this verb performs against `manifest` below -- finding the
87
+ // named policy, trigger matching, the Risk Classifier, environment
88
+ // resolution, deletion-target resolution, and the `when:` evaluation
89
+ // -- reads the base/machine/explicit-`--project` manifest only, an
90
+ // explicit `opts.project` still wins outright but NOTHING is derived
91
+ // from cwd here. This mirrors `harness policy intercept`
92
+ // (src/cli/policy/intercept.ts) and `harness dry-run`
93
+ // (src/cli/dry-run.ts), the two enforcement-facing verbs that decide
94
+ // a policy's real trigger verdict: neither ever consults a derived
95
+ // project layer, so this manifest must not either, or an operator's
96
+ // "would this apply" answer could disagree with what actually
97
+ // enforces. Only `session_start_preflight.setup` (and its `source`
98
+ // attribution) below get a project-scoped SECOND load: routing a
99
+ // derived layer through THIS one, unrelated load would let it
100
+ // silently reach the policy engine this verb evaluates, for zero
101
+ // benefit to the one key that needs it.
102
+ let manifest;
103
+ let resolvedPaths;
104
+ if (opts.manifest) {
105
+ manifest = opts.manifest;
106
+ }
107
+ else {
108
+ const loaded = loadManifest(opts);
109
+ manifest = loaded.manifest;
110
+ resolvedPaths = loaded.resolved;
111
+ }
31
112
  const policy = manifest.policies.find((p) => p.name === policyName);
32
113
  if (!policy) {
33
114
  const available = manifest.policies.map((p) => p.name).join(", ") || "(none)";
34
115
  throw new HarnessExitError(`no policy named "${policyName}" declared; available: ${available}`, EX_USAGE);
35
116
  }
117
+ // `session_start_preflight.setup` / `source` (task c88461c1, review
118
+ // round 2 decision D-021b; scope narrowed to this one key, review
119
+ // round 3 decision D-028; gated on the name-prefix check below,
120
+ // review round 3 residual, task `1c4eb3ea`): an explicit
121
+ // `opts.project` still wins outright; otherwise derive the project
122
+ // name from `opts.cwd` (defaulting to `process.cwd()`) via the SAME
123
+ // `deriveProjectName` the two other producers reach through
124
+ // `resolveScopedProjectName` (src/runtime/git-context.ts; this verb keeps
125
+ // its own copy with an `undefined` fallback, task f1eb1c5c), so this
126
+ // verb's `setup`/`source` reflect the layer the producer would read for
127
+ // this cwd, WITHOUT letting that derived layer reach the policy evaluation above. An injected
128
+ // `opts.manifest` carries no per-layer provenance to re-derive from
129
+ // and always reports "base" (unchanged from before this task).
130
+ //
131
+ // ONLY COMPUTED FOR `preflight-before-*` POLICIES (moved behind this
132
+ // check, review round 3 residual, task `1c4eb3ea`): the block below
133
+ // is rendered into the projection ONLY for those policies (see
134
+ // `ExplainPolicyProjection.session_start_preflight`'s doc comment),
135
+ // so a caller explaining any OTHER policy no longer pays for a
136
+ // second manifest load whose result it can never see.
137
+ let sessionStartPreflightSetup = false;
138
+ let sessionStartPreflightSource = "base";
139
+ if (policy.name.startsWith("preflight-before-")) {
140
+ sessionStartPreflightSetup = manifest.session_start_preflight.setup;
141
+ sessionStartPreflightSource = resolvedPaths
142
+ ? resolveSessionStartPreflightSource(resolvedPaths)
143
+ : "base";
144
+ if (!opts.manifest) {
145
+ try {
146
+ const scoped = loadManifest({
147
+ ...opts,
148
+ project: opts.project ?? deriveProjectName(opts.cwd ?? process.cwd()) ?? undefined,
149
+ });
150
+ sessionStartPreflightSetup = scoped.manifest.session_start_preflight.setup;
151
+ sessionStartPreflightSource = resolveSessionStartPreflightSource(scoped.resolved);
152
+ }
153
+ catch {
154
+ // Review round 3 residual, task `1c4eb3ea`: degrade to
155
+ // setup:false / source:"unresolvable" rather than keeping the
156
+ // plain load's values, matching the producer's own
157
+ // `setupEnabled` catch (`src/cli/session-start/index.ts`),
158
+ // which degrades to `setup: false` on the identical failure
159
+ // instead of falling back to a base/machine value it never
160
+ // actually re-read on this path. "base" would misattribute
161
+ // the result to a layer this function never consulted here.
162
+ sessionStartPreflightSetup = false;
163
+ sessionStartPreflightSource = "unresolvable";
164
+ }
165
+ }
166
+ }
36
167
  const { event, envelope } = loadEventEnvelope(opts.eventPath, opts, "explain-policy");
37
168
  // Kube seams resolve together: if either is injected, skip the
38
169
  // `~/.kube/config` read entirely — same contract as `resolve-env`.
@@ -73,6 +204,12 @@ export function explainPolicy(policyName, opts) {
73
204
  classifier,
74
205
  environment,
75
206
  deletion_target: deletionTarget,
207
+ ...(policy.name.startsWith("preflight-before-") && {
208
+ session_start_preflight: {
209
+ setup: sessionStartPreflightSetup,
210
+ source: sessionStartPreflightSource,
211
+ },
212
+ }),
76
213
  when: whenEval
77
214
  ? {
78
215
  declared: true,
@@ -1 +1 @@
1
- {"version":3,"file":"explain-policy.js","sourceRoot":"","sources":["../../src/cli/explain-policy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"explain-policy.js","sourceRoot":"","sources":["../../src/cli/explain-policy.ts"],"names":[],"mappings":"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"}
@@ -86,6 +86,15 @@ function ageDays(nowMs, thenMs) {
86
86
  }
87
87
  /** Plain files in `dir` whose mtime is older than `cutoffMs`. */
88
88
  function staleFilesByMtime(dir, cutoffMs, nowMs, category) {
89
+ let dirStat;
90
+ try {
91
+ dirStat = fs.lstatSync(dir);
92
+ }
93
+ catch {
94
+ return { candidates: [], kept: 0 };
95
+ }
96
+ if (!dirStat.isDirectory())
97
+ return { candidates: [], kept: 0 };
89
98
  let names;
90
99
  try {
91
100
  names = fs.readdirSync(dir);
@@ -205,13 +214,24 @@ function sweepDelegations(generatedDir, cutoffMs, nowMs) {
205
214
  // and session-id-shaped, so the ledger pass below can tell "expired"
206
215
  // from "absent" from "still valid" without re-reading the file.
207
216
  const statusBySessionId = new Map();
208
- let delegationNames;
209
- try {
210
- delegationNames = fs.readdirSync(delegationsDir);
211
- }
212
- catch {
213
- delegationNames = [];
214
- }
217
+ const readRootNames = (dir) => {
218
+ let dirStat;
219
+ try {
220
+ dirStat = fs.lstatSync(dir);
221
+ }
222
+ catch {
223
+ return [];
224
+ }
225
+ if (!dirStat.isDirectory())
226
+ return [];
227
+ try {
228
+ return fs.readdirSync(dir);
229
+ }
230
+ catch {
231
+ return [];
232
+ }
233
+ };
234
+ const delegationNames = readRootNames(delegationsDir);
215
235
  for (const name of delegationNames) {
216
236
  if (!SESSION_ID_BASENAME_RE.test(name))
217
237
  continue;
@@ -229,13 +249,7 @@ function sweepDelegations(generatedDir, cutoffMs, nowMs) {
229
249
  kept += 1;
230
250
  }
231
251
  }
232
- let ledgerNames;
233
- try {
234
- ledgerNames = fs.readdirSync(ledgerDir);
235
- }
236
- catch {
237
- ledgerNames = [];
238
- }
252
+ const ledgerNames = readRootNames(ledgerDir);
239
253
  for (const name of ledgerNames) {
240
254
  if (!SESSION_ID_BASENAME_RE.test(name))
241
255
  continue;