@intutic/cli 2.2.0 → 2.3.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 (344) hide show
  1. package/README.md +8 -8
  2. package/dist/cli.d.ts +0 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +481 -19
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/apiCommand.d.ts +40 -0
  7. package/dist/commands/apiCommand.d.ts.map +1 -0
  8. package/dist/commands/apiCommand.js +88 -0
  9. package/dist/commands/apiCommand.js.map +1 -0
  10. package/dist/commands/budget.d.ts +4 -0
  11. package/dist/commands/budget.d.ts.map +1 -1
  12. package/dist/commands/budget.js +8 -0
  13. package/dist/commands/budget.js.map +1 -1
  14. package/dist/commands/compliance.d.ts +122 -0
  15. package/dist/commands/compliance.d.ts.map +1 -0
  16. package/dist/commands/compliance.js +278 -0
  17. package/dist/commands/compliance.js.map +1 -0
  18. package/dist/commands/connect.d.ts +26 -1
  19. package/dist/commands/connect.d.ts.map +1 -1
  20. package/dist/commands/connect.js +109 -51
  21. package/dist/commands/connect.js.map +1 -1
  22. package/dist/commands/credentials.d.ts +1 -1
  23. package/dist/commands/credentials.js +1 -1
  24. package/dist/commands/decision.d.ts +4 -4
  25. package/dist/commands/decision.js +4 -4
  26. package/dist/commands/disconnect.d.ts +4 -0
  27. package/dist/commands/disconnect.d.ts.map +1 -1
  28. package/dist/commands/disconnect.js +17 -3
  29. package/dist/commands/disconnect.js.map +1 -1
  30. package/dist/commands/doctor.d.ts +0 -1
  31. package/dist/commands/doctor.d.ts.map +1 -1
  32. package/dist/commands/doctor.js +0 -1
  33. package/dist/commands/doctor.js.map +1 -1
  34. package/dist/commands/enforce.d.ts +1 -1
  35. package/dist/commands/enforce.js +1 -1
  36. package/dist/commands/enterprise.d.ts +3 -2
  37. package/dist/commands/enterprise.d.ts.map +1 -1
  38. package/dist/commands/enterprise.js +11 -3
  39. package/dist/commands/enterprise.js.map +1 -1
  40. package/dist/commands/env.js +1 -1
  41. package/dist/commands/exec.d.ts +2 -3
  42. package/dist/commands/exec.d.ts.map +1 -1
  43. package/dist/commands/exec.js +4 -5
  44. package/dist/commands/exec.js.map +1 -1
  45. package/dist/commands/gateLiveness.d.ts +15 -0
  46. package/dist/commands/gateLiveness.d.ts.map +1 -0
  47. package/dist/commands/gateLiveness.js +34 -0
  48. package/dist/commands/gateLiveness.js.map +1 -0
  49. package/dist/commands/gateway.d.ts +14 -4
  50. package/dist/commands/gateway.d.ts.map +1 -1
  51. package/dist/commands/gateway.js +34 -4
  52. package/dist/commands/gateway.js.map +1 -1
  53. package/dist/commands/github.d.ts +20 -0
  54. package/dist/commands/github.d.ts.map +1 -0
  55. package/dist/commands/github.js +41 -0
  56. package/dist/commands/github.js.map +1 -0
  57. package/dist/commands/guardrails.d.ts +28 -2
  58. package/dist/commands/guardrails.d.ts.map +1 -1
  59. package/dist/commands/guardrails.js +233 -24
  60. package/dist/commands/guardrails.js.map +1 -1
  61. package/dist/commands/init.d.ts +1 -2
  62. package/dist/commands/init.d.ts.map +1 -1
  63. package/dist/commands/init.js +1 -2
  64. package/dist/commands/init.js.map +1 -1
  65. package/dist/commands/install-daemon.d.ts +1 -1
  66. package/dist/commands/install-daemon.js +2 -2
  67. package/dist/commands/integrity.d.ts +10 -2
  68. package/dist/commands/integrity.d.ts.map +1 -1
  69. package/dist/commands/integrity.js +3 -3
  70. package/dist/commands/integrity.js.map +1 -1
  71. package/dist/commands/inventory.d.ts +60 -0
  72. package/dist/commands/inventory.d.ts.map +1 -0
  73. package/dist/commands/inventory.js +141 -0
  74. package/dist/commands/inventory.js.map +1 -0
  75. package/dist/commands/judge.d.ts +1 -1
  76. package/dist/commands/judge.js +2 -2
  77. package/dist/commands/judge.js.map +1 -1
  78. package/dist/commands/login.d.ts +0 -1
  79. package/dist/commands/login.d.ts.map +1 -1
  80. package/dist/commands/login.js +0 -1
  81. package/dist/commands/login.js.map +1 -1
  82. package/dist/commands/logout.d.ts +0 -1
  83. package/dist/commands/logout.d.ts.map +1 -1
  84. package/dist/commands/logout.js +0 -1
  85. package/dist/commands/logout.js.map +1 -1
  86. package/dist/commands/mcp.d.ts +31 -0
  87. package/dist/commands/mcp.d.ts.map +1 -0
  88. package/dist/commands/mcp.js +65 -0
  89. package/dist/commands/mcp.js.map +1 -0
  90. package/dist/commands/notifications.d.ts +54 -0
  91. package/dist/commands/notifications.d.ts.map +1 -0
  92. package/dist/commands/notifications.js +151 -0
  93. package/dist/commands/notifications.js.map +1 -0
  94. package/dist/commands/org.d.ts +3 -3
  95. package/dist/commands/org.js +3 -3
  96. package/dist/commands/policy.d.ts +6 -1
  97. package/dist/commands/policy.d.ts.map +1 -1
  98. package/dist/commands/policy.js +77 -37
  99. package/dist/commands/policy.js.map +1 -1
  100. package/dist/commands/rollback.d.ts.map +1 -1
  101. package/dist/commands/rollback.js +3 -1
  102. package/dist/commands/rollback.js.map +1 -1
  103. package/dist/commands/routing.d.ts +1 -1
  104. package/dist/commands/routing.js +1 -1
  105. package/dist/commands/rules.d.ts +74 -0
  106. package/dist/commands/rules.d.ts.map +1 -0
  107. package/dist/commands/rules.js +283 -0
  108. package/dist/commands/rules.js.map +1 -0
  109. package/dist/commands/settings.d.ts +43 -0
  110. package/dist/commands/settings.d.ts.map +1 -0
  111. package/dist/commands/settings.js +108 -0
  112. package/dist/commands/settings.js.map +1 -0
  113. package/dist/commands/setup.d.ts +1 -1
  114. package/dist/commands/setup.js +1 -1
  115. package/dist/commands/siem.d.ts +53 -0
  116. package/dist/commands/siem.d.ts.map +1 -0
  117. package/dist/commands/siem.js +132 -0
  118. package/dist/commands/siem.js.map +1 -0
  119. package/dist/commands/skill.d.ts +5 -5
  120. package/dist/commands/skill.js +7 -7
  121. package/dist/commands/skill.js.map +1 -1
  122. package/dist/commands/status.d.ts +0 -1
  123. package/dist/commands/status.d.ts.map +1 -1
  124. package/dist/commands/status.js +2 -3
  125. package/dist/commands/status.js.map +1 -1
  126. package/dist/commands/syncContext.d.ts +0 -1
  127. package/dist/commands/syncContext.d.ts.map +1 -1
  128. package/dist/commands/syncContext.js +0 -1
  129. package/dist/commands/syncContext.js.map +1 -1
  130. package/dist/commands/team.d.ts +2 -2
  131. package/dist/commands/team.js +2 -2
  132. package/dist/commands/traces.d.ts +0 -3
  133. package/dist/commands/traces.d.ts.map +1 -1
  134. package/dist/commands/traces.js +0 -3
  135. package/dist/commands/traces.js.map +1 -1
  136. package/dist/commands/usage.d.ts +41 -0
  137. package/dist/commands/usage.d.ts.map +1 -0
  138. package/dist/commands/usage.js +129 -0
  139. package/dist/commands/usage.js.map +1 -0
  140. package/dist/commands/whoami.d.ts +0 -1
  141. package/dist/commands/whoami.d.ts.map +1 -1
  142. package/dist/commands/whoami.js +0 -1
  143. package/dist/commands/whoami.js.map +1 -1
  144. package/dist/config/keychain.d.ts +0 -1
  145. package/dist/config/keychain.d.ts.map +1 -1
  146. package/dist/config/keychain.js +0 -1
  147. package/dist/config/keychain.js.map +1 -1
  148. package/dist/config/paths.d.ts +0 -1
  149. package/dist/config/paths.d.ts.map +1 -1
  150. package/dist/config/paths.js +0 -1
  151. package/dist/config/paths.js.map +1 -1
  152. package/dist/config/store.d.ts +0 -1
  153. package/dist/config/store.d.ts.map +1 -1
  154. package/dist/config/store.js +0 -1
  155. package/dist/config/store.js.map +1 -1
  156. package/dist/harness/ag2.d.ts +1 -1
  157. package/dist/harness/ag2.js +1 -1
  158. package/dist/harness/agentFramework.d.ts +1 -1
  159. package/dist/harness/agentFramework.js +1 -1
  160. package/dist/harness/agenticOrchestrator.d.ts +1 -1
  161. package/dist/harness/agenticOrchestrator.js +2 -2
  162. package/dist/harness/agenticOrchestrator.js.map +1 -1
  163. package/dist/harness/agentsMd.d.ts +24 -0
  164. package/dist/harness/agentsMd.d.ts.map +1 -0
  165. package/dist/harness/agentsMd.js +28 -0
  166. package/dist/harness/agentsMd.js.map +1 -0
  167. package/dist/harness/aider.d.ts +3 -1
  168. package/dist/harness/aider.d.ts.map +1 -1
  169. package/dist/harness/aider.js +9 -7
  170. package/dist/harness/aider.js.map +1 -1
  171. package/dist/harness/antigravity.d.ts +23 -5
  172. package/dist/harness/antigravity.d.ts.map +1 -1
  173. package/dist/harness/antigravity.js +45 -55
  174. package/dist/harness/antigravity.js.map +1 -1
  175. package/dist/harness/autogen.d.ts +1 -1
  176. package/dist/harness/autogen.js +2 -2
  177. package/dist/harness/autogen.js.map +1 -1
  178. package/dist/harness/claudeCode.d.ts +22 -1
  179. package/dist/harness/claudeCode.d.ts.map +1 -1
  180. package/dist/harness/claudeCode.js +47 -3
  181. package/dist/harness/claudeCode.js.map +1 -1
  182. package/dist/harness/cline.d.ts.map +1 -1
  183. package/dist/harness/cline.js +14 -23
  184. package/dist/harness/cline.js.map +1 -1
  185. package/dist/harness/codex.d.ts +6 -1
  186. package/dist/harness/codex.d.ts.map +1 -1
  187. package/dist/harness/codex.js +23 -21
  188. package/dist/harness/codex.js.map +1 -1
  189. package/dist/harness/continue.d.ts +10 -4
  190. package/dist/harness/continue.d.ts.map +1 -1
  191. package/dist/harness/continue.js +22 -22
  192. package/dist/harness/continue.js.map +1 -1
  193. package/dist/harness/cursor.d.ts +5 -2
  194. package/dist/harness/cursor.d.ts.map +1 -1
  195. package/dist/harness/cursor.js +19 -23
  196. package/dist/harness/cursor.js.map +1 -1
  197. package/dist/harness/detector.d.ts +0 -1
  198. package/dist/harness/detector.d.ts.map +1 -1
  199. package/dist/harness/detector.js +0 -1
  200. package/dist/harness/detector.js.map +1 -1
  201. package/dist/harness/dsh.d.ts +5 -3
  202. package/dist/harness/dsh.d.ts.map +1 -1
  203. package/dist/harness/dsh.js +17 -29
  204. package/dist/harness/dsh.js.map +1 -1
  205. package/dist/harness/eve.d.ts +3 -4
  206. package/dist/harness/eve.d.ts.map +1 -1
  207. package/dist/harness/eve.js +3 -4
  208. package/dist/harness/eve.js.map +1 -1
  209. package/dist/harness/githubCopilot.d.ts +4 -1
  210. package/dist/harness/githubCopilot.d.ts.map +1 -1
  211. package/dist/harness/githubCopilot.js +12 -20
  212. package/dist/harness/githubCopilot.js.map +1 -1
  213. package/dist/harness/goose.d.ts +7 -3
  214. package/dist/harness/goose.d.ts.map +1 -1
  215. package/dist/harness/goose.js +18 -21
  216. package/dist/harness/goose.js.map +1 -1
  217. package/dist/harness/grok.d.ts +2 -2
  218. package/dist/harness/grok.d.ts.map +1 -1
  219. package/dist/harness/grok.js +13 -24
  220. package/dist/harness/grok.js.map +1 -1
  221. package/dist/harness/hermes.d.ts +6 -0
  222. package/dist/harness/hermes.d.ts.map +1 -1
  223. package/dist/harness/hermes.js +17 -16
  224. package/dist/harness/hermes.js.map +1 -1
  225. package/dist/harness/muse.d.ts +3 -7
  226. package/dist/harness/muse.d.ts.map +1 -1
  227. package/dist/harness/muse.js +15 -35
  228. package/dist/harness/muse.js.map +1 -1
  229. package/dist/harness/n8n.d.ts +13 -14
  230. package/dist/harness/n8n.d.ts.map +1 -1
  231. package/dist/harness/n8n.js +21 -181
  232. package/dist/harness/n8n.js.map +1 -1
  233. package/dist/harness/openWebUI.d.ts.map +1 -1
  234. package/dist/harness/openWebUI.js +5 -4
  235. package/dist/harness/openWebUI.js.map +1 -1
  236. package/dist/harness/openaiAgents.d.ts +1 -1
  237. package/dist/harness/openaiAgents.js +2 -2
  238. package/dist/harness/openaiAgents.js.map +1 -1
  239. package/dist/harness/openclaw.d.ts +8 -0
  240. package/dist/harness/openclaw.d.ts.map +1 -1
  241. package/dist/harness/openclaw.js +27 -12
  242. package/dist/harness/openclaw.js.map +1 -1
  243. package/dist/harness/opencode.d.ts +4 -5
  244. package/dist/harness/opencode.d.ts.map +1 -1
  245. package/dist/harness/opencode.js +15 -30
  246. package/dist/harness/opencode.js.map +1 -1
  247. package/dist/harness/openhands.d.ts +9 -3
  248. package/dist/harness/openhands.d.ts.map +1 -1
  249. package/dist/harness/openhands.js +30 -24
  250. package/dist/harness/openhands.js.map +1 -1
  251. package/dist/harness/pi.d.ts +7 -2
  252. package/dist/harness/pi.d.ts.map +1 -1
  253. package/dist/harness/pi.js +19 -18
  254. package/dist/harness/pi.js.map +1 -1
  255. package/dist/harness/rooCode.d.ts +12 -5
  256. package/dist/harness/rooCode.d.ts.map +1 -1
  257. package/dist/harness/rooCode.js +23 -39
  258. package/dist/harness/rooCode.js.map +1 -1
  259. package/dist/harness/rulesFiles.d.ts +53 -0
  260. package/dist/harness/rulesFiles.d.ts.map +1 -0
  261. package/dist/harness/rulesFiles.js +106 -0
  262. package/dist/harness/rulesFiles.js.map +1 -0
  263. package/dist/harness/smolagents.d.ts +2 -2
  264. package/dist/harness/smolagents.js +3 -3
  265. package/dist/harness/smolagents.js.map +1 -1
  266. package/dist/harness/types.d.ts +20 -6
  267. package/dist/harness/types.d.ts.map +1 -1
  268. package/dist/harness/types.js +1 -95
  269. package/dist/harness/types.js.map +1 -1
  270. package/dist/harness/windsurf.d.ts +6 -1
  271. package/dist/harness/windsurf.d.ts.map +1 -1
  272. package/dist/harness/windsurf.js +21 -24
  273. package/dist/harness/windsurf.js.map +1 -1
  274. package/dist/harness/xirp.d.ts +1 -2
  275. package/dist/harness/xirp.d.ts.map +1 -1
  276. package/dist/harness/xirp.js +2 -3
  277. package/dist/harness/xirp.js.map +1 -1
  278. package/dist/lib/api.d.ts +27 -1
  279. package/dist/lib/api.d.ts.map +1 -1
  280. package/dist/lib/api.js +55 -18
  281. package/dist/lib/api.js.map +1 -1
  282. package/dist/lib/ciscoScanner.d.ts +1 -1
  283. package/dist/lib/ciscoScanner.js +1 -1
  284. package/dist/lib/decisionsLogRefresh.d.ts.map +1 -1
  285. package/dist/lib/decisionsLogRefresh.js +6 -7
  286. package/dist/lib/decisionsLogRefresh.js.map +1 -1
  287. package/dist/lib/deviceReport.d.ts +15 -0
  288. package/dist/lib/deviceReport.d.ts.map +1 -1
  289. package/dist/lib/deviceReport.js +30 -0
  290. package/dist/lib/deviceReport.js.map +1 -1
  291. package/dist/lib/gitHooks.d.ts +0 -1
  292. package/dist/lib/gitHooks.d.ts.map +1 -1
  293. package/dist/lib/gitHooks.js +8 -9
  294. package/dist/lib/gitHooks.js.map +1 -1
  295. package/dist/lib/gracefulShutdown.d.ts +1 -1
  296. package/dist/lib/gracefulShutdown.js +1 -1
  297. package/dist/lib/hash.d.ts +0 -1
  298. package/dist/lib/hash.d.ts.map +1 -1
  299. package/dist/lib/hash.js +0 -1
  300. package/dist/lib/hash.js.map +1 -1
  301. package/dist/lib/inventory.d.ts +47 -0
  302. package/dist/lib/inventory.d.ts.map +1 -0
  303. package/dist/lib/inventory.js +106 -0
  304. package/dist/lib/inventory.js.map +1 -0
  305. package/dist/lib/logger.d.ts +0 -1
  306. package/dist/lib/logger.d.ts.map +1 -1
  307. package/dist/lib/logger.js +0 -1
  308. package/dist/lib/logger.js.map +1 -1
  309. package/dist/lib/mdmManifest.d.ts +30 -2
  310. package/dist/lib/mdmManifest.d.ts.map +1 -1
  311. package/dist/lib/mdmManifest.js +50 -2
  312. package/dist/lib/mdmManifest.js.map +1 -1
  313. package/dist/lib/onboarding.d.ts +0 -1
  314. package/dist/lib/onboarding.d.ts.map +1 -1
  315. package/dist/lib/onboarding.js +1 -2
  316. package/dist/lib/onboarding.js.map +1 -1
  317. package/dist/lib/policySnapshot.d.ts +3 -2
  318. package/dist/lib/policySnapshot.d.ts.map +1 -1
  319. package/dist/lib/policySnapshot.js +6 -3
  320. package/dist/lib/policySnapshot.js.map +1 -1
  321. package/dist/lib/process.d.ts +5 -1
  322. package/dist/lib/process.d.ts.map +1 -1
  323. package/dist/lib/process.js +13 -4
  324. package/dist/lib/process.js.map +1 -1
  325. package/dist/lib/providerProbe.d.ts +2 -2
  326. package/dist/lib/providerProbe.js +2 -2
  327. package/dist/lib/sandbox/firecracker.d.ts +3 -3
  328. package/dist/lib/sandbox/firecracker.js +3 -3
  329. package/dist/lib/sandbox/index.d.ts +2 -2
  330. package/dist/lib/sandbox/index.js +2 -2
  331. package/dist/lib/sandbox/oci.d.ts +1 -1
  332. package/dist/lib/sandbox/oci.js +1 -1
  333. package/dist/lib/sandbox/types.d.ts +1 -1
  334. package/dist/lib/sandbox/types.js +1 -1
  335. package/dist/lib/sopFrontMatter.d.ts +1 -1
  336. package/dist/lib/sopFrontMatter.js +1 -1
  337. package/package.json +6 -5
  338. package/resources/sandbox/Dockerfile +1 -1
  339. package/resources/sandbox/entrypoint.sh +4 -4
  340. package/resources/sandbox/firecracker-launch.sh +1 -1
  341. package/dist/harness/base.d.ts +0 -24
  342. package/dist/harness/base.d.ts.map +0 -1
  343. package/dist/harness/base.js +0 -74
  344. package/dist/harness/base.js.map +0 -1
