@lannguyensi/harness 0.51.0 → 0.53.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 (95) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/cli/approve/understanding.d.ts +46 -0
  3. package/dist/cli/approve/understanding.js +50 -22
  4. package/dist/cli/approve/understanding.js.map +1 -1
  5. package/dist/cli/audit.d.ts +29 -0
  6. package/dist/cli/audit.js +130 -17
  7. package/dist/cli/audit.js.map +1 -1
  8. package/dist/cli/delegate/index.d.ts +63 -0
  9. package/dist/cli/delegate/index.js +313 -0
  10. package/dist/cli/delegate/index.js.map +1 -0
  11. package/dist/cli/doctor/auto-approve-mode.d.ts +37 -0
  12. package/dist/cli/doctor/auto-approve-mode.js +80 -0
  13. package/dist/cli/doctor/auto-approve-mode.js.map +1 -0
  14. package/dist/cli/doctor/bypass-without-auto-approve.d.ts +38 -0
  15. package/dist/cli/doctor/bypass-without-auto-approve.js +89 -0
  16. package/dist/cli/doctor/bypass-without-auto-approve.js.map +1 -0
  17. package/dist/cli/doctor/codex-config-drift.d.ts +28 -0
  18. package/dist/cli/doctor/codex-config-drift.js +240 -0
  19. package/dist/cli/doctor/codex-config-drift.js.map +1 -0
  20. package/dist/cli/doctor/format.js +39 -1
  21. package/dist/cli/doctor/format.js.map +1 -1
  22. package/dist/cli/doctor/index.js +68 -7
  23. package/dist/cli/doctor/index.js.map +1 -1
  24. package/dist/cli/doctor/settings-drift.d.ts +6 -0
  25. package/dist/cli/doctor/settings-drift.js +20 -6
  26. package/dist/cli/doctor/settings-drift.js.map +1 -1
  27. package/dist/cli/doctor/types.d.ts +54 -0
  28. package/dist/cli/doctor/types.js.map +1 -1
  29. package/dist/cli/doctor/ug-auto-approvals.js +18 -43
  30. package/dist/cli/doctor/ug-auto-approvals.js.map +1 -1
  31. package/dist/cli/doctor/ug-delegations.d.ts +26 -0
  32. package/dist/cli/doctor/ug-delegations.js +112 -0
  33. package/dist/cli/doctor/ug-delegations.js.map +1 -0
  34. package/dist/cli/doctor/understanding-mode-env.d.ts +2 -0
  35. package/dist/cli/doctor/understanding-mode-env.js +5 -2
  36. package/dist/cli/doctor/understanding-mode-env.js.map +1 -1
  37. package/dist/cli/gc/index.d.ts +13 -2
  38. package/dist/cli/gc/index.js +203 -1
  39. package/dist/cli/gc/index.js.map +1 -1
  40. package/dist/cli/index.js +133 -7
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/cli/init/composer.js +9 -0
  43. package/dist/cli/init/composer.js.map +1 -1
  44. package/dist/cli/init/profiles.d.ts +2 -2
  45. package/dist/cli/init/profiles.js +11 -0
  46. package/dist/cli/init/profiles.js.map +1 -1
  47. package/dist/cli/init/templates.d.ts +1 -1
  48. package/dist/cli/init/templates.js +11 -0
  49. package/dist/cli/init/templates.js.map +1 -1
  50. package/dist/cli/pack/approve-escape.js +16 -7
  51. package/dist/cli/pack/approve-escape.js.map +1 -1
  52. package/dist/cli/pack/auto-approve-path.d.ts +17 -0
  53. package/dist/cli/pack/auto-approve-path.js +56 -3
  54. package/dist/cli/pack/auto-approve-path.js.map +1 -1
  55. package/dist/cli/pack/hook-pre-tool-use.d.ts +25 -0
  56. package/dist/cli/pack/hook-pre-tool-use.js +335 -3
  57. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  58. package/dist/cli/pack/index.d.ts +1 -0
  59. package/dist/cli/pack/index.js +1 -0
  60. package/dist/cli/pack/index.js.map +1 -1
  61. package/dist/cli/pack/transcript-report-scan.d.ts +71 -0
  62. package/dist/cli/pack/transcript-report-scan.js +359 -0
  63. package/dist/cli/pack/transcript-report-scan.js.map +1 -0
  64. package/dist/cli/pack/upgrade.d.ts +32 -0
  65. package/dist/cli/pack/upgrade.js +273 -0
  66. package/dist/cli/pack/upgrade.js.map +1 -0
  67. package/dist/cli/smoke/index.d.ts +18 -1
  68. package/dist/cli/smoke/index.js +54 -0
  69. package/dist/cli/smoke/index.js.map +1 -1
  70. package/dist/io/read-json-dir-entries.d.ts +36 -0
  71. package/dist/io/read-json-dir-entries.js +38 -0
  72. package/dist/io/read-json-dir-entries.js.map +1 -0
  73. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.d.ts +35 -0
  74. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js +86 -0
  75. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve-default.js.map +1 -0
  76. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +76 -2
  77. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +139 -2
  78. package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +1 -1
  79. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +237 -0
  80. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +647 -0
  81. package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -0
  82. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +4 -1
  83. package/dist/policy-packs/builtin/understanding-before-execution/index.js +26 -1
  84. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  85. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.d.ts +71 -0
  86. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js +169 -0
  87. package/dist/policy-packs/builtin/understanding-before-execution/permission-mode-observations.js.map +1 -0
  88. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +19 -0
  89. package/dist/policy-packs/builtin/understanding-before-execution.js +29 -0
  90. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  91. package/dist/runtime/approval-signing.js +28 -0
  92. package/dist/runtime/approval-signing.js.map +1 -1
  93. package/dist/runtime/git-context.js +7 -1
  94. package/dist/runtime/git-context.js.map +1 -1
  95. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import type { Manifest } from "../../schema/index.js";
