open-multi-agent-kit 0.90.8 → 0.90.9

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 (306) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/doctor-provider-cli.d.ts +33 -0
  7. package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
  8. package/dist/commands/doctor-provider-cli.js +215 -0
  9. package/dist/commands/doctor-provider-cli.js.map +1 -0
  10. package/dist/commands/doctor-provider-transport.d.ts +35 -0
  11. package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
  12. package/dist/commands/doctor-provider-transport.js +185 -0
  13. package/dist/commands/doctor-provider-transport.js.map +1 -0
  14. package/dist/commands/doctor-provider.d.ts +121 -7
  15. package/dist/commands/doctor-provider.d.ts.map +1 -1
  16. package/dist/commands/doctor-provider.js +1295 -102
  17. package/dist/commands/doctor-provider.js.map +1 -1
  18. package/dist/commands/session-doctor-cli.d.ts +43 -0
  19. package/dist/commands/session-doctor-cli.d.ts.map +1 -0
  20. package/dist/commands/session-doctor-cli.js +494 -0
  21. package/dist/commands/session-doctor-cli.js.map +1 -0
  22. package/dist/commands/verify-ci.d.ts +21 -0
  23. package/dist/commands/verify-ci.d.ts.map +1 -0
  24. package/dist/commands/verify-ci.js +66 -0
  25. package/dist/commands/verify-ci.js.map +1 -0
  26. package/dist/core/agent-session-services.d.ts +3 -0
  27. package/dist/core/agent-session-services.d.ts.map +1 -1
  28. package/dist/core/agent-session-services.js +3 -0
  29. package/dist/core/agent-session-services.js.map +1 -1
  30. package/dist/core/agent-session.d.ts +113 -2
  31. package/dist/core/agent-session.d.ts.map +1 -1
  32. package/dist/core/agent-session.js +993 -72
  33. package/dist/core/agent-session.js.map +1 -1
  34. package/dist/core/agent-tool-settings.d.ts +35 -0
  35. package/dist/core/agent-tool-settings.d.ts.map +1 -0
  36. package/dist/core/agent-tool-settings.js +155 -0
  37. package/dist/core/agent-tool-settings.js.map +1 -0
  38. package/dist/core/atomic-session-file.d.ts +14 -0
  39. package/dist/core/atomic-session-file.d.ts.map +1 -0
  40. package/dist/core/atomic-session-file.js +104 -0
  41. package/dist/core/atomic-session-file.js.map +1 -0
  42. package/dist/core/compaction/compaction.d.ts +6 -0
  43. package/dist/core/compaction/compaction.d.ts.map +1 -1
  44. package/dist/core/compaction/compaction.js +12 -1
  45. package/dist/core/compaction/compaction.js.map +1 -1
  46. package/dist/core/compaction/hysteresis.d.ts +27 -0
  47. package/dist/core/compaction/hysteresis.d.ts.map +1 -0
  48. package/dist/core/compaction/hysteresis.js +84 -0
  49. package/dist/core/compaction/hysteresis.js.map +1 -0
  50. package/dist/core/compaction/index.d.ts +2 -0
  51. package/dist/core/compaction/index.d.ts.map +1 -1
  52. package/dist/core/compaction/index.js +2 -0
  53. package/dist/core/compaction/index.js.map +1 -1
  54. package/dist/core/compaction/transaction.d.ts +172 -0
  55. package/dist/core/compaction/transaction.d.ts.map +1 -0
  56. package/dist/core/compaction/transaction.js +824 -0
  57. package/dist/core/compaction/transaction.js.map +1 -0
  58. package/dist/core/context-budget-governor-v2.d.ts +2 -1
  59. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  60. package/dist/core/context-budget-governor-v2.js +2 -1
  61. package/dist/core/context-budget-governor-v2.js.map +1 -1
  62. package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
  63. package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
  64. package/dist/core/context-budget-reserved-tokens.js +188 -0
  65. package/dist/core/context-budget-reserved-tokens.js.map +1 -0
  66. package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
  67. package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
  68. package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
  69. package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
  70. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  71. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  72. package/dist/core/context-budget-v2-cache-keys.js +6 -0
  73. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  74. package/dist/core/context-budget-v2-cache-provider.d.ts +4 -0
  75. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
  76. package/dist/core/context-budget-v2-cache-provider.js +19 -0
  77. package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
  78. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  79. package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
  80. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  81. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  82. package/dist/core/context-budget-v2-planner.js +1 -0
  83. package/dist/core/context-budget-v2-planner.js.map +1 -1
  84. package/dist/core/context-budget-v2-types.d.ts +4 -0
  85. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  86. package/dist/core/context-budget-v2-types.js.map +1 -1
  87. package/dist/core/durable-file-directory.d.ts +4 -0
  88. package/dist/core/durable-file-directory.d.ts.map +1 -0
  89. package/dist/core/durable-file-directory.js +76 -0
  90. package/dist/core/durable-file-directory.js.map +1 -0
  91. package/dist/core/durable-file-identity.d.ts +26 -0
  92. package/dist/core/durable-file-identity.d.ts.map +1 -0
  93. package/dist/core/durable-file-identity.js +118 -0
  94. package/dist/core/durable-file-identity.js.map +1 -0
  95. package/dist/core/durable-file-io.d.ts +29 -0
  96. package/dist/core/durable-file-io.d.ts.map +1 -0
  97. package/dist/core/durable-file-io.js +179 -0
  98. package/dist/core/durable-file-io.js.map +1 -0
  99. package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
  100. package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
  101. package/dist/core/durable-file-lock-cleanup.js +39 -0
  102. package/dist/core/durable-file-lock-cleanup.js.map +1 -0
  103. package/dist/core/durable-file-lock-owner.d.ts +18 -0
  104. package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
  105. package/dist/core/durable-file-lock-owner.js +78 -0
  106. package/dist/core/durable-file-lock-owner.js.map +1 -0
  107. package/dist/core/durable-file-lock-storage.d.ts +35 -0
  108. package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
  109. package/dist/core/durable-file-lock-storage.js +263 -0
  110. package/dist/core/durable-file-lock-storage.js.map +1 -0
  111. package/dist/core/durable-file-mode.d.ts +8 -0
  112. package/dist/core/durable-file-mode.d.ts.map +1 -0
  113. package/dist/core/durable-file-mode.js +41 -0
  114. package/dist/core/durable-file-mode.js.map +1 -0
  115. package/dist/core/extensions/loader.d.ts +12 -0
  116. package/dist/core/extensions/loader.d.ts.map +1 -1
  117. package/dist/core/extensions/loader.js +46 -37
  118. package/dist/core/extensions/loader.js.map +1 -1
  119. package/dist/core/index.d.ts +6 -2
  120. package/dist/core/index.d.ts.map +1 -1
  121. package/dist/core/index.js +5 -0
  122. package/dist/core/index.js.map +1 -1
  123. package/dist/core/model-resolver.d.ts.map +1 -1
  124. package/dist/core/model-resolver.js +1 -1
  125. package/dist/core/model-resolver.js.map +1 -1
  126. package/dist/core/redaction.d.ts +8 -0
  127. package/dist/core/redaction.d.ts.map +1 -0
  128. package/dist/core/redaction.js +31 -0
  129. package/dist/core/redaction.js.map +1 -0
  130. package/dist/core/resource-loader.d.ts.map +1 -1
  131. package/dist/core/resource-loader.js +36 -0
  132. package/dist/core/resource-loader.js.map +1 -1
  133. package/dist/core/run-journal-store.d.ts +53 -0
  134. package/dist/core/run-journal-store.d.ts.map +1 -0
  135. package/dist/core/run-journal-store.js +278 -0
  136. package/dist/core/run-journal-store.js.map +1 -0
  137. package/dist/core/run-journal.d.ts +172 -0
  138. package/dist/core/run-journal.d.ts.map +1 -0
  139. package/dist/core/run-journal.js +1053 -0
  140. package/dist/core/run-journal.js.map +1 -0
  141. package/dist/core/sdk.d.ts +7 -0
  142. package/dist/core/sdk.d.ts.map +1 -1
  143. package/dist/core/sdk.js +94 -3
  144. package/dist/core/sdk.js.map +1 -1
  145. package/dist/core/session-doctor-plan.d.ts +128 -0
  146. package/dist/core/session-doctor-plan.d.ts.map +1 -0
  147. package/dist/core/session-doctor-plan.js +833 -0
  148. package/dist/core/session-doctor-plan.js.map +1 -0
  149. package/dist/core/session-file-compaction.d.ts +6 -0
  150. package/dist/core/session-file-compaction.d.ts.map +1 -0
  151. package/dist/core/session-file-compaction.js +169 -0
  152. package/dist/core/session-file-compaction.js.map +1 -0
  153. package/dist/core/session-file-head.d.ts +10 -0
  154. package/dist/core/session-file-head.d.ts.map +1 -0
  155. package/dist/core/session-file-head.js +78 -0
  156. package/dist/core/session-file-head.js.map +1 -0
  157. package/dist/core/session-file-persistence.d.ts +30 -0
  158. package/dist/core/session-file-persistence.d.ts.map +1 -0
  159. package/dist/core/session-file-persistence.js +106 -0
  160. package/dist/core/session-file-persistence.js.map +1 -0
  161. package/dist/core/session-integrity.d.ts +43 -0
  162. package/dist/core/session-integrity.d.ts.map +1 -0
  163. package/dist/core/session-integrity.js +420 -0
  164. package/dist/core/session-integrity.js.map +1 -0
  165. package/dist/core/session-manager.d.ts +24 -4
  166. package/dist/core/session-manager.d.ts.map +1 -1
  167. package/dist/core/session-manager.js +242 -125
  168. package/dist/core/session-manager.js.map +1 -1
  169. package/dist/core/session-owner-lease.d.ts +22 -0
  170. package/dist/core/session-owner-lease.d.ts.map +1 -0
  171. package/dist/core/session-owner-lease.js +176 -0
  172. package/dist/core/session-owner-lease.js.map +1 -0
  173. package/dist/core/session-path-policy.d.ts +132 -0
  174. package/dist/core/session-path-policy.d.ts.map +1 -0
  175. package/dist/core/session-path-policy.js +780 -0
  176. package/dist/core/session-path-policy.js.map +1 -0
  177. package/dist/core/session-repair-plan.d.ts +49 -0
  178. package/dist/core/session-repair-plan.d.ts.map +1 -0
  179. package/dist/core/session-repair-plan.js +202 -0
  180. package/dist/core/session-repair-plan.js.map +1 -0
  181. package/dist/core/session-termination.d.ts +100 -0
  182. package/dist/core/session-termination.d.ts.map +1 -0
  183. package/dist/core/session-termination.js +403 -0
  184. package/dist/core/session-termination.js.map +1 -0
  185. package/dist/core/settings-manager.d.ts +22 -0
  186. package/dist/core/settings-manager.d.ts.map +1 -1
  187. package/dist/core/settings-manager.js +37 -5
  188. package/dist/core/settings-manager.js.map +1 -1
  189. package/dist/core/system-prompt.d.ts.map +1 -1
  190. package/dist/core/system-prompt.js +1 -1
  191. package/dist/core/system-prompt.js.map +1 -1
  192. package/dist/core/verified-bash-adapter.d.ts +36 -0
  193. package/dist/core/verified-bash-adapter.d.ts.map +1 -0
  194. package/dist/core/verified-bash-adapter.js +90 -0
  195. package/dist/core/verified-bash-adapter.js.map +1 -0
  196. package/dist/guardrails/command-redaction.d.ts +32 -0
  197. package/dist/guardrails/command-redaction.d.ts.map +1 -0
  198. package/dist/guardrails/command-redaction.js +776 -0
  199. package/dist/guardrails/command-redaction.js.map +1 -0
  200. package/dist/guardrails/evidence-attestation.d.ts +31 -0
  201. package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
  202. package/dist/guardrails/evidence-attestation.js +178 -0
  203. package/dist/guardrails/evidence-attestation.js.map +1 -0
  204. package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
  205. package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
  206. package/dist/guardrails/evidence-receipt-store.js +446 -0
  207. package/dist/guardrails/evidence-receipt-store.js.map +1 -0
  208. package/dist/guardrails/evidence-receipt.d.ts +58 -0
  209. package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
  210. package/dist/guardrails/evidence-receipt.js +389 -0
  211. package/dist/guardrails/evidence-receipt.js.map +1 -0
  212. package/dist/guardrails/evidence-system.d.ts +39 -15
  213. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  214. package/dist/guardrails/evidence-system.js +255 -97
  215. package/dist/guardrails/evidence-system.js.map +1 -1
  216. package/dist/guardrails/replay-ledger-io.d.ts +6 -0
  217. package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
  218. package/dist/guardrails/replay-ledger-io.js +42 -0
  219. package/dist/guardrails/replay-ledger-io.js.map +1 -0
  220. package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
  221. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
  222. package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
  223. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
  224. package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
  225. package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
  226. package/dist/guardrails/replay-ledger-lock.js +135 -0
  227. package/dist/guardrails/replay-ledger-lock.js.map +1 -0
  228. package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
  229. package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
  230. package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
  231. package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
  232. package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
  233. package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
  234. package/dist/guardrails/replay-ledger-quarantine.js +19 -0
  235. package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
  236. package/dist/guardrails/replay-ledger-store.d.ts +32 -0
  237. package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
  238. package/dist/guardrails/replay-ledger-store.js +211 -0
  239. package/dist/guardrails/replay-ledger-store.js.map +1 -0
  240. package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
  241. package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
  242. package/dist/guardrails/shell-command-name-glob.js +233 -0
  243. package/dist/guardrails/shell-command-name-glob.js.map +1 -0
  244. package/dist/guardrails/shell-command-option.d.ts +7 -0
  245. package/dist/guardrails/shell-command-option.d.ts.map +1 -0
  246. package/dist/guardrails/shell-command-option.js +24 -0
  247. package/dist/guardrails/shell-command-option.js.map +1 -0
  248. package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
  249. package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
  250. package/dist/guardrails/shell-command-tokenizer.js +145 -0
  251. package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
  252. package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
  253. package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
  254. package/dist/guardrails/shell-command-wrapper.js +42 -0
  255. package/dist/guardrails/shell-command-wrapper.js.map +1 -0
  256. package/dist/guardrails/verified-executor.d.ts +65 -0
  257. package/dist/guardrails/verified-executor.d.ts.map +1 -0
  258. package/dist/guardrails/verified-executor.js +171 -0
  259. package/dist/guardrails/verified-executor.js.map +1 -0
  260. package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
  261. package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
  262. package/dist/guardrails/workspace-fingerprint.js +634 -0
  263. package/dist/guardrails/workspace-fingerprint.js.map +1 -0
  264. package/dist/index.d.ts +15 -3
  265. package/dist/index.d.ts.map +1 -1
  266. package/dist/index.js +13 -1
  267. package/dist/index.js.map +1 -1
  268. package/dist/main.d.ts.map +1 -1
  269. package/dist/main.js +12 -0
  270. package/dist/main.js.map +1 -1
  271. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  272. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  273. package/dist/modes/interactive/interactive-mode.js +59 -9
  274. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  275. package/dist/modes/print-mode.d.ts.map +1 -1
  276. package/dist/modes/print-mode.js +43 -2
  277. package/dist/modes/print-mode.js.map +1 -1
  278. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  279. package/dist/modes/rpc/rpc-mode.js +23 -5
  280. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  281. package/dist/modes/rpc/rpc-types.d.ts +3 -0
  282. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  283. package/dist/modes/rpc/rpc-types.js.map +1 -1
  284. package/dist/types/evidence.d.ts +211 -1
  285. package/dist/types/evidence.d.ts.map +1 -1
  286. package/dist/types/evidence.js.map +1 -1
  287. package/dist/verify-ci.d.ts +3 -0
  288. package/dist/verify-ci.d.ts.map +1 -0
  289. package/dist/verify-ci.js +30 -0
  290. package/dist/verify-ci.js.map +1 -0
  291. package/docs/compaction.md +38 -4
  292. package/docs/extensions.md +2 -0
  293. package/docs/sdk.md +238 -1
  294. package/docs/settings.md +50 -2
  295. package/docs/usage.md +38 -0
  296. package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
  297. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  298. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  299. package/examples/extensions/gondolin/package-lock.json +2 -2
  300. package/examples/extensions/gondolin/package.json +1 -1
  301. package/examples/extensions/sandbox/package-lock.json +2 -2
  302. package/examples/extensions/sandbox/package.json +1 -1
  303. package/examples/extensions/with-deps/package-lock.json +4 -4
  304. package/examples/extensions/with-deps/package.json +1 -1
  305. package/npm-shrinkwrap.json +12 -12
  306. package/package.json +4 -4