@@ -0,0 +1,278 @@
1
+ /**
2
+ * `intutic compliance` — framework coverage, and the evidence archive an
3
+ * auditor receives.
4
+ *
5
+ * Subcommands:
6
+ * - `intutic compliance coverage <framework_id>` — how the latest compliance
7
+ * probe results cover one framework (EU AI Act, ISO/IEC 42001, NIST AI
8
+ * RMF, MITRE ATLAS), as a summary or as the report file.
9
+ * - `intutic compliance collect` — run a fresh evidence collection and seal
10
+ * it into an archive.
11
+ * - `intutic compliance download <run_id>` — a stored archive.
12
+ * - `intutic compliance verify <file>` — check an archive offline.
13
+ *
14
+ * Server side: services/control-plane/src/routes/compliance.ts —
15
+ * `GET .../frameworks/:frameworkId/coverage` (`?format=` json, markdown, csv
16
+ * or pdf; a live report is unsigned, the signed copies are the ones sealed in
17
+ * the evidence pack), `POST .../soc2-collect` and `GET .../soc2-export/:runId`
18
+ * (OWNER/ADMIN). The archive's rules are in
19
+ * services/control-plane/src/services/soc2EvidenceService.ts: a sha256
20
+ * manifest over canonical JSON, and an Ed25519 signature over
21
+ * `intutic-soc2-evidence-v1\n<archiveSha256>` only when the deployment has a
22
+ * signing key. `verify` applies the same rules with nothing but the file and
23
+ * the published key set, so its answer does not depend on trusting the server
24
+ * that produced the archive.
25
+ *
26
+ * @module
27
+ */
28
+ import { createHash, createPublicKey, verify as nodeVerify } from 'node:crypto';
29
+ import { readFileSync, writeFileSync } from 'node:fs';
30
+ import pc from 'picocolors';
31
+ import { resolveControlPlaneUrl } from '../config/paths.js';
32
+ import { log } from '../lib/logger.js';
33
+ import { fail, runApiCommand, writeOutput } from './apiCommand.js';
34
+ import { fetchSigningKeys } from './integrity.js';
35
+ /** The CLI's format names, and the route's for each. */
36
+ const FORMATS = { json: 'json', md: 'markdown', csv: 'csv', pdf: 'pdf' };
37
+ /** `intutic compliance coverage <framework_id>` */
38
+ export async function runComplianceCoverage(frameworkId, opts) {
39
+ const format = opts.format ?? (opts.json ? 'json' : undefined);
40
+ const path = `/api/v1/compliance/frameworks/${encodeURIComponent(frameworkId)}/coverage`;
41
+ if (format === undefined) {
42
+ await runApiCommand(opts, `Failed to read ${frameworkId} coverage`, (client) => client.get(path), (c) => {
43
+ log.header(`Intutic — ${c.name} coverage`);
44
+ log.field('Controls', `${c.summary.controls} (${c.summary.mapped} mapped: ${c.summary.full} full, ${c.summary.partial} partial)`);
45
+ for (const [state, count] of Object.entries(c.summary.states))
46
+ log.field(state, String(count));
47
+ log.field('Mapping version', c.mappingVersion);
48
+ log.field('Generated', c.generatedAt);
49
+ log.dim(' The full report: --format md|csv|pdf --out <file>.');
50
+ });
51
+ return;
52
+ }
53
+ if (!Object.keys(FORMATS).includes(format))
54
+ fail(`--format must be one of ${Object.keys(FORMATS).join(', ')}, got "${format}"`);
55
+ // A PDF written to a terminal is noise and a lost report; a pipe or a file is fine.
56
+ if (format === 'pdf' && !opts.out && process.stdout.isTTY) {
57
+ fail('A PDF report needs --out <file>, or redirect the output to a file.');
58
+ }
59
+ await runApiCommand(
60
+ // The output is the report itself; `--json` only chose the format.
61
+ { dev: opts.dev }, `Failed to export ${frameworkId} coverage`, (client) => client.getFile(`${path}?format=${FORMATS[format]}`), (bytes) => writeOutput(bytes, opts.out, `${frameworkId} coverage (${format})`));
62
+ }
63
+ // ─── Evidence archive ────────────────────────────────────────────────
64
+ /** The domain the control plane signs evidence archives under (soc2EvidenceService.ts). */
65
+ export const EVIDENCE_SIGNING_DOMAIN = 'intutic-soc2-evidence-v1';
66
+ /**
67
+ * Object keys sorted recursively, arrays in order: the preimage the control
68
+ * plane hashes (`canonicalJson` in soc2EvidenceService.ts), because the
69
+ * archive round-trips through a JSONB column that does not keep key order.
70
+ */
71
+ export function canonicalJson(value) {
72
+ if (value === null || typeof value !== 'object')
73
+ return JSON.stringify(value);
74
+ if (Array.isArray(value))
75
+ return `[${value.map(canonicalJson).join(',')}]`;
76
+ const entries = Object.entries(value)
77
+ .filter(([, v]) => v !== undefined)
78
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
79
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
80
+ }
81
+ const sha256Hex = (data) => createHash('sha256').update(data).digest('hex');
82
+ /** What one manifest entry hashes, or null when the archive has nothing under that name. */
83
+ function sectionContent(archive, name) {
84
+ const category = archive.categories?.find((c) => c.category === name);
85
+ if (category)
86
+ return canonicalJson(category);
87
+ const m = /^framework:([^:]+)(?::(csv|pdf))?$/.exec(name);
88
+ const framework = m ? archive.frameworks?.find((f) => f.coverage?.frameworkId === m[1]) : undefined;
89
+ if (!m || !framework)
90
+ return null;
91
+ // The CSV as its UTF-8 bytes and the PDF decoded, so a file saved out of
92
+ // the archive checks with `sha256sum` alone.
93
+ if (m[2] === 'csv')
94
+ return framework.csv ?? null;
95
+ if (m[2] === 'pdf')
96
+ return framework.pdf === undefined ? null : Buffer.from(framework.pdf, 'base64');
97
+ return canonicalJson(framework);
98
+ }
99
+ /**
100
+ * Check an evidence archive the way it was sealed: the manifest's hashes,
101
+ * then the signature over the archive hash against the published key its
102
+ * `keyId` names. A signature over a hash that does not match proves nothing
103
+ * about this file, so it is reported only once the hash matches.
104
+ */
105
+ export function verifyEvidenceArchive(archive, jwks) {
106
+ const rest = { ...archive };
107
+ delete rest['manifest'];
108
+ delete rest['signature'];
109
+ const archiveSha256 = sha256Hex(canonicalJson(rest));
110
+ const manifest = archive.manifest ?? {};
111
+ const archiveHashMatches = archiveSha256 === manifest.archiveSha256;
112
+ const sections = Object.entries(manifest.sections ?? {});
113
+ const sectionMismatches = sections
114
+ .filter(([name, recorded]) => {
115
+ const content = sectionContent(archive, name);
116
+ return content === null || sha256Hex(content) !== recorded;
117
+ })
118
+ .map(([name]) => name);
119
+ const sig = archive.signature ?? null;
120
+ return {
121
+ archiveSha256,
122
+ archiveHashMatches,
123
+ sectionMismatches,
124
+ sectionsChecked: sections.length,
125
+ signature: sig ? signatureState(sig, manifest.archiveSha256 ?? '', jwks) : 'unsigned',
126
+ signingKeyId: sig?.keyId ?? null,
127
+ unsignedReason: sig ? null : (manifest.unsignedReason ?? null),
128
+ };
129
+ }
130
+ function signatureState(sig, archiveSha256, jwks) {
131
+ // A signature made under another domain (a human-oversight or SLA export)
132
+ // or algorithm is not this archive's signature, whatever its bytes.
133
+ if (sig.preimageDomain !== EVIDENCE_SIGNING_DOMAIN || sig.algorithm !== 'Ed25519')
134
+ return 'invalid';
135
+ if (!jwks)
136
+ return 'keys_unavailable';
137
+ const jwk = jwks.keys.find((k) => k['kid'] === sig.keyId);
138
+ if (!jwk)
139
+ return 'unverifiable';
140
+ try {
141
+ const ok = nodeVerify(null, Buffer.from(`${EVIDENCE_SIGNING_DOMAIN}\n${archiveSha256}`, 'utf8'), createPublicKey({ key: jwk, format: 'jwk' }), Buffer.from(sig.value, 'base64'));
142
+ return ok ? 'valid' : 'invalid';
143
+ }
144
+ catch {
145
+ // A published key this build cannot load is one we do not effectively hold.
146
+ return 'unverifiable';
147
+ }
148
+ }
149
+ /**
150
+ * The exit code for a verification: 0 only for an archive whose hashes match
151
+ * and whose signature a published key accepts; 1 when the file or its
152
+ * signature contradicts the archive (it was changed); 2 when the hashes match
153
+ * but nothing establishes who produced it (unsigned, or the key could not be
154
+ * had), which a script must not read as a pass.
155
+ */
156
+ export function verificationExitCode(v) {
157
+ if (!v.archiveHashMatches || v.sectionMismatches.length > 0 || v.signature === 'invalid')
158
+ return 1;
159
+ return v.signature === 'valid' ? 0 : 2;
160
+ }
161
+ const SIGNATURE_TEXT = {
162
+ valid: 'valid: the published key accepts the signature over this archive hash',
163
+ invalid: 'INVALID: the signature does not match this archive',
164
+ unsigned: 'unsigned: the deployment had no signing key when this archive was collected',
165
+ unverifiable: 'not verified: no published key has this key id',
166
+ keys_unavailable: 'not verified: the published keys could not be fetched; pass --jwks <file>',
167
+ };
168
+ /**
169
+ * `verify`'s exit status for an input it cannot read (a missing file, one
170
+ * that is not JSON, one that is not an archive or a JWKS): its own status, so
171
+ * a script never reads a wrong path as a changed archive (1) or an unsigned
172
+ * one (2).
173
+ */
174
+ export const UNREADABLE_INPUT_EXIT = 3;
175
+ function unreadable(message) {
176
+ log.error(message);
177
+ process.exit(UNREADABLE_INPUT_EXIT);
178
+ }
179
+ function readInput(path) {
180
+ let text;
181
+ try {
182
+ text = readFileSync(path, 'utf8');
183
+ }
184
+ catch (err) {
185
+ unreadable(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
186
+ }
187
+ try {
188
+ return JSON.parse(text);
189
+ }
190
+ catch (err) {
191
+ unreadable(`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
192
+ }
193
+ }
194
+ /** The published key set, from a file when given, else from the control plane's unauthenticated endpoint. */
195
+ function loadJwks(path) {
196
+ const parsed = readInput(path);
197
+ if (!Array.isArray(parsed?.keys))
198
+ unreadable(`${path} is not a JWKS: it has no "keys" array`);
199
+ return parsed;
200
+ }
201
+ /** `intutic compliance verify <file>` */
202
+ export async function runComplianceVerify(file, opts) {
203
+ const archive = readInput(file);
204
+ if (!archive?.manifest?.archiveSha256)
205
+ unreadable(`${file} is not an evidence archive: it has no manifest.archiveSha256`);
206
+ const jwks = opts.jwks
207
+ ? loadJwks(opts.jwks)
208
+ : archive.signature
209
+ ? await fetchSigningKeys(resolveControlPlaneUrl(opts.dev))
210
+ : null;
211
+ const v = verifyEvidenceArchive(archive, jwks);
212
+ const code = verificationExitCode(v);
213
+ if (opts.json) {
214
+ console.log(JSON.stringify({ file, ...v, verified: code === 0 }, null, 2));
215
+ }
216
+ else {
217
+ log.header('Intutic — Evidence Archive');
218
+ if (archive.runId)
219
+ log.field('Run', archive.runId);
220
+ if (archive.collectedAt)
221
+ log.field('Collected', archive.collectedAt);
222
+ log.field('Archive hash', v.archiveHashMatches ? pc.green(`matches (${v.archiveSha256})`) : pc.red(`DOES NOT MATCH the manifest (recomputed ${v.archiveSha256})`));
223
+ log.field('Sections', v.sectionMismatches.length === 0
224
+ ? pc.green(`${v.sectionsChecked} of ${v.sectionsChecked} match`)
225
+ : pc.red(`${v.sectionMismatches.length} of ${v.sectionsChecked} DO NOT MATCH: ${v.sectionMismatches.join(', ')}`));
226
+ const text = SIGNATURE_TEXT[v.signature];
227
+ log.field('Signature', v.signature === 'valid' ? pc.green(text) : v.signature === 'invalid' ? pc.red(text) : pc.yellow(text));
228
+ if (v.signingKeyId)
229
+ log.field('Key id', v.signingKeyId);
230
+ if (v.unsignedReason)
231
+ log.dim(` ${v.unsignedReason}`);
232
+ if (code === 2)
233
+ log.warn('Not verified: the hashes match, but nothing shows who produced this archive.');
234
+ }
235
+ if (code !== 0)
236
+ process.exit(code);
237
+ }
238
+ /** `intutic compliance collect` */
239
+ export async function runComplianceCollect(opts) {
240
+ for (const [flag, value] of [['--from', opts.from], ['--to', opts.to]]) {
241
+ if (value !== undefined && Number.isNaN(Date.parse(value)))
242
+ fail(`${flag} must be an ISO 8601 date or datetime, got "${value}"`);
243
+ }
244
+ await runApiCommand(
245
+ // Not `opts`: with --json the response is printed, and --out still writes the archive.
246
+ { dev: opts.dev }, 'Failed to collect evidence', (client) => client.post('/api/v1/compliance/soc2-collect', { periodStart: opts.from, periodEnd: opts.to }), (res) => {
247
+ if (opts.out)
248
+ writeFileSync(opts.out, JSON.stringify(res.archive, null, 2));
249
+ if (opts.json) {
250
+ console.log(JSON.stringify(res, null, 2));
251
+ return;
252
+ }
253
+ log.header('Intutic — Evidence Collected');
254
+ log.field('Run', res.runId);
255
+ log.field('Period', `${res.archive.periodStart} to ${res.archive.periodEnd}`);
256
+ log.field('Overall score', res.archive.overallScore === null ? '— (no scored category)' : String(res.archive.overallScore));
257
+ log.field('Signed', res.signed ? pc.green(`yes (key ${res.signingKeyId})`) : pc.yellow('no'));
258
+ if (!res.signed && res.unsignedReason)
259
+ log.dim(` ${res.unsignedReason}`);
260
+ if (res.artifactUrl)
261
+ log.field('Stored at', res.artifactUrl);
262
+ if (opts.out)
263
+ log.success(`Wrote the archive to ${opts.out}. Check it: intutic compliance verify ${opts.out}`);
264
+ else
265
+ log.dim(` Download it: intutic compliance download ${res.runId} --out <file>`);
266
+ });
267
+ }
268
+ /** `intutic compliance download <run_id>` */
269
+ export async function runComplianceDownload(runId, opts) {
270
+ await runApiCommand(
271
+ // The output is the archive itself.
272
+ { dev: opts.dev }, `Failed to download evidence run ${runId}`, (client) => client.getFile(`/api/v1/compliance/soc2-export/${encodeURIComponent(runId)}`), (bytes) => {
273
+ writeOutput(bytes, opts.out, `evidence run ${runId}`);
274
+ if (opts.out)
275
+ log.dim(` Check it: intutic compliance verify ${opts.out}`);
276
+ });
277
+ }
278
+ //# sourceMappingURL=compliance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/commands/compliance.ts"],"names":[],"mappings":"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"}
@@ -5,11 +5,36 @@
5
5
  * a real-time filesystem watcher for configuration drift detection,
6
6
  * and a 30-second HTTP polling loop as a secondary fallback.
7
7
  *
8
- * LLD #14 — connect.ts
9
8
  * HLD §3.14 — Real-Time State Mirroring
10
9
  *
11
10
  * @module
12
11
  */
12
+ import type { SyncSopEntry } from '@intutic/shared-types';
13
+ /**
14
+ * Applies a synced configuration to each configured harness.
15
+ *
16
+ * The gate is installed whatever rule sets the workspace has: it enforces the
17
+ * built-in protections, the destructive-command tier, group rules and holds,
18
+ * none of which need a rule set. Gating it on rule sets left a workspace with
19
+ * none, or with none targeting a harness, with that harness ungoverned. The
20
+ * rules file is written only when a rule set targets the harness, or on a
21
+ * forced sync.
22
+ *
23
+ * Harnesses that read the same file (Codex, Grok Build, OpenCode, Muse Code
24
+ * and others all read `AGENTS.md`) each get every rule set aimed at any of
25
+ * them that is configured here: one section, the same for every writer, so
26
+ * the last writer no longer replaces the others' rules.
27
+ *
28
+ * Claude Code reads its own `.claude/rules/` file at every launch, and also
29
+ * the workspace's `AGENTS.md` when there is no `CLAUDE.md` on the path (or
30
+ * the user chose `claude-md-and-agents-md`; see claudeAgentsMd.ts). When it
31
+ * does, a rule set aimed at both Claude Code and an `AGENTS.md` reader
32
+ * reaches it through `AGENTS.md`, so its own file leaves that rule set out
33
+ * rather than load it twice.
34
+ *
35
+ * @returns how many rule sets were written into rules files.
36
+ */
37
+ export declare function writeHarnessConfigs(harnesses: readonly string[], workspaceRoot: string, sops: readonly SyncSopEntry[], proxyUrl: string, force: boolean): Promise<number>;
13
38
  /**
14
39
  * Asset names MUST match publish.yml's build-rust-proxy matrix
15
40
  * artifact_name — mirrors packages/proxy/bin/proxy.js's `resolveAssetName`
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6GH;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,GAC1B,MAAM,GAAG,IAAI,CAWf;AA0DD,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAssChB"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/commands/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8BH,OAAO,KAAK,EAAkC,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAuEzF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,IAAI,GAAE,MAAqB,GAC1B,MAAM,GAAG,IAAI,CAWf;AA0DD,wBAAsB,UAAU,CAAC,IAAI,EAAE;IACrC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2tChB"}
@@ -5,7 +5,6 @@
5
5
  * a real-time filesystem watcher for configuration drift detection,
6
6
  * and a 30-second HTTP polling loop as a secondary fallback.
7
7
  *
8
- * LLD #14 — connect.ts
9
8
  * HLD §3.14 — Real-Time State Mirroring
10
9
  *
11
10
  * @module
@@ -25,10 +24,12 @@ import { getAdapter } from '../harness/detector.js';
25
24
  import { printOnboardingGuide } from '../lib/onboarding.js';
26
25
  import { writeEnforcementState } from '../lib/enforcementState.js';
27
26
  import { reportDeviceState } from '../lib/deviceReport.js';
27
+ import { reportMachineInventory, shouldReportInventoryThisIteration } from '../lib/inventory.js';
28
28
  import { parseChecksums, verifyChecksum } from '../lib/binaryChecksum.js';
29
29
  import { newIso } from '@intutic/id';
30
+ import { HarnessType, rulesFileOf } from '@intutic/shared-types';
30
31
  import pc from 'picocolors';
31
- import { SyncWsClient, startWatcher, updatePreToolUseHooks, injectMcpServer, noteProxyUrl, guardSettingsFile, warnIfDshCoverageGap, writeRuntimeEnv, refreshPolicySnapshot, refreshGateCaches, localHoldTokensFor, runComplianceProbes, drainHookEvents, drainReviewRequests, REVIEW_REQUESTS_LOG, syncOfflineTraces, TrajectoryMonitor, fetchLocalProxyInstanceId, endAllOpenSessions, applySkillOptEdits, reportHarnessAgents, captureAndUpload, shouldCaptureThisIteration, refreshDecisionsDigest, writeBundledSkills, clearImmutable, setImmutable, } from '@intutic/sync-daemon';
32
+ import { SyncWsClient, startWatcher, updatePreToolUseHooks, injectMcpServer, noteProxyUrl, guardPolicySnapshot, guardSettingsFile, isGuardedPath, warnIfDshCoverageGap, writeRuntimeEnv, refreshPolicySnapshot, refreshGateCaches, localHoldTokensFor, runComplianceProbes, drainHookEvents, drainReviewRequests, REVIEW_REQUESTS_LOG, syncOfflineTraces, TrajectoryMonitor, fetchLocalProxyInstanceId, endAllOpenSessions, applySkillOptEdits, reportHarnessAgents, captureAndUpload, shouldCaptureThisIteration, refreshDecisionsDigest, retireClaudeMdDigest, claudeCodeReadsAgentsMd, writeBundledSkills, clearImmutable, setImmutable, } from '@intutic/sync-daemon';
32
33
  import { watch } from 'chokidar';
33
34
  // Named, not default: under `module: Node16` TypeScript resolves ioredis's
34
35
  // default export to the module namespace rather than the class, so `new
@@ -61,20 +62,57 @@ function isPortInUse(port) {
61
62
  });
62
63
  }
63
64
  /**
64
- * Is this a path the tamper guard should inspect?
65
+ * Applies a synced configuration to each configured harness.
65
66
  *
66
- * Deliberately broad: `guardSettingsFile` already branches per harness, so the only
67
- * job here is to stop filtering out paths it knows how to handle.
67
+ * The gate is installed whatever rule sets the workspace has: it enforces the
68
+ * built-in protections, the destructive-command tier, group rules and holds,
69
+ * none of which need a rule set. Gating it on rule sets left a workspace with
70
+ * none, or with none targeting a harness, with that harness ungoverned. The
71
+ * rules file is written only when a rule set targets the harness, or on a
72
+ * forced sync.
73
+ *
74
+ * Harnesses that read the same file (Codex, Grok Build, OpenCode, Muse Code
75
+ * and others all read `AGENTS.md`) each get every rule set aimed at any of
76
+ * them that is configured here: one section, the same for every writer, so
77
+ * the last writer no longer replaces the others' rules.
78
+ *
79
+ * Claude Code reads its own `.claude/rules/` file at every launch, and also
80
+ * the workspace's `AGENTS.md` when there is no `CLAUDE.md` on the path (or
81
+ * the user chose `claude-md-and-agents-md`; see claudeAgentsMd.ts). When it
82
+ * does, a rule set aimed at both Claude Code and an `AGENTS.md` reader
83
+ * reaches it through `AGENTS.md`, so its own file leaves that rule set out
84
+ * rather than load it twice.
85
+ *
86
+ * @returns how many rule sets were written into rules files.
68
87
  */
69
- function isGovernedConfigPath(changedPath, filename) {
70
- if (filename === 'settings.json' || filename === 'settings.local.json')
71
- return true;
72
- if (filename === 'hooks.json')
73
- return true;
74
- // Goose's immutable governance plugin.
75
- if (changedPath.includes('intutic-governance'))
76
- return true;
77
- return false;
88
+ export async function writeHarnessConfigs(harnesses, workspaceRoot, sops, proxyUrl, force) {
89
+ let written = 0;
90
+ const writtenFiles = new Set();
91
+ const agentsMdReaders = harnesses.filter((h) => rulesFileOf(h) === 'AGENTS.md');
92
+ const claudeFromAgentsMd = harnesses.includes(HarnessType.CLAUDE_CODE) && agentsMdReaders.length > 0 && (await claudeCodeReadsAgentsMd(workspaceRoot));
93
+ for (const harnessType of harnesses) {
94
+ const adapter = getAdapter(harnessType);
95
+ if (!adapter)
96
+ continue;
97
+ await adapter.installGate?.(workspaceRoot, proxyUrl);
98
+ const file = rulesFileOf(harnessType);
99
+ const readers = file === null ? [harnessType] : harnesses.filter((h) => rulesFileOf(h) === file);
100
+ const aimed = sops.filter((sop) => sop.harnessTargets.some((t) => readers.includes(t)));
101
+ const viaAgentsMd = harnessType === HarnessType.CLAUDE_CODE && claudeFromAgentsMd;
102
+ const targetSops = viaAgentsMd ? aimed.filter((sop) => !sop.harnessTargets.some((t) => agentsMdReaders.includes(t))) : aimed;
103
+ // Claude Code's own file is rewritten even when every rule set aimed at
104
+ // it now comes through AGENTS.md, so it stops carrying them.
105
+ if (aimed.length === 0 && !force)
106
+ continue;
107
+ if (!(await adapter.writeConfig(workspaceRoot, targetSops, proxyUrl)))
108
+ continue;
109
+ // A shared file counts once, however many of its readers wrote it.
110
+ if (file === null || !writtenFiles.has(file))
111
+ written += targetSops.length;
112
+ if (file !== null)
113
+ writtenFiles.add(file);
114
+ }
115
+ return written;
78
116
  }
79
117
  /**
80
118
  * Asset names MUST match publish.yml's build-rust-proxy matrix
@@ -262,7 +300,7 @@ export async function runConnect(opts) {
262
300
  // 2. AbortController for clean shutdown
263
301
  let proxyProc = null;
264
302
  const ac = new AbortController();
265
- // TD-484: this handler used to abort the loop and hope. The process stayed
303
+ // This handler used to abort the loop and hope. The process stayed
266
304
  // alive for hours. `createShutdownHandler` adds the exit deadline and the
267
305
  // second-signal exit; `terminateChild` escalates to SIGKILL.
268
306
  const shutdown = createShutdownHandler({
@@ -453,12 +491,18 @@ export async function runConnect(opts) {
453
491
  // when the config version moved: policy changes without the config version
454
492
  // changing, and a guardrail promoted, a hold approved or an egress mode
455
493
  // flipped mid-session used to reach a connected machine only on restart —
456
- // this function was called once, at startup (TD-488). None of these throw.
494
+ // this function was called once, at startup. None of these throw.
457
495
  //
458
496
  // The snapshot also carries this workspace's `review_before:` tokens as hold
459
497
  // rules (synced SOPs, settings and local `.intutic/sops`), so every gate —
460
- // not only the Claude Code hook — holds on them (TD-474 item 4).
461
- async function refreshGateCachesForConnect() {
498
+ // not only the Claude Code hook — holds on them.
499
+ //
500
+ // First, the snapshot's self-heal: a snapshot changed while the daemon was
501
+ // stopped is put back to the last verified copy, and reported, before the
502
+ // refresh replaces it. With `checkSnapshot` false the caller is that check.
503
+ async function refreshGateCachesForConnect(checkSnapshot = true) {
504
+ if (checkSnapshot)
505
+ await guardPolicySnapshot(safeConfig.workspaceRoot, async () => { });
462
506
  const localHoldTokens = await localHoldTokensFor(safeConfig.workspaceRoot, lastCachedConfig?.sops ?? [], lastCachedConfig?.settings, safeConfig.harnesses);
463
507
  const landed = await refreshGateCaches({ controlPlaneUrl, apiKey: safeCreds.apiKey, workspaceId: safeCreds.workspaceId, localHoldTokens });
464
508
  if (!landed.snapshot)
@@ -471,18 +515,28 @@ export async function runConnect(opts) {
471
515
  .map((h) => getAdapter(h)?.configFileName)
472
516
  .filter((f) => Boolean(f))
473
517
  .map((f) => node_path.resolve(safeConfig.workspaceRoot, f));
518
+ // Whether Claude Code read the workspace's AGENTS.md at the last sync. A
519
+ // CLAUDE.md added or removed (or the user's Project instructions setting
520
+ // changed) moves rule sets between AGENTS.md and Claude Code's own file, so
521
+ // a change rewrites the rules files as a new config would.
522
+ let lastClaudeFromAgentsMd;
474
523
  // 3. Define configuration applier function
475
524
  async function applySyncConfig(syncConfig, force = false) {
476
525
  let sopsWritten = 0;
477
526
  lastCachedConfig = syncConfig;
478
527
  await refreshGateCachesForConnect();
528
+ const claudeFromAgentsMd = safeConfig.harnesses.includes(HarnessType.CLAUDE_CODE)
529
+ ? await claudeCodeReadsAgentsMd(safeConfig.workspaceRoot)
530
+ : false;
531
+ const claudeDeliveryMoved = lastClaudeFromAgentsMd !== undefined && claudeFromAgentsMd !== lastClaudeFromAgentsMd;
532
+ lastClaudeFromAgentsMd = claudeFromAgentsMd;
479
533
  // Write-protect (`bypassEnforcementTier: 'immutable'`, macOS): the rules
480
534
  // files carry the user-immutable flag between cycles, so it comes off
481
535
  // before anything below writes them and goes back on afterwards. It also
482
536
  // comes off whenever the config moved, so a workspace that switched away
483
537
  // from write-protect is not left with files nothing can rewrite.
484
538
  const writeProtect = syncConfig.settings?.bypassEnforcementTier === 'immutable';
485
- const configMoved = syncConfig.configVersion > localConfigVersion || force;
539
+ const configMoved = syncConfig.configVersion > localConfigVersion || force || claudeDeliveryMoved;
486
540
  if (writeProtect || configMoved) {
487
541
  for (const file of rulesFilePaths())
488
542
  await clearImmutable(file);
@@ -545,18 +599,7 @@ export async function runConnect(opts) {
545
599
  // first: `intutic disconnect` recognises the base-URL settings it is
546
600
  // written into by it.
547
601
  await noteProxyUrl(syncConfig.proxyUrl);
548
- for (const harnessType of safeConfig.harnesses) {
549
- const adapter = getAdapter(harnessType);
550
- if (!adapter)
551
- continue;
552
- const targetSops = combinedSops.filter((sop) => sop.harnessTargets.includes(harnessType));
553
- if (targetSops.length === 0 && !force)
554
- continue;
555
- const written = await adapter.writeConfig(safeConfig.workspaceRoot, targetSops, syncConfig.proxyUrl);
556
- if (written) {
557
- sopsWritten += targetSops.length;
558
- }
559
- }
602
+ sopsWritten += await writeHarnessConfigs(safeConfig.harnesses, safeConfig.workspaceRoot, combinedSops, syncConfig.proxyUrl, force);
560
603
  // b. Invalidate/update Claude Code hooks and settings
561
604
  if (safeConfig.harnesses.includes('claude-code')) {
562
605
  try {
@@ -596,7 +639,11 @@ export async function runConnect(opts) {
596
639
  reapplyAll: configMoved,
597
640
  });
598
641
  // e. The governed decisions log, opt-in (`decisionsLogEnabled`, off by
599
- // default). Also before the hashes: it writes a section into CLAUDE.md.
642
+ // default), into each harness's instructions file. Also before the hashes:
643
+ // it writes into shared files such as AGENTS.md, whose rules section they
644
+ // cover. The section earlier versions put in CLAUDE.md comes out whether
645
+ // or not the log is on.
646
+ await retireClaudeMdDigest(safeConfig.workspaceRoot);
600
647
  if (syncConfig.settings?.decisionsLogEnabled) {
601
648
  await refreshDecisionsDigest({
602
649
  controlPlaneUrl,
@@ -746,7 +793,7 @@ export async function runConnect(opts) {
746
793
  proxyStatus = 'unhealthy';
747
794
  if (ac.signal.aborted) {
748
795
  // Shutting down: the proxy is gone because we stopped it. Re-spawning
749
- // it here would leave an orphan holding the port (TD-484).
796
+ // it here would leave an orphan holding the port.
750
797
  proxyStatus = 'stopped';
751
798
  }
752
799
  else if (proxyProc) {
@@ -813,7 +860,7 @@ export async function runConnect(opts) {
813
860
  catch (err) {
814
861
  log.warn(`Failed to send daemon heartbeat: ${err instanceof Error ? err.message : String(err)}`);
815
862
  }
816
- // g. Enforcement device visibility (post-strip gap #2, LLD #63
863
+ // g. Enforcement device visibility (post-strip gap #2, egress
817
864
  // hardening) — the "interval" half of "continuous compliance": this
818
865
  // loop already runs unprivileged, as the real user, on a timer, and
819
866
  // already just computed sslTrustStatus above for its own DR-healing
@@ -848,7 +895,7 @@ export async function runConnect(opts) {
848
895
  mcpProxyFailBehavior: syncConfig.settings?.mcpProxyFailBehavior,
849
896
  mcpProxyMode: syncConfig.settings?.mcpProxyMode,
850
897
  bypassEnforcementTier: syncConfig.settings?.bypassEnforcementTier,
851
- // Shared by the MCP proxies for their session window (Wave 5.3, TD-437) — only when it is running.
898
+ // Shared by the MCP proxies for their session window — only when it is running.
852
899
  valkeyUrl: valkeyResult.running ? valkeyUrl : undefined,
853
900
  });
854
901
  }
@@ -950,7 +997,7 @@ export async function runConnect(opts) {
950
997
  controlPlaneUrl,
951
998
  apiKey: safeCreds.apiKey,
952
999
  workspaceId: safeCreds.workspaceId,
953
- // Shared by the MCP proxies for their session window (Wave 5.3, TD-437) — only when it is running.
1000
+ // Shared by the MCP proxies for their session window — only when it is running.
954
1001
  valkeyUrl: valkeyResult.running ? valkeyUrl : undefined,
955
1002
  });
956
1003
  }
@@ -1075,7 +1122,7 @@ export async function runConnect(opts) {
1075
1122
  const drainSafetyTimer = setInterval(runDrain, 60_000);
1076
1123
  // Run initial compliance check on startup
1077
1124
  await runProbes();
1078
- // dsh coverage-gap visibility (TD-370): once at startup, not every poll
1125
+ // dsh coverage-gap visibility: once at startup, not every poll
1079
1126
  // tick — the gap only changes state on the user's first `dsh --profile
1080
1127
  // <name>` run, which the filesystem watcher below reacts to immediately
1081
1128
  // via settingsGuard.ts's isDshProfilesRoot `addDir` handling. Only worth
@@ -1121,22 +1168,17 @@ export async function runConnect(opts) {
1121
1168
  }
1122
1169
  // B. Tamper detection for every governed config, not just Claude Code's.
1123
1170
  //
1124
- // This used to filter on `filename === 'settings.json'`, which silently disabled
1125
- // five of the six branches inside guardSettingsFile: the Cursor, Windsurf, Cline
1126
- // and OpenHands restores all key off `hooks.json`, and the Goose
1127
- // governance-plugin override incident keys off a path containing
1128
- // `intutic-governance`. None of those basenames could ever reach the guard, so a
1129
- // tampered hooks file on any harness but Claude Code was a no-op — and a tampered
1130
- // Windsurf/VS Code/Gemini settings file passed the basename test only to fall
1131
- // through and log `settings_intact`.
1132
- //
1133
- // Route anything on the governed list to the guard and let it decide.
1134
- if (isGovernedConfigPath(changedPath, filename)) {
1171
+ // Routed on the guard's own watch list. A filter on file names here, first
1172
+ // `settings.json` and later `settings.json`/`hooks.json`/`intutic-governance`,
1173
+ // kept the guard from ever seeing the paths it restores under other names:
1174
+ // a gate script, Cline's PreToolUse, OpenClaw's openclaw.json, Muse's
1175
+ // managed-hooks file and every dsh profile patch.
1176
+ if (isGuardedPath(changedPath, safeConfig.workspaceRoot)) {
1135
1177
  try {
1136
1178
  const sops = lastCachedConfig?.sops ?? [];
1137
1179
  // The proxy URL too: the restored gate scripts carry it, and an empty
1138
1180
  // one would be written into them.
1139
- const tampered = await guardSettingsFile(changedPath, safeConfig.workspaceRoot, sops, lastCachedConfig?.proxyUrl ?? '', undefined, new Set(safeConfig.disconnectedHarnesses ?? []));
1181
+ const tampered = await guardSettingsFile(changedPath, safeConfig.workspaceRoot, sops, lastCachedConfig?.proxyUrl ?? '', undefined, new Set(safeConfig.disconnectedHarnesses ?? []), () => refreshGateCachesForConnect(false));
1140
1182
  if (tampered) {
1141
1183
  log.warn(`[Security] Governance settings tamper detected and restored: ${changedPath}`);
1142
1184
  wsClient.send({
@@ -1153,8 +1195,9 @@ export async function runConnect(opts) {
1153
1195
  }
1154
1196
  return;
1155
1197
  }
1156
- // B. Handle governed harness file drift detection
1157
- const matchingHarness = safeConfig.harnesses.find((h) => getAdapter(h)?.configFileName === filename);
1198
+ // B. Handle governed harness file drift detection. By path, not
1199
+ // basename: rules files sit in directories (`.cursor/rules/…`).
1200
+ const matchingHarness = safeConfig.harnesses.find((h) => getAdapter(h)?.configFileName === relativePath.split(node_path.sep).join('/'));
1158
1201
  if (!matchingHarness)
1159
1202
  return;
1160
1203
  const adapter = getAdapter(matchingHarness);
@@ -1248,6 +1291,21 @@ export async function runConnect(opts) {
1248
1291
  for (const { harness, error } of failures) {
1249
1292
  log.dim(`Agent report/session for harness '${harness}' failed: ${error}`);
1250
1293
  }
1294
+ // Every few minutes, the machine's AI inventory for the org-wide view:
1295
+ // every harness the detection rules find, connected or not, with its
1296
+ // gate state, and the MCP servers and skill bundles on the machine.
1297
+ if (shouldReportInventoryThisIteration(pollIteration)) {
1298
+ const inventory = await reportMachineInventory({
1299
+ controlPlaneUrl,
1300
+ apiKey: safeCreds.apiKey,
1301
+ workspaceRoot: safeConfig.workspaceRoot,
1302
+ configured: safeConfig.harnesses,
1303
+ disconnected: safeConfig.disconnectedHarnesses,
1304
+ cliVersion: cliPkgVersion,
1305
+ });
1306
+ if (!inventory.reported)
1307
+ log.dim(`AI inventory report not sent (will retry): ${inventory.reason}`);
1308
+ }
1251
1309
  // Every Nth poll, capture the rules files that changed for the config
1252
1310
  // history. Content goes only when this poll's settings have
1253
1311
  // `configBodyUpload` on; otherwise path, hash, size and time.