2
+ export interface AutoApproveModeWarning {
3
+ /**
4
+ * The RESOLVED effective mode (`resolveModeFromConfig(pack).mode`);
5
+ * never the literal `pack.config.mode` value. Always a recognised
6
+ * Mode literal (the resolver always returns one, defaulting to
7
+ * `grill_me` when `config.mode` is absent or unrecognised).
8
+ */
9
+ mode: string;
10
+ /** Rendered as the `⚠` line itself. */
11
+ message: string;
12
+ /**
13
+ * Indented detail lines rendered directly under {@link message} by
14
+ * `format.ts`, mirroring `understandingModeEnv`'s detail-line idiom.
15
+ */
16
+ detail: string[];
17
+ }
18
+ /**
19
+ * Pure: manifest in, a warning out (or `undefined` when there is nothing
20
+ * to flag). Only fires when:
21
+ * - the `understanding-before-execution` pack is declared AND enabled
22
+ * (a pack that never runs has no live auto-approve path to warn
23
+ * about, mirrors the enabled-gate `understanding-mode-env.ts` and
24
+ * `ug-auto-approvals.ts` use);
25
+ * - `config.auto_approve` parses as a valid opt-in block (schema
26
+ * validation already rejects a malformed block at lint time, so a
27
+ * manifest that reaches doctor with an unparseable block has
28
+ * already failed loud elsewhere; this mirrors that same fail-closed
29
+ * "not opted in" reading rather than re-deriving a second opinion);
30
+ * - the RESOLVED effective mode (`resolveModeFromConfig(pack).mode`)
31
+ * is not `"grill_me"`. An absent or unrecognised `config.mode`
32
+ * resolves to `grill_me` (`DEFAULT_MODE`) and therefore never fires
33
+ * this warning, see the module header for why that resolved value,
34
+ * not the literal config key, is what the auto path's report
35
+ * actually carries.
36
+ */
37
+ export declare function checkAutoApproveMode(manifest: Manifest): AutoApproveModeWarning | undefined;
@@ -0,0 +1,80 @@
1
+ // Doctor advisory (agent-tasks abfad738, follow-up of ADR
2
+ // docs/decisions/2026-08-27-ug-auto-mode-approval.md slice 1): warn when
3
+ // an operator has opted into the hook-written signed auto-marker path
4
+ // (`policy_packs[understanding-before-execution].config.auto_approve`)
5
+ // without also pairing it with `mode: grill_me`.
6
+ //
7
+ // Why this matters: the auto-approval path's report precondition reuses
8
+ // `validatePersistedReport` (`src/cli/approve/understanding.ts`), which
9
+ // only enforces the report's `mode` field when it is `grill_me`.
10
+ // Outside that mode, report validation is structural only (the report
11
+ // exists and belongs to the session, not that its contents reflect any
12
+ // real understanding exchange). Both the ADR (recommendation 4) and the
13
+ // pack doc recommend pairing `auto_approve` with `grill_me` for exactly
14
+ // this reason. Nothing surfaced the gap before this check: an operator
15
+ // who opts a non-grill_me mode into `auto_approve` gets no signal that
16
+ // the auto path's report gate has degraded to a near-no-op.
17
+ //
18
+ // Deliberately advisory, never an error: `auto_approve` outside
19
+ // `grill_me` is a real, if weaker, gate (the permission-mode allowlist
20
+ // and the signed marker still apply), not a misconfiguration.
21
+ //
22
+ // This check reads `resolveModeFromConfig(pack).mode`, the same
23
+ // GENERATION-path resolver `checkUnderstandingModeEnvDivergence` and
24
+ // `resolveBuiltinDefaultConfig` use, NOT `pack.config.mode` verbatim.
25
+ // A missing `config.mode` resolves to `DEFAULT_MODE` (`grill_me`), and
26
+ // that resolved value is what the generated Stop-hook command actually
27
+ // passes as `UNDERSTANDING_GATE_MODE`, so a report written under an
28
+ // unset `mode` genuinely carries `grill_me` and IS the strict check;
29
+ // there is no gap to warn about. An unrecognised `config.mode` literal
30
+ // also resolves to `grill_me` this same way, with its own warning
31
+ // already surfaced elsewhere (`resolveModeFromConfig`'s `warning`
32
+ // return, consumed by `harness validate` / other doctor checks); this
33
+ // check does not duplicate that.
34
+ //
35
+ // A session-level `UNDERSTANDING_GATE_MODE` env override that diverges
36
+ // from the resolved `config.mode` is a SEPARATE gap, already covered by
37
+ // `checkUnderstandingModeEnvDivergence` (`understanding-mode-env.ts`)'s
38
+ // own advisory line, this check only ever reads the manifest, never
39
+ // `process.env`.
40
+ import { resolveModeFromConfig } from "../../policy-packs/builtin/understanding-before-execution.js";
41
+ import { parseAutoApprove } from "../../policy-packs/builtin/understanding-before-execution/index.js";
42
+ import { findEnabledUnderstandingPack } from "./understanding-mode-env.js";
43
+ /**
44
+ * Pure: manifest in, a warning out (or `undefined` when there is nothing
45
+ * to flag). Only fires when:
46
+ * - the `understanding-before-execution` pack is declared AND enabled
47
+ * (a pack that never runs has no live auto-approve path to warn
48
+ * about, mirrors the enabled-gate `understanding-mode-env.ts` and
49
+ * `ug-auto-approvals.ts` use);
50
+ * - `config.auto_approve` parses as a valid opt-in block (schema
51
+ * validation already rejects a malformed block at lint time, so a
52
+ * manifest that reaches doctor with an unparseable block has
53
+ * already failed loud elsewhere; this mirrors that same fail-closed
54
+ * "not opted in" reading rather than re-deriving a second opinion);
55
+ * - the RESOLVED effective mode (`resolveModeFromConfig(pack).mode`)
56
+ * is not `"grill_me"`. An absent or unrecognised `config.mode`
57
+ * resolves to `grill_me` (`DEFAULT_MODE`) and therefore never fires
58
+ * this warning, see the module header for why that resolved value,
59
+ * not the literal config key, is what the auto path's report
60
+ * actually carries.
61
+ */
62
+ export function checkAutoApproveMode(manifest) {
63
+ const pack = findEnabledUnderstandingPack(manifest);
64
+ if (!pack)
65
+ return undefined;
66
+ const autoApprove = parseAutoApprove(pack.config["auto_approve"]);
67
+ if (autoApprove === null)
68
+ return undefined;
69
+ const { mode } = resolveModeFromConfig(pack);
70
+ if (mode === "grill_me")
71
+ return undefined;
72
+ return {
73
+ mode,
74
+ message: `auto_approve is configured with mode ${mode} (policy_packs[understanding-before-execution].config.mode); report validation is structural only outside grill_me`,
75
+ detail: [
76
+ "set config.mode: grill_me, or accept the weaker report gate; see docs/policy-packs/understanding-before-execution.md",
77
+ ],
78
+ };
79
+ }
80
+ //# sourceMappingURL=auto-approve-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-approve-mode.js","sourceRoot":"","sources":["../../../src/cli/doctor/auto-approve-mode.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,yEAAyE;AACzE,sEAAsE;AACtE,uEAAuE;AACvE,iDAAiD;AACjD,EAAE;AACF,wEAAwE;AACxE,wEAAwE;AACxE,iEAAiE;AACjE,sEAAsE;AACtE,uEAAuE;AACvE,wEAAwE;AACxE,wEAAwE;AACxE,uEAAuE;AACvE,uEAAuE;AACvE,4DAA4D;AAC5D,EAAE;AACF,gEAAgE;AAChE,uEAAuE;AACvE,8DAA8D;AAC9D,EAAE;AACF,gEAAgE;AAChE,qEAAqE;AACrE,sEAAsE;AACtE,uEAAuE;AACvE,uEAAuE;AACvE,oEAAoE;AACpE,qEAAqE;AACrE,uEAAuE;AACvE,kEAAkE;AAClE,kEAAkE;AAClE,sEAAsE;AACtE,iCAAiC;AACjC,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,wEAAwE;AACxE,oEAAoE;AACpE,iBAAiB;AAEjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,8DAA8D,CAAC;AACrG,OAAO,EAAE,gBAAgB,EAAE,MAAM,oEAAoE,CAAC;AAEtG,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAmB3E;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAkB;IACrD,MAAM,IAAI,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IAClE,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,EAAE,IAAI,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAE1C,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,wCAAwC,IAAI,oHAAoH;QACzK,MAAM,EAAE;YACN,sHAAsH;SACvH;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { Manifest } from "../../schema/index.js";
2
+ export interface BypassWithoutAutoApproveFinding {
3
+ /** The newest observed session that tripped this finding. */
4
+ sessionId: string;
5
+ /** That observation's `observedAt` (ISO). */
6
+ observedAt: string;
7
+ /** Rendered as the `⚠` line itself. */
8
+ message: string;
9
+ /**
10
+ * Indented detail lines rendered directly under {@link message},
11
+ * mirroring the other advisory checks' idiom. Ends with the exact
12
+ * `auto_approve` snippet (the same one `harness init` ships and
13
+ * `harness pack upgrade understanding-before-execution` inserts, see
14
+ * auto-approve-default.ts) and names the upgrade command.
15
+ */
16
+ detail: string[];
17
+ }
18
+ /**
19
+ * Pure(-ish): manifest + `generatedDir` in, a finding out (or
20
+ * `undefined` when there is nothing to flag). Only fires when:
21
+ * - the `understanding-before-execution` pack is declared AND enabled
22
+ * (mirrors every sibling advisory's gate: a pack that never runs
23
+ * has no auto-approve path to be missing);
24
+ * - at least one of the newest `recentSessions` hook-written
25
+ * observations records `permissionMode === "bypassPermissions"`;
26
+ * - `auto_approve` does NOT cover that combination today: either the
27
+ * block is absent/malformed (`parseAutoApprove` returns `null`), or
28
+ * `bypassPermissions` is not in `when`, or `claude-code` is not in
29
+ * `harnesses` (an operator who opted Codex in alone, dropping
30
+ * Claude Code, still has this exact gap on the Claude Code side).
31
+ *
32
+ * Reports the NEWEST qualifying observation only: this is a "you have
33
+ * at least one gap" flag, not a per-session audit; `ugAutoApprovals`
34
+ * already owns the full listing idiom for that.
35
+ */
36
+ export declare function checkBypassWithoutAutoApprove(manifest: Manifest, generatedDir: string, opts: {
37
+ recentSessions: number;
38
+ }): BypassWithoutAutoApproveFinding | undefined;
@@ -0,0 +1,89 @@
1
+ // Doctor finding for the Mac-mini incident shape (task 8f637efd,
2
+ // docs/decisions/2026-08-27-ug-auto-mode-approval.md, "Amendment: install
3
+ // default"): a session ran under `bypassPermissions` (observed on the
4
+ // PreToolUse payload by the hook itself, see
5
+ // permission-mode-observations.ts) but `auto_approve` is missing, or
6
+ // present without covering `bypassPermissions` for `claude-code`. In
7
+ // that shape every gated call in the session falls through to the manual
8
+ // `harness approve understanding` prompt, which is exactly the gap the
9
+ // operator hit: the pack expects an unattended session under
10
+ // `bypassPermissions` to have opted in, and nothing said otherwise.
11
+ //
12
+ // This check reads ONLY the hook-written observation
13
+ // (`.permission-mode-observations/`), never the ledger and never the
14
+ // persisted report: both are agent-writable, and the evidence-ledger
15
+ // trust boundary this pack already enforces for approval
16
+ // (docs/okf/evidence-ledger-trust-boundary.md) applies to a doctor
17
+ // finding just as much as to a gate decision: a fact the AGENT can write
18
+ // must not be what tells the operator "you meant to opt in and didn't".
19
+ //
20
+ // Deliberately advisory (⚠), never an error: an operator who runs
21
+ // `bypassPermissions` sessions on the manual approval path on purpose
22
+ // (never wanting unattended auto-approval) is not misconfigured.
23
+ //
24
+ // THE OBSERVATION IS UNSIGNED (review round 3, F2). "Never the ledger,
25
+ // never the persisted report" above is about the WRITE PATH (this
26
+ // finding does not trust an agent-writable ledger row or report field),
27
+ // not a claim that the observation itself is tamper-proof: unlike the
28
+ // approval marker, `permission-mode-observations.ts`'s record carries no
29
+ // signature, so it is evidence that `harness pack hook pre-tool-use` was
30
+ // invoked with a given `session_id`/`permission_mode` on its stdin, not
31
+ // proof that a particular session actually ran under that mode: an
32
+ // already-approved session (or anything else able to invoke the hook
33
+ // binary directly) can write, or forge, one. That is exactly why this
34
+ // finding stays advisory-only and never feeds a gate or an approval: the
35
+ // operator who reads it decides what it means. A possible future
36
+ // hardening is signing the observation the way approval markers are
37
+ // signed.
38
+ import { CLAUDE_CODE_HARNESS, harnessAllowed, listPermissionModeObservations, parseAutoApprove, permissionModeAllowed, renderAutoApproveSnippet, sanitizeForDisplay, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
39
+ import { findEnabledUnderstandingPack } from "./understanding-mode-env.js";
40
+ /** The one payload literal this finding cares about (T1, see the ADR's signal table). */
41
+ const BYPASS_MODE = "bypassPermissions";
42
+ /**
43
+ * Pure(-ish): manifest + `generatedDir` in, a finding out (or
44
+ * `undefined` when there is nothing to flag). Only fires when:
45
+ * - the `understanding-before-execution` pack is declared AND enabled
46
+ * (mirrors every sibling advisory's gate: a pack that never runs
47
+ * has no auto-approve path to be missing);
48
+ * - at least one of the newest `recentSessions` hook-written
49
+ * observations records `permissionMode === "bypassPermissions"`;
50
+ * - `auto_approve` does NOT cover that combination today: either the
51
+ * block is absent/malformed (`parseAutoApprove` returns `null`), or
52
+ * `bypassPermissions` is not in `when`, or `claude-code` is not in
53
+ * `harnesses` (an operator who opted Codex in alone, dropping
54
+ * Claude Code, still has this exact gap on the Claude Code side).
55
+ *
56
+ * Reports the NEWEST qualifying observation only: this is a "you have
57
+ * at least one gap" flag, not a per-session audit; `ugAutoApprovals`
58
+ * already owns the full listing idiom for that.
59
+ */
60
+ export function checkBypassWithoutAutoApprove(manifest, generatedDir, opts) {
61
+ const pack = findEnabledUnderstandingPack(manifest);
62
+ if (!pack)
63
+ return undefined;
64
+ const observations = listPermissionModeObservations(generatedDir, {
65
+ windowSize: opts.recentSessions,
66
+ });
67
+ const bypassObserved = observations.entries.find((e) => e.permissionMode === BYPASS_MODE);
68
+ if (!bypassObserved)
69
+ return undefined;
70
+ const autoApprove = parseAutoApprove(pack.config["auto_approve"]);
71
+ const covered = permissionModeAllowed(autoApprove, BYPASS_MODE) &&
72
+ harnessAllowed(autoApprove, CLAUDE_CODE_HARNESS);
73
+ if (covered)
74
+ return undefined;
75
+ // Sanitise at the render boundary as well, not only in the reader
76
+ // (mirrors persisted-reports.ts): both fields are payload-controlled.
77
+ const sessionId = sanitizeForDisplay(bypassObserved.sessionId);
78
+ const observedAt = sanitizeForDisplay(bypassObserved.observedAt);
79
+ return {
80
+ sessionId,
81
+ observedAt,
82
+ message: `bypassPermissions observed for session ${sessionId} (${observedAt}) but auto_approve does not cover it; every gated call in that session needed a manual approval`,
83
+ detail: [
84
+ "run `harness pack upgrade understanding-before-execution` to insert the block below, or add it by hand under policy_packs[understanding-before-execution].config:",
85
+ ...renderAutoApproveSnippet(0).split("\n"),
86
+ ],
87
+ };
88
+ }
89
+ //# sourceMappingURL=bypass-without-auto-approve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bypass-without-auto-approve.js","sourceRoot":"","sources":["../../../src/cli/doctor/bypass-without-auto-approve.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,0EAA0E;AAC1E,sEAAsE;AACtE,6CAA6C;AAC7C,qEAAqE;AACrE,qEAAqE;AACrE,yEAAyE;AACzE,uEAAuE;AACvE,6DAA6D;AAC7D,oEAAoE;AACpE,EAAE;AACF,qDAAqD;AACrD,qEAAqE;AACrE,qEAAqE;AACrE,yDAAyD;AACzD,mEAAmE;AACnE,yEAAyE;AACzE,wEAAwE;AACxE,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,iEAAiE;AACjE,EAAE;AACF,uEAAuE;AACvE,kEAAkE;AAClE,wEAAwE;AACxE,sEAAsE;AACtE,yEAAyE;AACzE,yEAAyE;AACzE,wEAAwE;AACxE,mEAAmE;AACnE,qEAAqE;AACrE,sEAAsE;AACtE,yEAAyE;AACzE,iEAAiE;AACjE,oEAAoE;AACpE,UAAU;AAEV,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,8BAA8B,EAC9B,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,sEAAsE,CAAC;AAE9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAE3E,yFAAyF;AACzF,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAmBxC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,6BAA6B,CAC3C,QAAkB,EAClB,YAAoB,EACpB,IAAgC;IAEhC,MAAM,IAAI,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,YAAY,GAAG,8BAA8B,CAAC,YAAY,EAAE;QAChE,UAAU,EAAE,IAAI,CAAC,cAAc;KAChC,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,KAAK,WAAW,CAAC,CAAC;IAC1F,IAAI,CAAC,cAAc;QAAE,OAAO,SAAS,CAAC;IAEtC,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GACX,qBAAqB,CAAC,WAAW,EAAE,WAAW,CAAC;QAC/C,cAAc,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACnD,IAAI,OAAO;QAAE,OAAO,SAAS,CAAC;IAE9B,kEAAkE;IAClE,sEAAsE;IACtE,MAAM,SAAS,GAAG,kBAAkB,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,kBAAkB,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IACjE,OAAO;QACL,SAAS;QACT,UAAU;QACV,OAAO,EAAE,0CAA0C,SAAS,KAAK,UAAU,iGAAiG;QAC5K,MAAM,EAAE;YACN,mKAAmK;YACnK,GAAG,wBAAwB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;SAC3C;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Manifest } from "../../schema/index.js";
2
+ export interface CodexConfigDriftSection {
3
+ /** ⚠ lines; each rolls into warningCount. */
4
+ warnings: string[];
5
+ }
6
+ /**
7
+ * Whether the manifest's `understanding-before-execution` pack has opted
8
+ * Codex into the `auto_approve` path (`auto_approve.harnesses` contains
9
+ * `"codex"`). Pure and side-effect-free: reuses the same fail-closed
10
+ * parser the Codex PreToolUse hook itself consults
11
+ * (`attemptAutoApproval` in `../pack/auto-approve-path.ts`), so a
12
+ * malformed `auto_approve` block resolves to "not opted in" here too,
13
+ * never a guess.
14
+ */
15
+ export declare function isCodexOptedIntoAutoApprove(manifest: Manifest): boolean;
16
+ export interface BuildCodexConfigDriftOptions {
17
+ /** `harness.generated/` for the manifest in use; kept for shape-parity with `BuildSettingsDriftOptions` (`driftCheckOpts` in index.ts is shared between both), unused by this check (see module header: no baseline exists to read here). */
18
+ generatedDir: string;
19
+ /** `harness.lock` path for the manifest in use; kept for the same shape-parity reason as `generatedDir`, unused by this check. */
20
+ lockPath: string;
21
+ /** Resolves the repo-scoped candidate file (`<cwd>/.codex/config.toml`). */
22
+ cwd: string;
23
+ /** Operator home; resolves the default `$CODEX_HOME` when the env var is unset. */
24
+ home: string;
25
+ /** Env for `CODEX_HOME` resolution. */
26
+ env: NodeJS.ProcessEnv;
27
+ }
28
+ export declare function buildCodexConfigDrift(opts: BuildCodexConfigDriftOptions): CodexConfigDriftSection;
@@ -0,0 +1,240 @@
1
+ // Codex counterpart of settings-drift.ts (ADR
2
+ // docs/decisions/2026-08-27-ug-auto-mode-approval.md, threat model (c),
3
+ // "Audit and doctor"), follow-up of slice 2 (agent-tasks f59ea0eb).
4
+ //
5
+ // `approval_policy = "never"` is the MEASURED signal: dogfood/ug-auto-
6
+ // mode-signals/README.md section (l) (Codex TUI) shows `tui-config-never`
7
+ // (`config.toml`: `approval_policy = "never"`) reports `bypassPermissions`
8
+ // where the otherwise-comparable `tui-default` shape (no `approval_policy`
9
+ // set) reports `default`, and section (k) (Codex exec) shows the same
10
+ // key discriminating `exec-onrequest`/`exec-onfailure` (`bypassPermissions`
11
+ // regardless, exec auto-approves almost everything headless) from nothing
12
+ // special, i.e. exec's own vocabulary does not turn on this key either
13
+ // way there. The `default_permissions` full-access check below is NOT a
14
+ // measured signal in the same sense: section (k)'s exec rows show
15
+ // `config-perm-fullaccess` (`default_permissions = ":danger-full-access"`)
16
+ // and `config-perm-readonly` (`default_permissions = ":read-only"`) both
17
+ // reporting `bypassPermissions`, i.e. this key does not discriminate
18
+ // `permission_mode` at all under `codex exec`, and section (l)'s TUI
19
+ // table has no `default_permissions`-via-`config.toml` row at all (the
20
+ // one full-access TUI shape, `tui-perm-full-access`, was set through the
21
+ // `/permissions` menu, not this config key, per
22
+ // `payloads/codex-tui-perm-full-access.config.toml`). This check flags
23
+ // `default_permissions` as a threat-model heuristic on a key the operator
24
+ // can set (it is documented Codex config surface that plausibly feeds a
25
+ // yet-unmeasured code path), not because measurement showed it moving
26
+ // `permission_mode`.
27
+ //
28
+ // Either key pre-sets the trusted `when`-allowlist signal the Codex
29
+ // auto-approval path (`src/cli/pack/auto-approve-path.ts`,
30
+ // `CODEX_HARNESS`) consumes, the same way `permissions.defaultMode` does
31
+ // for Claude Code (settings-drift.ts's module header, threat model (c)).
32
+ //
33
+ // Gated on the pack's `auto_approve.harnesses` actually listing `codex`
34
+ // (`isCodexOptedIntoAutoApprove` below): a repo that never opted Codex
35
+ // into the auto path has no live Codex `permission_mode` signal for this
36
+ // key to pre-set, so warning there would be noise unconnected to any
37
+ // live risk (mirrors the per-harness gate `attemptAutoApproval` itself
38
+ // applies before the `when` allowlist, `auto-approve.ts`'s module
39
+ // header).
40
+ //
41
+ // Unlike settings-drift.ts's `permissions.defaultMode` check, this is
42
+ // NOT drift-gated (warn only when the live value differs from the
43
+ // apply-time baseline): `approval_policy = "never"` is a real live risk
44
+ // the moment it is present, whether or not it was already there at the
45
+ // last `harness apply`, so this check always warns while present and
46
+ // opted in. There is no apply-time three-way distinction to annotate
47
+ // with either: harness never snapshots the operator's live, whole
48
+ // `config.toml` (only `harness apply --runtime codex --install`'s own
49
+ // generated hook block, merged in place by `install-codex-config.ts`,
50
+ // ever lands in `.last-apply`'s `"codex/config.toml"` entry), and
51
+ // `apply.ts` never records a `kind: "target"` lock entry for the
52
+ // installed Codex config path the way it does for Claude Code's
53
+ // `--target` write, so there is no baseline to compare the live value
54
+ // against and no lock entry to tell a harness-touched file apart from
55
+ // one it has never seen. Every warning below therefore carries the same
56
+ // constant `NO_SNAPSHOT_SUFFIX`, reporting presence, not drift.
57
+ //
58
+ // Fail-open, like every other doctor check in this family: a missing or
59
+ // unreadable candidate file resolves to "nothing to report" for that
60
+ // file, and a candidate whose TOML this module's minimal scanner cannot
61
+ // make sense of (an unterminated quoted string) adds ONE diagnostic
62
+ // line, never a thrown error; the keys that did parse from the rest of
63
+ // that same file are still evaluated and can still warn.
64
+ //
65
+ // No full TOML parser: none is a dependency of this package (checked
66
+ // before writing this file), and `install-codex-config.ts` itself never
67
+ // parses the operator's config.toml into an object either, it locates
68
+ // harness's own managed block by string search. This module only needs
69
+ // two ROOT-LEVEL (not inside any `[table]`) string-valued keys, so a
70
+ // small line-oriented scanner is enough: it accepts a bare or quoted key
71
+ // (`approval_policy = ...` and `"approval_policy" = ...`), skips over
72
+ // multi-line basic/literal string bodies (`"""`/`'''`) so a body line
73
+ // that happens to look like `key = "value"` inside one is never
74
+ // mistaken for a root-level assignment, and does not attempt custom
75
+ // `[profiles.*]` tables: a profile-scoped `approval_policy` under
76
+ // `[profiles.<name>]` is out of scope and not detected today (see the
77
+ // task's constraints).
78
+ import * as fs from "node:fs";
79
+ import * as path from "node:path";
80
+ import { PACK_NAME as UNDERSTANDING_PACK_NAME } from "../../policy-packs/builtin/understanding-before-execution.js";
81
+ import { CODEX_HARNESS, harnessAllowed, parseAutoApprove, } from "../../policy-packs/builtin/understanding-before-execution/auto-approve.js";
82
+ import { tildeize } from "./settings-drift.js";
83
+ /** Every warning line carries this: harness keeps no apply-time snapshot of a live Codex config.toml (see module header). */
84
+ const NO_SNAPSHOT_SUFFIX = " (harness keeps no apply-time snapshot of this file; presence only)";
85
+ /**
86
+ * Whether the manifest's `understanding-before-execution` pack has opted
87
+ * Codex into the `auto_approve` path (`auto_approve.harnesses` contains
88
+ * `"codex"`). Pure and side-effect-free: reuses the same fail-closed
89
+ * parser the Codex PreToolUse hook itself consults
90
+ * (`attemptAutoApproval` in `../pack/auto-approve-path.ts`), so a
91
+ * malformed `auto_approve` block resolves to "not opted in" here too,
92
+ * never a guess.
93
+ */
94
+ export function isCodexOptedIntoAutoApprove(manifest) {
95
+ const pack = manifest.policy_packs.find((p) => p.name === UNDERSTANDING_PACK_NAME && p.enabled !== false);
96
+ if (!pack)
97
+ return false;
98
+ const cfg = parseAutoApprove(pack.config["auto_approve"], null);
99
+ return harnessAllowed(cfg, CODEX_HARNESS);
100
+ }
101
+ /**
102
+ * Scan the ROOT-LEVEL (before the first `[table]` header) `key = "..."`
103
+ * assignments in a Codex `config.toml` body. Returns the string-valued
104
+ * keys found, plus `malformed: true` when a candidate assignment opens a
105
+ * single-line quoted string that never closes on the same line (the one
106
+ * shape this scanner treats as broken TOML rather than "not a string /
107
+ * not a key we track"). A multi-line basic/literal string (`"""`/`'''`)
108
+ * is a different, valid TOML shape: every line up to and including its
109
+ * closing delimiter is skipped rather than scanned as assignments, and
110
+ * skipping it never sets `malformed`. Non-string values (numbers,
111
+ * booleans, arrays, inline tables) are silently skipped too, not flagged
112
+ * malformed: this scanner has no opinion about them. The key itself may
113
+ * be bare (`approval_policy`) or a quoted TOML key
114
+ * (`"approval_policy"` / `'approval_policy'`); either form is accepted.
115
+ */
116
+ function scanRootLevelStringKeys(raw) {
117
+ const values = {};
118
+ let malformed = false;
119
+ let multilineDelim = null;
120
+ for (const rawLine of raw.split(/\r?\n/)) {
121
+ if (multilineDelim !== null) {
122
+ // Inside a multi-line basic/literal string body: this line is
123
+ // string content, never a root-level assignment, however much it
124
+ // may look like one. Only the closing delimiter ends the skip.
125
+ if (rawLine.includes(multilineDelim))
126
+ multilineDelim = null;
127
+ continue;
128
+ }
129
+ const line = rawLine.trim();
130
+ if (line.length === 0 || line.startsWith("#"))
131
+ continue;
132
+ // A `[table]` or `[[array-of-tables]]` header ends the root-level
133
+ // scope for every key from here on; this module only tracks
134
+ // root-level keys.
135
+ if (line.startsWith("["))
136
+ break;
137
+ const eq = line.indexOf("=");
138
+ if (eq === -1)
139
+ continue;
140
+ let key = line.slice(0, eq).trim();
141
+ if ((key.startsWith('"') && key.endsWith('"') && key.length >= 2) ||
142
+ (key.startsWith("'") && key.endsWith("'") && key.length >= 2)) {
143
+ key = key.slice(1, -1);
144
+ }
145
+ if (!/^[A-Za-z0-9_-]+$/.test(key))
146
+ continue;
147
+ const valuePart = line.slice(eq + 1).trim();
148
+ if (valuePart.startsWith('"""') || valuePart.startsWith("'''")) {
149
+ const delim = valuePart.slice(0, 3);
150
+ // A closing delimiter later on this same line makes it a
151
+ // single-line multi-line-string literal; this scanner does not
152
+ // track such values (out of scope, same as any other value shape
153
+ // it has no opinion about), it just must not treat the rest of
154
+ // the file as still inside the string.
155
+ if (valuePart.indexOf(delim, 3) === -1)
156
+ multilineDelim = delim;
157
+ continue;
158
+ }
159
+ const parsed = matchTomlString(valuePart);
160
+ if (parsed === null) {
161
+ if (valuePart.startsWith('"') || valuePart.startsWith("'"))
162
+ malformed = true;
163
+ continue;
164
+ }
165
+ values[key] = parsed;
166
+ }
167
+ return { values, malformed };
168
+ }
169
+ /** A basic (`"..."`) or literal (`'...'`) single-line TOML string, or `null` if `value` is not one. */
170
+ function matchTomlString(value) {
171
+ if (value.startsWith('"')) {
172
+ const m = /^"((?:[^"\\]|\\.)*)"/.exec(value);
173
+ if (!m)
174
+ return null;
175
+ return (m[1] ?? "")
176
+ .replace(/\\"/g, '"')
177
+ .replace(/\\n/g, "\n")
178
+ .replace(/\\t/g, "\t")
179
+ .replace(/\\\\/g, "\\");
180
+ }
181
+ if (value.startsWith("'")) {
182
+ const m = /^'([^']*)'/.exec(value);
183
+ if (!m)
184
+ return null;
185
+ return m[1] ?? "";
186
+ }
187
+ return null;
188
+ }
189
+ function checkOneFile(file, warnings) {
190
+ let raw;
191
+ try {
192
+ raw = fs.readFileSync(file.absolutePath, "utf8");
193
+ }
194
+ catch {
195
+ return;
196
+ }
197
+ const scan = scanRootLevelStringKeys(raw);
198
+ if (scan.malformed) {
199
+ warnings.push(`${file.displayLabel}: unreadable/invalid Codex config TOML`);
200
+ // Keep going: a malformed line elsewhere in the file must not hide a
201
+ // risky key that DID parse from a different line of the same file.
202
+ }
203
+ const approvalPolicy = scan.values["approval_policy"];
204
+ if (approvalPolicy === "never") {
205
+ warnings.push(`approval_policy = "never" set in ${file.displayLabel}${NO_SNAPSHOT_SUFFIX}`);
206
+ }
207
+ const defaultPermissions = scan.values["default_permissions"];
208
+ if (defaultPermissions !== undefined) {
209
+ const stripped = defaultPermissions.startsWith(":")
210
+ ? defaultPermissions.slice(1)
211
+ : defaultPermissions;
212
+ if (stripped === "danger-full-access") {
213
+ warnings.push(`default_permissions = ${JSON.stringify(defaultPermissions)} (full access) set in ${file.displayLabel}${NO_SNAPSHOT_SUFFIX}`);
214
+ }
215
+ }
216
+ }
217
+ export function buildCodexConfigDrift(opts) {
218
+ const warnings = [];
219
+ const codexHomeEnv = opts.env["CODEX_HOME"];
220
+ const codexHomeFromEnv = typeof codexHomeEnv === "string" && codexHomeEnv.trim().length > 0;
221
+ const codexHomeDir = codexHomeFromEnv ? codexHomeEnv : path.join(opts.home, ".codex");
222
+ const codexHomeConfigPath = path.join(codexHomeDir, "config.toml");
223
+ const candidates = [
224
+ {
225
+ absolutePath: path.resolve(codexHomeConfigPath),
226
+ displayLabel: codexHomeFromEnv
227
+ ? "$CODEX_HOME/config.toml"
228
+ : tildeize(codexHomeConfigPath, opts.home),
229
+ },
230
+ {
231
+ absolutePath: path.resolve(opts.cwd, ".codex", "config.toml"),
232
+ displayLabel: ".codex/config.toml",
233
+ },
234
+ ];
235
+ for (const c of candidates) {
236
+ checkOneFile(c, warnings);
237
+ }
238
+ return { warnings };
239
+ }
240
+ //# sourceMappingURL=codex-config-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-config-drift.js","sourceRoot":"","sources":["../../../src/cli/doctor/codex-config-drift.ts"],"names":[],"mappings":"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"}
@@ -60,13 +60,30 @@ function formatEnvironmentSection(report) {
60
60
  const bin = report.npmGlobalBin;
61
61
  const modeEnv = report.understandingModeEnv;
62
62
  const ugAuto = report.ugAutoApprovals;
63
+ const autoApproveMode = report.ugAutoApproveMode;
64
+ const bypassWithoutAutoApprove = report.ugBypassWithoutAutoApprove;
63
65
  // "nothing when `.approvals/` is absent" (ug-auto-approvals.ts's AC 1):
64
66
  // stay silent unless the directory actually exists, mirroring the rest
65
67
  // of this section's "no line for a check that found nothing" style.
66
68
  const showUgAuto = ugAuto !== undefined && ugAuto.approvalsDirPresent;
69
+ const ugDeleg = report.ugDelegations;
70
+ // Same "no line for a check that found nothing" convention as
71
+ // showUgAuto: silent unless `.delegations/` actually exists. A
72
+ // present-but-empty directory still renders the zero-count line
73
+ // (ug-delegations.ts's own doc comment on `delegationsDirPresent`).
74
+ const showUgDeleg = ugDeleg !== undefined && ugDeleg.delegationsDirPresent;
67
75
  const drift = report.settingsDrift;
68
76
  const hasDriftContent = drift !== undefined && (drift.notes.length > 0 || drift.warnings.length > 0);
69
- if ((!bin || bin.status !== "warn") && !modeEnv && !showUgAuto && !hasDriftContent)
77
+ const codexDrift = report.codexConfigDrift;
78
+ const hasCodexDriftContent = codexDrift !== undefined && codexDrift.warnings.length > 0;
79
+ if ((!bin || bin.status !== "warn") &&
80
+ !modeEnv &&
81
+ !autoApproveMode &&
82
+ !bypassWithoutAutoApprove &&
83
+ !showUgAuto &&
84
+ !showUgDeleg &&
85
+ !hasDriftContent &&
86
+ !hasCodexDriftContent)
70
87
  return [];
71
88
  const out = ["", "Environment"];
72
89
  if (bin && bin.status === "warn") {
@@ -77,6 +94,16 @@ function formatEnvironmentSection(report) {
77
94
  for (const line of modeEnv.detail)
78
95
  out.push(` ${line}`);
79
96
  }
97
+ if (autoApproveMode) {
98
+ out.push(` ⚠ ${autoApproveMode.message}`);
99
+ for (const line of autoApproveMode.detail)
100
+ out.push(` ${line}`);
101
+ }
102
+ if (bypassWithoutAutoApprove) {
103
+ out.push(` ⚠ ${bypassWithoutAutoApprove.message}`);
104
+ for (const line of bypassWithoutAutoApprove.detail)
105
+ out.push(` ${line}`);
106
+ }
80
107
  if (showUgAuto && ugAuto) {
81
108
  const modeParts = Object.keys(ugAuto.byMode)
82
109
  .sort()
@@ -99,12 +126,23 @@ function formatEnvironmentSection(report) {
99
126
  out.push(` ${ugAuto.unreadableCount} marker${ugAuto.unreadableCount === 1 ? "" : "s"} unreadable, excluded from the count`);
100
127
  }
101
128
  }
129
+ if (showUgDeleg && ugDeleg) {
130
+ const marker = ugDeleg.unreadable > 0 ? "⚠" : "ℹ";
131
+ const line = ugDeleg.total === 0
132
+ ? ` ${marker} delegations on disk: 0`
133
+ : ` ${marker} delegations on disk: ${ugDeleg.total} (${ugDeleg.expired} expired, ${ugDeleg.unreadable} unreadable)`;
134
+ out.push(line);
135
+ }
102
136
  if (drift) {
103
137
  for (const n of drift.notes)
104
138
  out.push(` ℹ ${n}`);
105
139
  for (const w of drift.warnings)
106
140
  out.push(` ⚠ ${w}`);
107
141
  }
142
+ if (codexDrift) {
143
+ for (const w of codexDrift.warnings)
144
+ out.push(` ⚠ ${w}`);
145
+ }
108
146
  return out;
109
147
  }
110
148
  function formatToolsSection(report) {