@@ -0,0 +1,90 @@
1
+ import { MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES } from "../guardrails/evidence-receipt.js";
2
+ import { getShellConfig } from "../utils/shell.js";
3
+ import { redactSensitiveText } from "./redaction.js";
4
+ import { createLocalBashOperations } from "./tools/bash.js";
5
+ export const VERIFIED_BASH_REDACTION_POLICY_ID = "omk-bash-combined-v1+omk-sensitive-text-v1+capture-tail-128k+tail-64k";
6
+ const MAX_CAPTURE_BYTES = MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES * 2;
7
+ export class VerifiedBashAdapterError extends Error {
8
+ code = "INVALID_RUNNER_EXIT";
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "VerifiedBashAdapterError";
12
+ }
13
+ }
14
+ /**
15
+ * Bind an opt-in, caller-trusted BashOperations execution to a receipt.
16
+ * BashOperations exposes a combined output stream, so the policy records the
17
+ * redacted bounded stream as stdout and leaves stderr empty by construction.
18
+ */
19
+ export async function executeVerifiedBash(request) {
20
+ let combined = Buffer.alloc(0);
21
+ const capture = (data) => {
22
+ // ponytail: bounded concat is simpler than a chunk deque; split if profiling shows pressure.
23
+ const appended = Buffer.concat([combined, data]);
24
+ combined = appended.byteLength > MAX_CAPTURE_BYTES ? appended.subarray(-MAX_CAPTURE_BYTES) : appended;
25
+ };
26
+ const alreadyRedactedOutput = () => {
27
+ const redacted = Buffer.from(redactSensitiveText(combined.toString("utf8")));
28
+ const bounded = redacted.byteLength > MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES
29
+ ? redacted.subarray(-MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES)
30
+ : redacted;
31
+ return {
32
+ redactionPolicyId: VERIFIED_BASH_REDACTION_POLICY_ID,
33
+ stdout: bounded,
34
+ stderr: Buffer.alloc(0),
35
+ };
36
+ };
37
+ return request.evidenceExecutor.execute({
38
+ goalId: request.goalId,
39
+ ...(request.laneId !== undefined ? { laneId: request.laneId } : {}),
40
+ claim: request.claim,
41
+ command: { kind: "shell", shell: request.shell, script: request.script },
42
+ cwd: request.cwd,
43
+ timeoutMs: request.timeoutMs,
44
+ workspaceScope: request.workspaceScope,
45
+ executor: request.executor ?? "bash-tool",
46
+ ...(request.toolCallId !== undefined ? { toolCallId: request.toolCallId } : {}),
47
+ execute: async () => {
48
+ if (request.signal?.aborted) {
49
+ return { status: "aborted", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };
50
+ }
51
+ try {
52
+ const result = await request.operations.exec(request.script, request.cwd, {
53
+ onData: capture,
54
+ ...(request.signal !== undefined ? { signal: request.signal } : {}),
55
+ ...(request.timeoutMs !== null ? { timeout: request.timeoutMs / 1000 } : {}),
56
+ });
57
+ if (request.signal?.aborted) {
58
+ return { status: "aborted", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };
59
+ }
60
+ if (result.exitCode === null || !Number.isSafeInteger(result.exitCode)) {
61
+ throw new VerifiedBashAdapterError("bash runner returned an invalid exit code without an abort or timeout signal");
62
+ }
63
+ return result.exitCode === 0
64
+ ? { status: "passed", exitCode: 0, alreadyRedactedOutput: alreadyRedactedOutput() }
65
+ : { status: "failed", exitCode: result.exitCode, alreadyRedactedOutput: alreadyRedactedOutput() };
66
+ }
67
+ catch (error) {
68
+ if (request.signal?.aborted || (error instanceof Error && error.message === "aborted")) {
69
+ return { status: "aborted", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };
70
+ }
71
+ if (error instanceof Error && error.message.startsWith("timeout:")) {
72
+ return { status: "timeout", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };
73
+ }
74
+ throw error;
75
+ }
76
+ },
77
+ });
78
+ }
79
+ /** Bind OMK's first-party local shell backend to the verified bash adapter. */
80
+ export async function executeVerifiedLocalBash(request) {
81
+ const { shellPath, sandboxPolicy, ...executionRequest } = request;
82
+ const { shell } = getShellConfig(shellPath);
83
+ const resolvedShellPath = shell === "sh" && shellPath === undefined ? undefined : shell;
84
+ const operations = createLocalBashOperations({
85
+ ...(resolvedShellPath !== undefined ? { shellPath: resolvedShellPath } : {}),
86
+ ...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}),
87
+ });
88
+ return executeVerifiedBash({ ...executionRequest, operations, shell });
89
+ }
90
+ //# sourceMappingURL=verified-bash-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verified-bash-adapter.js","sourceRoot":"","sources":["../../src/core/verified-bash-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES } from \"../guardrails/evidence-receipt.ts\";\nimport type {\n\tVerifiedEvidenceExecutionOutcome,\n\tVerifiedEvidenceExecutionResult,\n\tVerifiedEvidenceExecutor,\n} from \"../guardrails/verified-executor.ts\";\nimport type { WorkspaceScope } from \"../types/evidence.ts\";\nimport { getShellConfig } from \"../utils/shell.ts\";\nimport { redactSensitiveText } from \"./redaction.ts\";\nimport { type BashOperations, type BashSandboxPreflight, createLocalBashOperations } from \"./tools/bash.ts\";\n\nexport const VERIFIED_BASH_REDACTION_POLICY_ID =\n\t\"omk-bash-combined-v1+omk-sensitive-text-v1+capture-tail-128k+tail-64k\" as const;\n\nconst MAX_CAPTURE_BYTES = MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES * 2;\n\nexport interface VerifiedBashExecutionRequest {\n\treadonly evidenceExecutor: VerifiedEvidenceExecutor;\n\treadonly operations: BashOperations;\n\treadonly goalId: string;\n\treadonly laneId?: string;\n\treadonly claim: string;\n\treadonly shell: string;\n\treadonly script: string;\n\treadonly cwd: string;\n\treadonly timeoutMs: number | null;\n\treadonly workspaceScope: WorkspaceScope;\n\treadonly executor?: \"bash-tool\" | \"ci-runner\";\n\treadonly toolCallId?: string;\n\treadonly signal?: AbortSignal;\n}\n\nexport type VerifiedLocalBashExecutionRequest = Omit<VerifiedBashExecutionRequest, \"operations\" | \"shell\"> & {\n\treadonly shellPath?: string;\n\treadonly sandboxPolicy?: BashSandboxPreflight;\n};\n\nexport class VerifiedBashAdapterError extends Error {\n\treadonly code = \"INVALID_RUNNER_EXIT\" as const;\n\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"VerifiedBashAdapterError\";\n\t}\n}\n\n/**\n * Bind an opt-in, caller-trusted BashOperations execution to a receipt.\n * BashOperations exposes a combined output stream, so the policy records the\n * redacted bounded stream as stdout and leaves stderr empty by construction.\n */\nexport async function executeVerifiedBash(\n\trequest: VerifiedBashExecutionRequest,\n): Promise<VerifiedEvidenceExecutionResult> {\n\tlet combined = Buffer.alloc(0);\n\n\tconst capture = (data: Buffer): void => {\n\t\t// ponytail: bounded concat is simpler than a chunk deque; split if profiling shows pressure.\n\t\tconst appended = Buffer.concat([combined, data]);\n\t\tcombined = appended.byteLength > MAX_CAPTURE_BYTES ? appended.subarray(-MAX_CAPTURE_BYTES) : appended;\n\t};\n\n\tconst alreadyRedactedOutput = () => {\n\t\tconst redacted = Buffer.from(redactSensitiveText(combined.toString(\"utf8\")));\n\t\tconst bounded =\n\t\t\tredacted.byteLength > MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES\n\t\t\t\t? redacted.subarray(-MAX_EVIDENCE_RECEIPT_OUTPUT_BYTES)\n\t\t\t\t: redacted;\n\t\treturn {\n\t\t\tredactionPolicyId: VERIFIED_BASH_REDACTION_POLICY_ID,\n\t\t\tstdout: bounded,\n\t\t\tstderr: Buffer.alloc(0),\n\t\t};\n\t};\n\n\treturn request.evidenceExecutor.execute({\n\t\tgoalId: request.goalId,\n\t\t...(request.laneId !== undefined ? { laneId: request.laneId } : {}),\n\t\tclaim: request.claim,\n\t\tcommand: { kind: \"shell\", shell: request.shell, script: request.script },\n\t\tcwd: request.cwd,\n\t\ttimeoutMs: request.timeoutMs,\n\t\tworkspaceScope: request.workspaceScope,\n\t\texecutor: request.executor ?? \"bash-tool\",\n\t\t...(request.toolCallId !== undefined ? { toolCallId: request.toolCallId } : {}),\n\t\texecute: async (): Promise<VerifiedEvidenceExecutionOutcome> => {\n\t\t\tif (request.signal?.aborted) {\n\t\t\t\treturn { status: \"aborted\", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await request.operations.exec(request.script, request.cwd, {\n\t\t\t\t\tonData: capture,\n\t\t\t\t\t...(request.signal !== undefined ? { signal: request.signal } : {}),\n\t\t\t\t\t...(request.timeoutMs !== null ? { timeout: request.timeoutMs / 1000 } : {}),\n\t\t\t\t});\n\t\t\t\tif (request.signal?.aborted) {\n\t\t\t\t\treturn { status: \"aborted\", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };\n\t\t\t\t}\n\t\t\t\tif (result.exitCode === null || !Number.isSafeInteger(result.exitCode)) {\n\t\t\t\t\tthrow new VerifiedBashAdapterError(\n\t\t\t\t\t\t\"bash runner returned an invalid exit code without an abort or timeout signal\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn result.exitCode === 0\n\t\t\t\t\t? { status: \"passed\", exitCode: 0, alreadyRedactedOutput: alreadyRedactedOutput() }\n\t\t\t\t\t: { status: \"failed\", exitCode: result.exitCode, alreadyRedactedOutput: alreadyRedactedOutput() };\n\t\t\t} catch (error) {\n\t\t\t\tif (request.signal?.aborted || (error instanceof Error && error.message === \"aborted\")) {\n\t\t\t\t\treturn { status: \"aborted\", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };\n\t\t\t\t}\n\t\t\t\tif (error instanceof Error && error.message.startsWith(\"timeout:\")) {\n\t\t\t\t\treturn { status: \"timeout\", exitCode: null, alreadyRedactedOutput: alreadyRedactedOutput() };\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t});\n}\n\n/** Bind OMK's first-party local shell backend to the verified bash adapter. */\nexport async function executeVerifiedLocalBash(\n\trequest: VerifiedLocalBashExecutionRequest,\n): Promise<VerifiedEvidenceExecutionResult> {\n\tconst { shellPath, sandboxPolicy, ...executionRequest } = request;\n\tconst { shell } = getShellConfig(shellPath);\n\tconst resolvedShellPath = shell === \"sh\" && shellPath === undefined ? undefined : shell;\n\tconst operations = createLocalBashOperations({\n\t\t...(resolvedShellPath !== undefined ? { shellPath: resolvedShellPath } : {}),\n\t\t...(sandboxPolicy !== undefined ? { sandboxPolicy } : {}),\n\t});\n\treturn executeVerifiedBash({ ...executionRequest, operations, shell });\n}\n"]}
@@ -0,0 +1,32 @@
1
+ import type { CommandHmacBinding, CommandRedactionSummary, EvidenceCommandDescriptor } from "../types/evidence.ts";
2
+ /** Identifies the tokenization rules that produced a persisted redacted command. */
3
+ export declare const EVIDENCE_COMMAND_REDACTION_POLICY_ID: "omk-command-redaction-v1";
4
+ /** Upper bound for the total number of placeholders in one redacted command. */
5
+ export declare const MAX_COMMAND_REDACTION_PLACEHOLDERS = 256;
6
+ /** All command redaction failures are static-message errors that never echo command data. */
7
+ export declare class CommandRedactionError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /** Structure-only parse of a command descriptor; performs no credential policy checks. */
11
+ export declare function parseEvidenceCommandShape(value: unknown): EvidenceCommandDescriptor;
12
+ /** Throw without echoing data when a persisted command is secret-bearing or ambiguous. */
13
+ export declare function assertCredentialFreeEvidenceCommand(command: EvidenceCommandDescriptor): void;
14
+ export interface RedactedCommandDescriptor {
15
+ /** Credential-free representation safe to persist in a receipt core. */
16
+ readonly command: EvidenceCommandDescriptor;
17
+ /** Bounded placeholder metadata describing what was tokenized. */
18
+ readonly summary: CommandRedactionSummary;
19
+ }
20
+ /**
21
+ * Tokenize secret-bearing CLI forms into a persisted-safe representation with
22
+ * `[REDACTED]` placeholders, preserving placeholder count and type. Fails closed
23
+ * when the result is unrepresentable or the placeholder metadata is oversize.
24
+ * The original command is never stored, echoed, or hashed here.
25
+ */
26
+ export declare function redactCommandDescriptor(value: unknown): RedactedCommandDescriptor;
27
+ /** Strict bounded parse of persisted command redaction placeholder metadata. */
28
+ export declare function parseCommandRedactionSummary(value: unknown): CommandRedactionSummary;
29
+ /** Strict bounded parse of a persisted keyed command binding. */
30
+ export declare function parseCommandHmacBinding(value: unknown): CommandHmacBinding;
31
+ export { bindEvidenceCommandHmac, type CommandHmacBinder, createCommandHmacBinder, } from "./evidence-attestation.ts";
32
+ //# sourceMappingURL=command-redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-redaction.d.ts","sourceRoot":"","sources":["../../src/guardrails/command-redaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,kBAAkB,EAGlB,uBAAuB,EACvB,yBAAyB,EAEzB,MAAM,sBAAsB,CAAC;AAO9B,oFAAoF;AACpF,eAAO,MAAM,oCAAoC,4BAAsC,CAAC;AAExF,gFAAgF;AAChF,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAsBtD,6FAA6F;AAC7F,qBAAa,qBAAsB,SAAQ,KAAK;IAC/C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAsGD,0FAA0F;AAC1F,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,yBAAyB,CAyBnF;AA6hBD,0FAA0F;AAC1F,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAS5F;AAsCD,MAAM,WAAW,yBAAyB;IACzC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,yBAAyB,CAmDjF;AAMD,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAiCpF;AAED,iEAAiE;AACjE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAoB1E;AAGD,OAAO,EACN,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,uBAAuB,GACvB,MAAM,2BAA2B,CAAC","sourcesContent":["// allow: SIZE_OK - command-string secret policy: single source of truth for credential\n// detection, placeholder tokenization, and the internal HMAC key boundary (ALG-003 §7.7).\nimport type {\n\tCommandHmacBinding,\n\tCommandRedactionPlaceholder,\n\tCommandRedactionPlaceholderType,\n\tCommandRedactionSummary,\n\tEvidenceCommandDescriptor,\n\tSha256Hex,\n} from \"../types/evidence.ts\";\nimport {\n\ttype NormalizedStaticShell,\n\tStaticShellSyntaxError,\n\tnormalizeStaticShell as tokenizeStaticShell,\n} from \"./shell-command-tokenizer.ts\";\n\n/** Identifies the tokenization rules that produced a persisted redacted command. */\nexport const EVIDENCE_COMMAND_REDACTION_POLICY_ID = \"omk-command-redaction-v1\" as const;\n\n/** Upper bound for the total number of placeholders in one redacted command. */\nexport const MAX_COMMAND_REDACTION_PLACEHOLDERS = 256;\n\nconst REDACTED = \"[REDACTED]\";\nconst KEY_ID_HEX = /^[0-9a-f]{16}$/;\nconst NONCE_HEX = /^[0-9a-f]{32}$/;\nconst MAC_HEX = /^[0-9a-f]{64}$/;\n\nconst PLACEHOLDER_TYPES: readonly CommandRedactionPlaceholderType[] = Object.freeze([\n\t\"api-key-header\",\n\t\"authorization-header\",\n\t\"basic-auth\",\n\t\"bearer-token\",\n\t\"cli-option-inline\",\n\t\"cli-option-value\",\n\t\"cookie-header\",\n\t\"env-assignment\",\n\t\"known-token\",\n\t\"url-credential\",\n\t\"url-query\",\n]);\nconst PLACEHOLDER_TYPE_SET: ReadonlySet<string> = new Set(PLACEHOLDER_TYPES);\n\n/** All command redaction failures are static-message errors that never echo command data. */\nexport class CommandRedactionError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"CommandRedactionError\";\n\t}\n}\n\n// ============================================================================\n// Structural command-shape validation (paranoid, own-enumerable-data only)\n// ============================================================================\n\nfunction exactObject(\n\tvalue: unknown,\n\tlabel: string,\n\trequiredKeys: readonly string[],\n\toptionalKeys: readonly string[] = [],\n): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new CommandRedactionError(`${label} must be an object`);\n\t}\n\tconst record = value as Record<string, unknown>;\n\tconst descriptors = Object.getOwnPropertyDescriptors(record);\n\tconst actual = Object.entries(descriptors)\n\t\t.filter(([, descriptor]) => descriptor.enumerable)\n\t\t.map(([key]) => key)\n\t\t.sort();\n\tconst allowed = new Set([...requiredKeys, ...optionalKeys]);\n\tconst isOwnEnumerableDataProperty = (key: string): boolean => {\n\t\tconst descriptor = descriptors[key];\n\t\treturn descriptor !== undefined && descriptor.enumerable === true && \"value\" in descriptor;\n\t};\n\tif (\n\t\tactual.some((key) => !allowed.has(key)) ||\n\t\trequiredKeys.some((key) => !isOwnEnumerableDataProperty(key)) ||\n\t\toptionalKeys.some((key) => key in record && !isOwnEnumerableDataProperty(key))\n\t) {\n\t\tthrow new CommandRedactionError(`${label} has an invalid key set`);\n\t}\n\tconst snapshot: Record<string, unknown> = {};\n\tfor (const key of actual) {\n\t\tconst descriptor = descriptors[key];\n\t\tif (descriptor === undefined || !(\"value\" in descriptor)) {\n\t\t\tthrow new CommandRedactionError(`${label} has an invalid key set`);\n\t\t}\n\t\tsnapshot[key] = descriptor.value;\n\t}\n\treturn snapshot;\n}\n\nfunction exactArray(value: unknown, label: string): unknown[] {\n\tif (!Array.isArray(value)) throw new CommandRedactionError(`${label} must be an array`);\n\tconst descriptors = Object.getOwnPropertyDescriptors(value);\n\tconst lengthDescriptor = descriptors.length as PropertyDescriptor | undefined;\n\tif (lengthDescriptor === undefined || !(\"value\" in lengthDescriptor)) {\n\t\tthrow new CommandRedactionError(`${label} has invalid index properties`);\n\t}\n\tconst rawLength = lengthDescriptor.value;\n\tif (!Number.isSafeInteger(rawLength) || (rawLength as number) < 0) {\n\t\tthrow new CommandRedactionError(`${label} has invalid index properties`);\n\t}\n\tconst length = rawLength as number;\n\tconst snapshot: unknown[] = [];\n\tfor (let index = 0; index < length; index++) {\n\t\tconst descriptor = descriptors[String(index)];\n\t\tif (descriptor === undefined || descriptor.enumerable !== true || !(\"value\" in descriptor)) {\n\t\t\tthrow new CommandRedactionError(`${label} must use own enumerable data index properties`);\n\t\t}\n\t\tsnapshot.push(descriptor.value);\n\t}\n\tconst enumerableKeys = Object.entries(descriptors)\n\t\t.filter(([, descriptor]) => descriptor.enumerable)\n\t\t.map(([key]) => key);\n\tif (enumerableKeys.length !== length || enumerableKeys.some((key, index) => key !== String(index))) {\n\t\tthrow new CommandRedactionError(`${label} must use own enumerable data index properties`);\n\t}\n\treturn snapshot;\n}\n\nfunction ownEnumerableDataProperty(record: Record<string, unknown>, label: string, key: string): unknown {\n\tconst descriptor = Object.getOwnPropertyDescriptor(record, key);\n\tif (descriptor === undefined || descriptor.enumerable !== true || !(\"value\" in descriptor)) {\n\t\tthrow new CommandRedactionError(`${label} has an invalid key set`);\n\t}\n\treturn descriptor.value;\n}\n\nfunction nonEmptyString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\" || value.length === 0 || value.includes(\"\\0\")) {\n\t\tthrow new CommandRedactionError(`${label} must be a non-empty string without NUL bytes`);\n\t}\n\treturn value;\n}\n\nfunction stringWithoutNul(value: unknown, label: string): string {\n\tif (typeof value !== \"string\" || value.includes(\"\\0\")) {\n\t\tthrow new CommandRedactionError(`${label} must be a string without NUL bytes`);\n\t}\n\treturn value;\n}\n\nfunction positiveBoundedInteger(value: unknown, label: string, max: number): number {\n\tif (!Number.isSafeInteger(value) || (value as number) <= 0 || (value as number) > max) {\n\t\tthrow new CommandRedactionError(`${label} must be a positive integer within the placeholder bound`);\n\t}\n\treturn value as number;\n}\n\n/** Structure-only parse of a command descriptor; performs no credential policy checks. */\nexport function parseEvidenceCommandShape(value: unknown): EvidenceCommandDescriptor {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new CommandRedactionError(\"receipt core command must be an object\");\n\t}\n\tconst candidate = value as Record<string, unknown>;\n\tconst kind = ownEnumerableDataProperty(candidate, \"command descriptor\", \"kind\");\n\tif (kind === \"argv\") {\n\t\tconst raw = exactObject(value, \"argv command descriptor\", [\"kind\", \"executable\", \"argv\"]);\n\t\tconst rawArgv = exactArray(raw.argv, \"argv command descriptor argv\");\n\t\tif (!rawArgv.every((argument) => typeof argument === \"string\")) {\n\t\t\tthrow new CommandRedactionError(\"argv command descriptor argv must be a string array\");\n\t\t}\n\t\tconst executable = nonEmptyString(raw.executable, \"argv command executable\");\n\t\tconst argv = rawArgv.map((argument, index) => stringWithoutNul(argument, `argv[${index}]`));\n\t\treturn Object.freeze({ kind: \"argv\", executable, argv: Object.freeze(argv) });\n\t}\n\tif (kind === \"shell\") {\n\t\tconst raw = exactObject(value, \"shell command descriptor\", [\"kind\", \"shell\", \"script\"]);\n\t\treturn Object.freeze({\n\t\t\tkind: \"shell\",\n\t\t\tshell: nonEmptyString(raw.shell, \"shell command identity\"),\n\t\t\tscript: nonEmptyString(raw.script, \"shell command script\"),\n\t\t});\n\t}\n\tthrow new CommandRedactionError(\"receipt command kind must be argv or shell\");\n}\n\n// ============================================================================\n// Placeholder recognition\n// ============================================================================\n\nfunction unquote(value: string): string {\n\tif (\n\t\tvalue.length >= 2 &&\n\t\t((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\")))\n\t) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction shellCredentialValue(value: string): string {\n\tconst candidate = value.trim();\n\tif (\n\t\tcandidate.length >= 2 &&\n\t\t((candidate.startsWith('\"') && candidate.endsWith('\"')) || (candidate.startsWith(\"'\") && candidate.endsWith(\"'\")))\n\t) {\n\t\treturn candidate.slice(1, -1);\n\t}\n\tif (candidate.endsWith('\"') || candidate.endsWith(\"'\")) return candidate.slice(0, -1);\n\treturn candidate;\n}\n\nfunction isCredentialPlaceholder(value: string): boolean {\n\tconst candidate = unquote(shellCredentialValue(value));\n\treturn (\n\t\t/^\\[(?:REDACTED|MASKED)\\]$/i.test(candidate) ||\n\t\t/^<(?:redacted|masked)>$/i.test(candidate) ||\n\t\t/^\\$[A-Za-z_][A-Za-z0-9_]*$/.test(candidate) ||\n\t\t/^\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$/.test(candidate)\n\t);\n}\n\nfunction isAuthorizationPlaceholder(value: string): boolean {\n\tconst candidate = shellCredentialValue(value);\n\tconst scheme = /^(?:bearer|basic)\\s+(.+)$/i.exec(candidate);\n\treturn isCredentialPlaceholder(scheme?.[1] ?? candidate);\n}\n\nfunction isBasicAuthPlaceholder(value: string): boolean {\n\tconst candidate = shellCredentialValue(value);\n\tif (isCredentialPlaceholder(candidate)) return true;\n\tconst separator = candidate.indexOf(\":\");\n\treturn separator >= 0 && isCredentialPlaceholder(candidate.slice(separator + 1));\n}\n\n// ============================================================================\n// Header credential scanning (shared by detection and tokenization)\n// ============================================================================\n\ntype HeaderValueBoundary = \"argument\" | \"shell\";\n\ninterface HeaderCredentialRule {\n\treadonly type: \"api-key-header\" | \"authorization-header\" | \"cookie-header\";\n\treadonly preserveScheme: boolean;\n}\n\nfunction headerCredentialRule(name: string): HeaderCredentialRule | undefined {\n\tconst normalized = name.toLowerCase().replaceAll(\"_\", \"-\");\n\tif (normalized === \"authorization\" || normalized === \"proxy-authorization\") {\n\t\treturn { type: \"authorization-header\", preserveScheme: true };\n\t}\n\tif (normalized === \"cookie\" || normalized === \"set-cookie\") {\n\t\treturn { type: \"cookie-header\", preserveScheme: false };\n\t}\n\tif (\n\t\t/^(?:x-)?(?:[a-z0-9]+-)*(?:api-key|api-token|auth-token|access-token|security-token|bearer-token|private-key|token)$/.test(\n\t\t\tnormalized,\n\t\t) ||\n\t\t/^x-(?:[a-z0-9]+-)+key$/.test(normalized)\n\t) {\n\t\treturn { type: \"api-key-header\", preserveScheme: false };\n\t}\n\treturn undefined;\n}\n\ninterface RedactionSpan {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly type: CommandRedactionPlaceholderType;\n\treadonly exactArgumentEnd?: boolean;\n}\n\nfunction isHeaderTokenBoundary(character: string): boolean {\n\treturn /[\\s,;|()\"'`]/.test(character);\n}\n\nfunction isHeaderTokenStart(text: string, index: number): boolean {\n\tif (index === 0 || isHeaderTokenBoundary(text[index - 1])) return true;\n\tconst shortOptionStart = index - 2;\n\tif (\n\t\tshortOptionStart >= 0 &&\n\t\ttext.slice(shortOptionStart, index) === \"-H\" &&\n\t\t(shortOptionStart === 0 || isHeaderTokenBoundary(text[shortOptionStart - 1]))\n\t) {\n\t\treturn true;\n\t}\n\tconst longOptionStart = index - 9;\n\treturn (\n\t\tlongOptionStart >= 0 &&\n\t\ttext.slice(longOptionStart, index) === \"--header=\" &&\n\t\t(longOptionStart === 0 || isHeaderTokenBoundary(text[longOptionStart - 1]))\n\t);\n}\n\ninterface HeaderValueAtom {\n\treadonly value: string;\n\treadonly start: number;\n\treadonly end: number;\n\treadonly scanEnd: number;\n}\n\nfunction staticShellError(): never {\n\tthrow new CommandRedactionError(\"shell command is dynamic or malformed; failing closed\");\n}\n\nfunction normalizeStaticShell(text: string): NormalizedStaticShell {\n\ttry {\n\t\treturn tokenizeStaticShell(text);\n\t} catch (error) {\n\t\tif (error instanceof StaticShellSyntaxError) staticShellError();\n\t\tthrow error;\n\t}\n}\n\nfunction readCompleteHeaderValue(text: string, start: number): HeaderValueAtom | undefined {\n\tlet valueStart = start;\n\twhile (valueStart < text.length && /\\s/.test(text[valueStart])) valueStart++;\n\treturn valueStart === text.length\n\t\t? undefined\n\t\t: { value: text.slice(valueStart), start: valueStart, end: text.length, scanEnd: text.length };\n}\n\n/**\n * Walk every credential-bearing header occurrence. Returns whether any literal\n * (non-placeholder) credential exists; optionally collects replacement spans.\n */\nfunction scanHeaderCredentials(text: string, spans?: RedactionSpan[]): boolean {\n\tlet found = false;\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (!isHeaderTokenStart(text, index)) continue;\n\t\tconst name = /^[A-Za-z][A-Za-z0-9_-]*/.exec(text.slice(index))?.[0];\n\t\tif (name === undefined) continue;\n\t\tconst rule = headerCredentialRule(name);\n\t\tif (rule === undefined) continue;\n\t\tlet separatorIndex = index + name.length;\n\t\twhile (separatorIndex < text.length && /\\s/.test(text[separatorIndex])) separatorIndex++;\n\t\tif (text[separatorIndex] !== \":\" && text[separatorIndex] !== \"=\") continue;\n\t\tif (text[separatorIndex] === \"=\" && name.includes(\"_\")) continue;\n\t\tconst credential = readCompleteHeaderValue(text, separatorIndex + 1);\n\t\tif (credential === undefined) {\n\t\t\tfound = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconst placeholder = rule.preserveScheme\n\t\t\t? isAuthorizationPlaceholder(credential.value)\n\t\t\t: isCredentialPlaceholder(credential.value);\n\t\tif (!placeholder) {\n\t\t\tfound = true;\n\t\t\tconst scheme = rule.preserveScheme ? /^(?:bearer|basic)\\s+/i.exec(credential.value) : null;\n\t\t\tconst redactStart = credential.start + (scheme?.[0].length ?? 0);\n\t\t\tif (redactStart < credential.end) spans?.push({ start: redactStart, end: credential.end, type: rule.type });\n\t\t}\n\t\tindex = Math.max(index, credential.scanEnd - 1);\n\t}\n\treturn found;\n}\n\n// ============================================================================\n// Pattern scanning (shared by detection and tokenization)\n// ============================================================================\n\nconst SECRET_NAME = `(?:token|secret|password|passwd|api[-_]?key|private[-_]?key)`;\nconst SECRET_OPTION_NAME = `(?:[A-Za-z0-9]+[-_])*${SECRET_NAME}(?:[-_][A-Za-z0-9]+)*`;\nconst envAssignmentPattern = () =>\n\tnew RegExp(\n\t\tString.raw`(?:^|[\\s;\"'\\x60])(?:export\\s+)?(?:[A-Za-z0-9_]*${SECRET_NAME}[A-Za-z0-9_]*)\\s*[:=]\\s*([^\\s;]+)`,\n\t\t\"dgi\",\n\t);\nconst cliOptionPattern = () =>\n\tnew RegExp(String.raw`--(?:${SECRET_OPTION_NAME}|authorization|cookie)(=|\\s+)([^\\s;]+)`, \"dgi\");\nconst basicAuthPattern = () =>\n\t/(?:^|[\\s;&|()\"'`])(?:(?:-[uU])(?:=|\\s+)?|--(?:proxy-)?user(?:=|\\s+))(\"[^\"\\r\\n;&|()]*\"|'[^'\\r\\n;&|()]*'|[^\\s;&|()]+)/dg;\nconst urlCredentialPattern = () => /\\b[a-z][a-z0-9+.-]*:\\/\\/[^/\\s:@]+:([^/\\s@]+)@/dgi;\nconst bareBearerPattern = () => /\\bbearer\\s+([A-Za-z0-9._~+/-]{12,})/dgi;\nconst knownTokenPattern = () =>\n\t/\\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9_-]{16,}|github_pat_[A-Za-z0-9_]{20,}|npm_[A-Za-z0-9_-]{16,}|AIza[A-Za-z0-9_-]{35})\\b/dg;\n\ninterface PatternScan {\n\treadonly pattern: RegExp;\n\treadonly valueGroup: number;\n\treadonly placeholder?: (value: string) => boolean;\n\treadonly shellWord?: boolean;\n\treadonly shellStopCharacters?: string;\n\treadonly exactArgumentEnd?: boolean;\n\treadonly type: (match: RegExpMatchArray) => CommandRedactionPlaceholderType;\n}\n\nfunction scanPattern(\n\ttext: string,\n\tscan: PatternScan,\n\tspans?: RedactionSpan[],\n\tboundary: HeaderValueBoundary = \"shell\",\n\tshellWords?: NormalizedStaticShell[\"words\"],\n): boolean {\n\tlet found = false;\n\tfor (const match of text.matchAll(scan.pattern)) {\n\t\tconst value = match[scan.valueGroup];\n\t\tif (value === undefined || scan.placeholder?.(value)) continue;\n\t\tfound = true;\n\t\tif (spans === undefined) continue;\n\t\tconst indices = match.indices?.[scan.valueGroup];\n\t\tif (indices === undefined) continue;\n\t\tconst [start, rawEnd] = indices;\n\t\tlet end = rawEnd;\n\t\tif (scan.shellWord && boundary === \"shell\") {\n\t\t\tconst word = shellWords?.find((candidate) => start >= candidate.start && start < candidate.end);\n\t\t\tif (word !== undefined) {\n\t\t\t\tend = word.end;\n\t\t\t\tif (scan.shellStopCharacters !== undefined) {\n\t\t\t\t\tfor (let cursor = start; cursor < end; cursor++) {\n\t\t\t\t\t\tif (scan.shellStopCharacters.includes(text[cursor])) {\n\t\t\t\t\t\t\tend = cursor;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstaticShellError();\n\t\t\t}\n\t\t} else if (scan.exactArgumentEnd && scan.shellStopCharacters !== undefined) {\n\t\t\tend = start;\n\t\t\twhile (end < text.length && !scan.shellStopCharacters.includes(text[end])) end++;\n\t\t}\n\t\tspans.push({ start, end, type: scan.type(match), exactArgumentEnd: scan.exactArgumentEnd });\n\t}\n\treturn found;\n}\n\nconst ENV_SCAN: PatternScan = {\n\tpattern: envAssignmentPattern(),\n\tvalueGroup: 1,\n\tplaceholder: isCredentialPlaceholder,\n\tshellWord: true,\n\ttype: () => \"env-assignment\",\n};\nconst CLI_SCAN: PatternScan = {\n\tpattern: cliOptionPattern(),\n\tvalueGroup: 2,\n\tplaceholder: isCredentialPlaceholder,\n\tshellWord: true,\n\ttype: (match) => (match[1] === \"=\" ? \"cli-option-inline\" : \"cli-option-value\"),\n};\nconst BASIC_SCAN: PatternScan = {\n\tpattern: basicAuthPattern(),\n\tvalueGroup: 1,\n\tplaceholder: isBasicAuthPlaceholder,\n\tshellWord: true,\n\ttype: () => \"basic-auth\",\n};\nconst URL_SCAN: PatternScan = {\n\tpattern: urlCredentialPattern(),\n\tvalueGroup: 1,\n\tplaceholder: isCredentialPlaceholder,\n\ttype: () => \"url-credential\",\n};\nconst BEARER_SCAN: PatternScan = {\n\tpattern: bareBearerPattern(),\n\tvalueGroup: 1,\n\ttype: () => \"bearer-token\",\n};\nconst KNOWN_TOKEN_SCAN: PatternScan = {\n\tpattern: knownTokenPattern(),\n\tvalueGroup: 0,\n\ttype: () => \"known-token\",\n};\n\nfunction withFreshPattern(scan: PatternScan): PatternScan {\n\treturn { ...scan, pattern: new RegExp(scan.pattern.source, scan.pattern.flags) };\n}\n\nconst QUERY_SECRET_NAME = new RegExp(`^[A-Za-z0-9_.~-]*${SECRET_NAME}[A-Za-z0-9_.~-]*$`, \"i\");\n\ninterface QueryPair {\n\treadonly name: string;\n\treadonly rawName: string;\n\treadonly nameStart: number;\n\treadonly valueStart: number;\n\treadonly valueEnd: number;\n}\n\nfunction queryPairs(text: string): QueryPair[] {\n\tconst pairs: QueryPair[] = [];\n\tfor (let index = 0; index < text.length; index++) {\n\t\tif (text[index] !== \"?\" && text[index] !== \"&\") continue;\n\t\tconst nameStart = index + 1;\n\t\tlet separator = nameStart;\n\t\twhile (separator < text.length && !\"=&#\".includes(text[separator])) separator++;\n\t\tif (text[separator] !== \"=\") continue;\n\t\tconst rawName = text.slice(nameStart, separator);\n\t\tif (/%(?![0-9a-f]{2})/i.test(rawName)) staticShellError();\n\t\tlet name: string;\n\t\ttry {\n\t\t\tname = decodeURIComponent(rawName);\n\t\t} catch {\n\t\t\tstaticShellError();\n\t\t}\n\t\tif (/%[0-9a-f]{2}|%/i.test(name)) staticShellError();\n\t\tlet valueEnd = separator + 1;\n\t\twhile (valueEnd < text.length && text[valueEnd] !== \"&\" && text[valueEnd] !== \"#\") valueEnd++;\n\t\tpairs.push({ name, rawName, nameStart, valueStart: separator + 1, valueEnd });\n\t}\n\treturn pairs;\n}\n\nfunction scanUrlQueryCredentials(text: string, spans?: RedactionSpan[]): boolean {\n\tlet found = false;\n\tfor (const pair of queryPairs(text)) {\n\t\tif (!QUERY_SECRET_NAME.test(pair.name)) continue;\n\t\tconst value = text.slice(pair.valueStart, pair.valueEnd);\n\t\tif (!isCredentialPlaceholder(value)) {\n\t\t\tfound = true;\n\t\t\tif (pair.valueStart < pair.valueEnd) {\n\t\t\t\tspans?.push({ start: pair.valueStart, end: pair.valueEnd, type: \"url-query\", exactArgumentEnd: true });\n\t\t\t}\n\t\t}\n\t}\n\treturn found;\n}\n\nfunction scanStaticShellHeaders(shell: NormalizedStaticShell, spans?: RedactionSpan[]): boolean {\n\tlet found = false;\n\tfor (const word of shell.words) {\n\t\tconst local: RedactionSpan[] | undefined = spans === undefined ? undefined : [];\n\t\tif (scanHeaderCredentials(shell.text.slice(word.start, word.end), local)) found = true;\n\t\tfor (const span of local ?? [])\n\t\t\tspans?.push({ ...span, start: span.start + word.start, end: span.end + word.start });\n\t}\n\treturn found;\n}\n\nfunction scanStaticShellQueries(shell: NormalizedStaticShell, spans?: RedactionSpan[]): boolean {\n\tlet found = false;\n\tfor (const word of shell.words) {\n\t\tconst local: RedactionSpan[] | undefined = spans === undefined ? undefined : [];\n\t\tif (scanUrlQueryCredentials(shell.text.slice(word.start, word.end), local)) found = true;\n\t\tfor (const span of local ?? [])\n\t\t\tspans?.push({ ...span, start: span.start + word.start, end: span.end + word.start });\n\t}\n\treturn found;\n}\n\nfunction mapStaticShellSpans(shell: NormalizedStaticShell, spans: readonly RedactionSpan[]): RedactionSpan[] {\n\treturn spans.map((span) => {\n\t\tconst first = shell.sources[span.start];\n\t\tconst last = shell.sources[span.end - 1];\n\t\tif (first === undefined || last === undefined) staticShellError();\n\t\tconst end = last.quoteStart !== undefined && last.quoteStart < first.start ? last.contentEnd : last.end;\n\t\treturn { ...span, start: first.start, end };\n\t});\n}\n\ninterface CredentialValueRange {\n\treadonly start: number;\n\treadonly end: number;\n\treadonly atomStart: number;\n\treadonly atomEnd: number;\n}\n\nfunction credentialAtom(start: number, value: string, basic = false, authorization = false): CredentialValueRange {\n\tlet atomOffset = 0;\n\tif (authorization) atomOffset = /^(?:bearer|basic)\\s+/i.exec(value)?.[0].length ?? 0;\n\tif (basic && !authorization) atomOffset = Math.max(0, value.indexOf(\":\") + 1);\n\treturn { start, end: start + value.length, atomStart: start + atomOffset, atomEnd: start + value.length };\n}\n\nfunction headerCredentialRange(word: string, offset: number): CredentialValueRange | undefined {\n\tconst start = word.startsWith(\"-H\") ? 2 : word.startsWith(\"--header=\") ? 9 : 0;\n\tconst name = /^[A-Za-z][A-Za-z0-9_-]*/.exec(word.slice(start))?.[0];\n\tif (name === undefined) return undefined;\n\tconst rule = headerCredentialRule(name);\n\tif (rule === undefined) return undefined;\n\tlet separator = start + name.length;\n\twhile (/\\s/.test(word[separator] ?? \"\")) separator++;\n\tif (word[separator] !== \":\" && word[separator] !== \"=\") return undefined;\n\tlet valueStart = separator + 1;\n\twhile (/\\s/.test(word[valueStart] ?? \"\")) valueStart++;\n\treturn credentialAtom(offset + valueStart, word.slice(valueStart), false, rule.preserveScheme);\n}\n\nfunction credentialRanges(shell: NormalizedStaticShell): CredentialValueRange[] {\n\tconst ranges: CredentialValueRange[] = [];\n\tfor (let index = 0; index < shell.words.length; index++) {\n\t\tconst word = shell.words[index];\n\t\tconst text = shell.text.slice(word.start, word.end);\n\t\tconst previousWord = shell.words[index - 1];\n\t\tconst previous = previousWord === undefined ? undefined : shell.text.slice(previousWord.start, previousWord.end);\n\t\tif (previous !== undefined && (CREDENTIAL_OPTION_PAIR.test(previous) || BASIC_AUTH_OPTION_PAIR.test(previous))) {\n\t\t\tranges.push(credentialAtom(word.start, text, BASIC_AUTH_OPTION_PAIR.test(previous)));\n\t\t}\n\t\tconst assignment = new RegExp(`^[A-Za-z0-9_]*${SECRET_NAME}[A-Za-z0-9_]*\\\\s*[:=]\\\\s*`, \"i\").exec(text);\n\t\tif (assignment !== null)\n\t\t\tranges.push(credentialAtom(word.start + assignment[0].length, text.slice(assignment[0].length)));\n\t\tconst option = new RegExp(`^--(?:${SECRET_OPTION_NAME}|authorization|cookie)=`, \"i\").exec(text);\n\t\tif (option !== null) ranges.push(credentialAtom(word.start + option[0].length, text.slice(option[0].length)));\n\t\tconst basic = /^(?:-[uU]=?|--(?:proxy-)?user=)/.exec(text);\n\t\tif (basic !== null && basic[0].length < text.length) {\n\t\t\tranges.push(credentialAtom(word.start + basic[0].length, text.slice(basic[0].length), true));\n\t\t}\n\t\tconst header = headerCredentialRange(text, word.start);\n\t\tif (header !== undefined) ranges.push(header);\n\t\tfor (const pair of queryPairs(text)) {\n\t\t\tif (QUERY_SECRET_NAME.test(pair.name)) {\n\t\t\t\tranges.push(credentialAtom(word.start + pair.valueStart, text.slice(pair.valueStart, pair.valueEnd)));\n\t\t\t}\n\t\t}\n\t\tfor (const match of text.matchAll(/\\b[a-z][a-z0-9+.-]*:\\/\\/[^/\\s:@]+:([^/\\s@]+)@/dgi)) {\n\t\t\tconst value = match[1];\n\t\t\tconst indices = match.indices?.[1];\n\t\t\tif (value !== undefined && indices !== undefined) ranges.push(credentialAtom(word.start + indices[0], value));\n\t\t}\n\t}\n\treturn ranges;\n}\n\nfunction hasDynamicCredentialName(word: string, previous: string | undefined): boolean {\n\tconst equals = word.indexOf(\"=\");\n\tif (word.startsWith(\"--\") && word.slice(0, equals < 0 ? word.length : equals).includes(\"$\")) return true;\n\tconst headerLike =\n\t\tprevious === \"-H\" || previous === \"--header\" || word.startsWith(\"-H\") || word.startsWith(\"--header=\");\n\tif (headerLike) {\n\t\tconst start = word.startsWith(\"-H\") ? 2 : word.startsWith(\"--header=\") ? 9 : 0;\n\t\tconst separator = Math.min(...[word.indexOf(\":\", start), word.indexOf(\"=\", start)].filter((value) => value >= 0));\n\t\tif ((separator === Infinity ? word.length : separator) > start && word.slice(start, separator).includes(\"$\"))\n\t\t\treturn true;\n\t}\n\treturn queryPairs(word).some((pair) => pair.rawName.includes(\"$\"));\n}\n\nfunction assertStaticShellCredentialProvenance(shell: NormalizedStaticShell): void {\n\tconst ranges = credentialRanges(shell);\n\tfor (let index = 0; index < shell.words.length; index++) {\n\t\tconst word = shell.words[index];\n\t\tconst previousWord = shell.words[index - 1];\n\t\tconst previous = previousWord === undefined ? undefined : shell.text.slice(previousWord.start, previousWord.end);\n\t\tif (hasDynamicCredentialName(shell.text.slice(word.start, word.end), previous)) staticShellError();\n\t}\n\tfor (const range of ranges) {\n\t\tconst atom = shell.text.slice(range.atomStart, range.atomEnd);\n\t\tconst variables = shell.variables.filter((variable) => variable.start < range.end && variable.end > range.start);\n\t\tif (variables.length === 0) {\n\t\t\tif (atom.includes(\"$\")) staticShellError();\n\t\t\tcontinue;\n\t\t}\n\t\tif (variables.length !== 1 || variables[0].start !== range.atomStart || variables[0].end !== range.atomEnd) {\n\t\t\tstaticShellError();\n\t\t}\n\t}\n\tfor (const variable of shell.variables) {\n\t\tconst covered = ranges.some((range) => variable.start >= range.atomStart && variable.end <= range.atomEnd);\n\t\tif (!covered) staticShellError();\n\t}\n}\n\nfunction scanCredentialShapedValue(\n\ttext: string,\n\tboundary: HeaderValueBoundary,\n\tspans?: RedactionSpan[],\n\tincludeBasic = false,\n): boolean {\n\tconst shell = boundary === \"shell\" ? normalizeStaticShell(text) : undefined;\n\tif (shell !== undefined) assertStaticShellCredentialProvenance(shell);\n\tconst scannedText = shell?.text ?? text;\n\tconst candidates: RedactionSpan[] | undefined = spans === undefined ? undefined : [];\n\tlet found = false;\n\t// Classify complete headers and query values first so narrower token forms\n\t// cannot steal their replacement spans or placeholder types.\n\tif (shell === undefined) {\n\t\tif (scanHeaderCredentials(scannedText, candidates)) found = true;\n\t\tif (scanUrlQueryCredentials(scannedText, candidates)) found = true;\n\t} else {\n\t\tif (scanStaticShellHeaders(shell, candidates)) found = true;\n\t\tif (scanStaticShellQueries(shell, candidates)) found = true;\n\t}\n\tfor (const scan of [ENV_SCAN, CLI_SCAN, BEARER_SCAN, KNOWN_TOKEN_SCAN, URL_SCAN]) {\n\t\tif (scanPattern(scannedText, withFreshPattern(scan), candidates, boundary, shell?.words)) found = true;\n\t}\n\tif (includeBasic && scanPattern(scannedText, withFreshPattern(BASIC_SCAN), candidates, boundary, shell?.words)) {\n\t\tfound = true;\n\t}\n\tspans?.push(...(shell === undefined ? (candidates ?? []) : mapStaticShellSpans(shell, candidates ?? [])));\n\treturn found;\n}\n\nfunction containsCredentialShapedValue(text: string, boundary: HeaderValueBoundary): boolean {\n\treturn scanCredentialShapedValue(text, boundary);\n}\n\nfunction containsLiteralBasicAuth(text: string, boundary: HeaderValueBoundary): boolean {\n\tconst shell = boundary === \"shell\" ? normalizeStaticShell(text) : undefined;\n\treturn scanPattern(shell?.text ?? text, withFreshPattern(BASIC_SCAN), undefined, boundary, shell?.words);\n}\n\nconst CREDENTIAL_OPTION_PAIR = new RegExp(`^--(?:${SECRET_OPTION_NAME}|authorization|cookie)$`, \"i\");\nconst BASIC_AUTH_OPTION_PAIR = /^(?:-[uU]|--user|--proxy-user)$/;\nconst SHELL_EXECUTABLE = /(?:^|[\\\\/])(?:ba|da|k|z)?sh(?:\\.exe)?$/i;\n\nfunction argvElementBoundary(executable: string, previous: string | undefined): HeaderValueBoundary {\n\tconst shellScript =\n\t\tSHELL_EXECUTABLE.test(executable) && (previous === \"--command\" || /^-[A-Za-z]*c[A-Za-z]*$/.test(previous ?? \"\"));\n\treturn shellScript ? \"shell\" : \"argument\";\n}\n\nfunction assertCredentialFreeEvidenceCommandImpl(command: EvidenceCommandDescriptor): void {\n\tconst basicAuthText = command.kind === \"argv\" ? command.argv.join(\"\\n\") : command.script;\n\tconst credentialShaped =\n\t\tcommand.kind === \"argv\"\n\t\t\t? containsCredentialShapedValue(command.executable, \"argument\") ||\n\t\t\t\tcommand.argv.some((value, index) =>\n\t\t\t\t\tcontainsCredentialShapedValue(value, argvElementBoundary(command.executable, command.argv[index - 1])),\n\t\t\t\t)\n\t\t\t: containsCredentialShapedValue(command.shell, \"argument\") ||\n\t\t\t\tcontainsCredentialShapedValue(command.script, \"shell\");\n\tconst literalBasicAuth = containsLiteralBasicAuth(basicAuthText, command.kind === \"argv\" ? \"argument\" : \"shell\");\n\tif (credentialShaped || literalBasicAuth) {\n\t\tthrow new CommandRedactionError(\"command descriptor contains credential-shaped inline data\");\n\t}\n\tif (command.kind === \"argv\") {\n\t\tfor (let index = 0; index < command.argv.length - 1; index++) {\n\t\t\tif (CREDENTIAL_OPTION_PAIR.test(command.argv[index]) && !isCredentialPlaceholder(command.argv[index + 1])) {\n\t\t\t\tthrow new CommandRedactionError(\"command descriptor contains credential-shaped inline data\");\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Throw without echoing data when a persisted command is secret-bearing or ambiguous. */\nexport function assertCredentialFreeEvidenceCommand(command: EvidenceCommandDescriptor): void {\n\ttry {\n\t\tassertCredentialFreeEvidenceCommandImpl(command);\n\t} catch (error) {\n\t\tif (error instanceof CommandRedactionError) {\n\t\t\tthrow new CommandRedactionError(\"command descriptor contains credential-shaped or ambiguous inline data\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n// ============================================================================\n// Tokenization\n// ============================================================================\n\ntype PlaceholderCounts = Map<CommandRedactionPlaceholderType, number>;\n\nfunction overlaps(a: RedactionSpan, b: RedactionSpan): boolean {\n\treturn a.start < b.end && b.start < a.end;\n}\n\n/** Replace every detected literal credential in one text with `[REDACTED]`. */\nfunction redactText(text: string, boundary: HeaderValueBoundary, counts: PlaceholderCounts): string {\n\tconst candidates: RedactionSpan[] = [];\n\tscanCredentialShapedValue(text, boundary, candidates, true);\n\tconst accepted: RedactionSpan[] = [];\n\tfor (const candidate of candidates) {\n\t\t// In argv elements whitespace is literal data, so the tail after a secret is\n\t\t// unsplittable; extend to the element end instead of leaking fragments.\n\t\tconst span =\n\t\t\tboundary === \"argument\" && candidate.exactArgumentEnd !== true\n\t\t\t\t? { ...candidate, end: text.length }\n\t\t\t\t: candidate;\n\t\tif (span.start >= span.end) continue;\n\t\tif (accepted.some((existing) => overlaps(existing, span))) continue;\n\t\taccepted.push(span);\n\t}\n\tlet redacted = text;\n\tfor (const span of [...accepted].sort((a, b) => b.start - a.start)) {\n\t\tredacted = `${redacted.slice(0, span.start)}${REDACTED}${redacted.slice(span.end)}`;\n\t}\n\tfor (const span of accepted) {\n\t\tcounts.set(span.type, (counts.get(span.type) ?? 0) + 1);\n\t}\n\treturn redacted;\n}\n\nexport interface RedactedCommandDescriptor {\n\t/** Credential-free representation safe to persist in a receipt core. */\n\treadonly command: EvidenceCommandDescriptor;\n\t/** Bounded placeholder metadata describing what was tokenized. */\n\treadonly summary: CommandRedactionSummary;\n}\n\n/**\n * Tokenize secret-bearing CLI forms into a persisted-safe representation with\n * `[REDACTED]` placeholders, preserving placeholder count and type. Fails closed\n * when the result is unrepresentable or the placeholder metadata is oversize.\n * The original command is never stored, echoed, or hashed here.\n */\nexport function redactCommandDescriptor(value: unknown): RedactedCommandDescriptor {\n\tconst command = parseEvidenceCommandShape(value);\n\tconst counts: PlaceholderCounts = new Map();\n\tlet redacted: EvidenceCommandDescriptor;\n\tif (command.kind === \"shell\") {\n\t\tredacted = Object.freeze({\n\t\t\tkind: \"shell\",\n\t\t\tshell: redactText(command.shell, \"argument\", counts),\n\t\t\tscript: redactText(command.script, \"shell\", counts),\n\t\t});\n\t} else {\n\t\tconst executable = redactText(command.executable, \"argument\", counts);\n\t\tconst argv = [...command.argv];\n\t\tconst consumed = new Set<number>();\n\t\tfor (let index = 0; index < argv.length - 1; index++) {\n\t\t\tif (CREDENTIAL_OPTION_PAIR.test(argv[index]) && !isCredentialPlaceholder(argv[index + 1])) {\n\t\t\t\targv[index + 1] = REDACTED;\n\t\t\t\tcounts.set(\"cli-option-value\", (counts.get(\"cli-option-value\") ?? 0) + 1);\n\t\t\t\tconsumed.add(index + 1);\n\t\t\t} else if (BASIC_AUTH_OPTION_PAIR.test(argv[index]) && !isBasicAuthPlaceholder(argv[index + 1])) {\n\t\t\t\targv[index + 1] = REDACTED;\n\t\t\t\tcounts.set(\"basic-auth\", (counts.get(\"basic-auth\") ?? 0) + 1);\n\t\t\t\tconsumed.add(index + 1);\n\t\t\t}\n\t\t}\n\t\tfor (let index = 0; index < argv.length; index++) {\n\t\t\tif (consumed.has(index)) continue;\n\t\t\targv[index] = redactText(argv[index], argvElementBoundary(command.executable, argv[index - 1]), counts);\n\t\t}\n\t\tredacted = Object.freeze({ kind: \"argv\", executable, argv: Object.freeze(argv) });\n\t}\n\tlet total = 0;\n\tfor (const count of counts.values()) total += count;\n\tif (total > MAX_COMMAND_REDACTION_PLACEHOLDERS) {\n\t\tthrow new CommandRedactionError(\"redacted command placeholder metadata exceeds the supported bound\");\n\t}\n\ttry {\n\t\tassertCredentialFreeEvidenceCommand(redacted);\n\t} catch {\n\t\tthrow new CommandRedactionError(\"command redaction produced an unrepresentable result; failing closed\");\n\t}\n\tconst placeholders = [...counts.entries()]\n\t\t.map(([type, count]) => Object.freeze({ type, count }))\n\t\t.sort((a, b) => (a.type < b.type ? -1 : 1));\n\treturn {\n\t\tcommand: redacted,\n\t\tsummary: Object.freeze({\n\t\t\tpolicyId: EVIDENCE_COMMAND_REDACTION_POLICY_ID,\n\t\t\tplaceholders: Object.freeze(placeholders),\n\t\t}),\n\t};\n}\n\n// ============================================================================\n// Persisted metadata validation (structurally bounded, fail closed)\n// ============================================================================\n\n/** Strict bounded parse of persisted command redaction placeholder metadata. */\nexport function parseCommandRedactionSummary(value: unknown): CommandRedactionSummary {\n\tconst raw = exactObject(value, \"commandRedaction\", [\"policyId\", \"placeholders\"]);\n\tconst policyId = nonEmptyString(raw.policyId, \"commandRedaction policyId\");\n\tif (policyId.length > 256) throw new CommandRedactionError(\"commandRedaction policyId is too long\");\n\tconst rawPlaceholders = exactArray(raw.placeholders, \"commandRedaction placeholders\");\n\tif (rawPlaceholders.length > PLACEHOLDER_TYPES.length) {\n\t\tthrow new CommandRedactionError(\"commandRedaction placeholder metadata exceeds the supported bound\");\n\t}\n\tconst placeholders: CommandRedactionPlaceholder[] = [];\n\tlet total = 0;\n\tlet previousType = \"\";\n\tfor (const entry of rawPlaceholders) {\n\t\tconst parsed = exactObject(entry, \"commandRedaction placeholder\", [\"type\", \"count\"]);\n\t\tconst type = parsed.type;\n\t\tif (typeof type !== \"string\" || !PLACEHOLDER_TYPE_SET.has(type)) {\n\t\t\tthrow new CommandRedactionError(\"commandRedaction placeholder type is invalid\");\n\t\t}\n\t\tif (type <= previousType) {\n\t\t\tthrow new CommandRedactionError(\"commandRedaction placeholder types must be unique and ascending\");\n\t\t}\n\t\tpreviousType = type;\n\t\tconst count = positiveBoundedInteger(\n\t\t\tparsed.count,\n\t\t\t\"commandRedaction placeholder count\",\n\t\t\tMAX_COMMAND_REDACTION_PLACEHOLDERS,\n\t\t);\n\t\ttotal += count;\n\t\tplaceholders.push(Object.freeze({ type: type as CommandRedactionPlaceholderType, count }));\n\t}\n\tif (total > MAX_COMMAND_REDACTION_PLACEHOLDERS) {\n\t\tthrow new CommandRedactionError(\"commandRedaction placeholder metadata exceeds the supported bound\");\n\t}\n\treturn Object.freeze({ policyId, placeholders: Object.freeze(placeholders) });\n}\n\n/** Strict bounded parse of a persisted keyed command binding. */\nexport function parseCommandHmacBinding(value: unknown): CommandHmacBinding {\n\tconst raw = exactObject(value, \"commandBinding\", [\"algorithm\", \"keyId\", \"nonce\", \"mac\"]);\n\tif (raw.algorithm !== \"hmac-sha256\") {\n\t\tthrow new CommandRedactionError(\"commandBinding algorithm must be hmac-sha256\");\n\t}\n\tif (typeof raw.keyId !== \"string\" || !KEY_ID_HEX.test(raw.keyId)) {\n\t\tthrow new CommandRedactionError(\"commandBinding keyId is invalid\");\n\t}\n\tif (typeof raw.nonce !== \"string\" || !NONCE_HEX.test(raw.nonce)) {\n\t\tthrow new CommandRedactionError(\"commandBinding nonce is invalid\");\n\t}\n\tif (typeof raw.mac !== \"string\" || !MAC_HEX.test(raw.mac)) {\n\t\tthrow new CommandRedactionError(\"commandBinding mac is invalid\");\n\t}\n\treturn Object.freeze({\n\t\talgorithm: \"hmac-sha256\",\n\t\tkeyId: raw.keyId,\n\t\tnonce: raw.nonce,\n\t\tmac: raw.mac as Sha256Hex,\n\t});\n}\n\n// Compatibility re-exports; implementation lives at the attestation trust boundary.\nexport {\n\tbindEvidenceCommandHmac,\n\ttype CommandHmacBinder,\n\tcreateCommandHmacBinder,\n} from \"./evidence-attestation.ts\";\n"]}