@principles/pd-cli 1.152.12 → 1.152.14

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 (305) hide show
  1. package/dist/commands/candidate.d.ts.map +1 -1
  2. package/dist/commands/candidate.js +34 -7
  3. package/dist/commands/candidate.js.map +1 -1
  4. package/dist/commands/diagnose.d.ts.map +1 -1
  5. package/dist/commands/diagnose.js +23 -7
  6. package/dist/commands/diagnose.js.map +1 -1
  7. package/dist/commands/pain-retry.d.ts.map +1 -1
  8. package/dist/commands/pain-retry.js +21 -7
  9. package/dist/commands/pain-retry.js.map +1 -1
  10. package/package.json +13 -7
  11. package/CHANGELOG.md +0 -23
  12. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts +0 -9
  13. package/dist/commands/__tests__/intent-flag-wiring.test.d.ts.map +0 -1
  14. package/dist/commands/__tests__/intent-flag-wiring.test.js +0 -237
  15. package/dist/commands/__tests__/intent-flag-wiring.test.js.map +0 -1
  16. package/dist/commands/__tests__/legacy-cleanup.test.d.ts +0 -18
  17. package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +0 -1
  18. package/dist/commands/__tests__/legacy-cleanup.test.js +0 -467
  19. package/dist/commands/__tests__/legacy-cleanup.test.js.map +0 -1
  20. package/dist/commands/__tests__/mvp-smoke.test.d.ts +0 -15
  21. package/dist/commands/__tests__/mvp-smoke.test.d.ts.map +0 -1
  22. package/dist/commands/__tests__/mvp-smoke.test.js +0 -262
  23. package/dist/commands/__tests__/mvp-smoke.test.js.map +0 -1
  24. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts +0 -2
  25. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.d.ts.map +0 -1
  26. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js +0 -60
  27. package/dist/commands/__tests__/pain-retry-maxTokens-flag.test.js.map +0 -1
  28. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +0 -21
  29. package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +0 -1
  30. package/dist/commands/__tests__/rulecode-flag-wiring.test.js +0 -186
  31. package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +0 -1
  32. package/dist/commands/__tests__/rulecode-handler.test.d.ts +0 -16
  33. package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +0 -1
  34. package/dist/commands/__tests__/rulecode-handler.test.js +0 -285
  35. package/dist/commands/__tests__/rulecode-handler.test.js.map +0 -1
  36. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts +0 -24
  37. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.d.ts.map +0 -1
  38. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +0 -232
  39. package/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +0 -1
  40. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts +0 -15
  41. package/dist/commands/__tests__/runtime-activation-nextaction.test.d.ts.map +0 -1
  42. package/dist/commands/__tests__/runtime-activation-nextaction.test.js +0 -73
  43. package/dist/commands/__tests__/runtime-activation-nextaction.test.js.map +0 -1
  44. package/dist/commands/__tests__/runtime-probe-config.test.d.ts +0 -20
  45. package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +0 -1
  46. package/dist/commands/__tests__/runtime-probe-config.test.js +0 -389
  47. package/dist/commands/__tests__/runtime-probe-config.test.js.map +0 -1
  48. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts +0 -9
  49. package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +0 -1
  50. package/dist/commands/__tests__/telemetry-flag-wiring.test.js +0 -76
  51. package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +0 -1
  52. package/dist/commands/__tests__/version.test.d.ts +0 -2
  53. package/dist/commands/__tests__/version.test.d.ts.map +0 -1
  54. package/dist/commands/__tests__/version.test.js +0 -238
  55. package/dist/commands/__tests__/version.test.js.map +0 -1
  56. package/dist/commands/runtime-uat.guard.test.d.ts +0 -2
  57. package/dist/commands/runtime-uat.guard.test.d.ts.map +0 -1
  58. package/dist/commands/runtime-uat.guard.test.js +0 -153
  59. package/dist/commands/runtime-uat.guard.test.js.map +0 -1
  60. package/dist/services/__tests__/cli-output.test.d.ts +0 -18
  61. package/dist/services/__tests__/cli-output.test.d.ts.map +0 -1
  62. package/dist/services/__tests__/cli-output.test.js +0 -103
  63. package/dist/services/__tests__/cli-output.test.js.map +0 -1
  64. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts +0 -2
  65. package/dist/services/__tests__/evaluator-runner-deps.test.d.ts.map +0 -1
  66. package/dist/services/__tests__/evaluator-runner-deps.test.js +0 -343
  67. package/dist/services/__tests__/evaluator-runner-deps.test.js.map +0 -1
  68. package/dist/services/__tests__/rulehost-readiness.test.d.ts +0 -2
  69. package/dist/services/__tests__/rulehost-readiness.test.d.ts.map +0 -1
  70. package/dist/services/__tests__/rulehost-readiness.test.js +0 -311
  71. package/dist/services/__tests__/rulehost-readiness.test.js.map +0 -1
  72. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +0 -18
  73. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +0 -1
  74. package/dist/services/__tests__/runtime-adapter-resolver.test.js +0 -673
  75. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +0 -1
  76. package/dist/utils/production-workspace-guard.test.d.ts +0 -2
  77. package/dist/utils/production-workspace-guard.test.d.ts.map +0 -1
  78. package/dist/utils/production-workspace-guard.test.js +0 -137
  79. package/dist/utils/production-workspace-guard.test.js.map +0 -1
  80. package/scripts/llm-dogfood.ts +0 -456
  81. package/scripts/migrate-illegal-expected-decision.ts +0 -252
  82. package/src/commands/__tests__/intent-flag-wiring.test.ts +0 -282
  83. package/src/commands/__tests__/legacy-cleanup.test.ts +0 -602
  84. package/src/commands/__tests__/mvp-smoke.test.ts +0 -300
  85. package/src/commands/__tests__/pain-retry-maxTokens-flag.test.ts +0 -73
  86. package/src/commands/__tests__/rulecode-flag-wiring.test.ts +0 -237
  87. package/src/commands/__tests__/rulecode-handler.test.ts +0 -369
  88. package/src/commands/__tests__/run-rulehost-flag-wiring.test.ts +0 -292
  89. package/src/commands/__tests__/runtime-activation-nextaction.test.ts +0 -81
  90. package/src/commands/__tests__/runtime-probe-config.test.ts +0 -432
  91. package/src/commands/__tests__/telemetry-flag-wiring.test.ts +0 -84
  92. package/src/commands/__tests__/version.test.ts +0 -251
  93. package/src/commands/admission-gate.ts +0 -30
  94. package/src/commands/artifact.ts +0 -82
  95. package/src/commands/build-trajectory-evidence.ts +0 -298
  96. package/src/commands/candidate.ts +0 -1250
  97. package/src/commands/codex-ingest-catchup.ts +0 -114
  98. package/src/commands/codex-ingest-quarantine.ts +0 -127
  99. package/src/commands/codex-reconcile.ts +0 -84
  100. package/src/commands/codex-setup.ts +0 -476
  101. package/src/commands/codex-worker.ts +0 -185
  102. package/src/commands/command-helpers.ts +0 -24
  103. package/src/commands/config-doctor.ts +0 -193
  104. package/src/commands/console.ts +0 -797
  105. package/src/commands/context.ts +0 -72
  106. package/src/commands/demo-story-a.ts +0 -241
  107. package/src/commands/diagnose.ts +0 -756
  108. package/src/commands/errors-list.ts +0 -441
  109. package/src/commands/evolution-tasks-list.ts +0 -48
  110. package/src/commands/evolution-tasks-show.ts +0 -62
  111. package/src/commands/flow.ts +0 -60
  112. package/src/commands/health-codex.ts +0 -576
  113. package/src/commands/health.ts +0 -315
  114. package/src/commands/history.ts +0 -63
  115. package/src/commands/intent.ts +0 -457
  116. package/src/commands/legacy-cleanup.ts +0 -531
  117. package/src/commands/legacy-import.ts +0 -104
  118. package/src/commands/mvp-smoke.ts +0 -160
  119. package/src/commands/pain-evidence.ts +0 -226
  120. package/src/commands/pain-list.ts +0 -239
  121. package/src/commands/pain-record.ts +0 -612
  122. package/src/commands/pain-retry.ts +0 -916
  123. package/src/commands/principles-stats.ts +0 -700
  124. package/src/commands/proven-channel-baseline.ts +0 -115
  125. package/src/commands/quality-scorecard.ts +0 -272
  126. package/src/commands/remediation-output.ts +0 -66
  127. package/src/commands/rulecode.ts +0 -465
  128. package/src/commands/run.ts +0 -89
  129. package/src/commands/runtime-activation.ts +0 -1629
  130. package/src/commands/runtime-artifact-repair.ts +0 -524
  131. package/src/commands/runtime-canary.ts +0 -395
  132. package/src/commands/runtime-compatibility-scan.ts +0 -105
  133. package/src/commands/runtime-diagnostics-export.ts +0 -229
  134. package/src/commands/runtime-features.ts +0 -153
  135. package/src/commands/runtime-gfi-snapshot.ts +0 -135
  136. package/src/commands/runtime-health-snapshot.ts +0 -108
  137. package/src/commands/runtime-init.ts +0 -429
  138. package/src/commands/runtime-internalization-enqueue-successors.ts +0 -530
  139. package/src/commands/runtime-internalization-integrity-repair.ts +0 -79
  140. package/src/commands/runtime-internalization-integrity.ts +0 -107
  141. package/src/commands/runtime-internalization-queue.ts +0 -135
  142. package/src/commands/runtime-internalization-retry.ts +0 -191
  143. package/src/commands/runtime-internalization-run-once.ts +0 -791
  144. package/src/commands/runtime-internalization-run-rulehost.ts +0 -749
  145. package/src/commands/runtime-internalization-wake-once.ts +0 -87
  146. package/src/commands/runtime-pain-flood-simulation.ts +0 -121
  147. package/src/commands/runtime-pruning.ts +0 -438
  148. package/src/commands/runtime-recovery-failed-tasks.ts +0 -258
  149. package/src/commands/runtime-recovery.ts +0 -109
  150. package/src/commands/runtime-synthetic-baseline.ts +0 -70
  151. package/src/commands/runtime-uat.guard.test.ts +0 -175
  152. package/src/commands/runtime-uat.ts +0 -380
  153. package/src/commands/runtime.ts +0 -473
  154. package/src/commands/samples-list.ts +0 -39
  155. package/src/commands/samples-review.ts +0 -36
  156. package/src/commands/task.ts +0 -271
  157. package/src/commands/telemetry.ts +0 -349
  158. package/src/commands/trace.ts +0 -174
  159. package/src/commands/trajectory-db-unavailable.ts +0 -19
  160. package/src/commands/trajectory.ts +0 -64
  161. package/src/commands/version.ts +0 -49
  162. package/src/config-reader.ts +0 -122
  163. package/src/index.ts +0 -1276
  164. package/src/legacy/legacy-import.ts +0 -179
  165. package/src/legacy/session-history-import.ts +0 -203
  166. package/src/resolve-workspace.ts +0 -106
  167. package/src/services/__tests__/cli-output.test.ts +0 -130
  168. package/src/services/__tests__/evaluator-runner-deps.test.ts +0 -382
  169. package/src/services/__tests__/rulehost-readiness.test.ts +0 -363
  170. package/src/services/__tests__/runtime-adapter-resolver.test.ts +0 -793
  171. package/src/services/cli-output.ts +0 -95
  172. package/src/services/config-doctor.ts +0 -697
  173. package/src/services/console-launcher.ts +0 -537
  174. package/src/services/demo-rule-compiler.ts +0 -40
  175. package/src/services/demo-story-a-runner.ts +0 -471
  176. package/src/services/mainline-snapshot-assembler.ts +0 -579
  177. package/src/services/pain-flood-simulation-runner.ts +0 -462
  178. package/src/services/pd-config-loader.ts +0 -433
  179. package/src/services/proven-channel-baseline-runner.ts +0 -157
  180. package/src/services/quality-scorecard/data-extractor.ts +0 -150
  181. package/src/services/quality-scorecard/local-evaluator.ts +0 -142
  182. package/src/services/quality-scorecard/strong-model-gate.ts +0 -202
  183. package/src/services/resolve-runtime-from-pd-config.ts +0 -230
  184. package/src/services/rulehost-pipeline-runner.ts +0 -1214
  185. package/src/services/rulehost-readiness.ts +0 -326
  186. package/src/services/runtime-adapter-resolver.ts +0 -364
  187. package/src/services/synthetic-baseline-runner.ts +0 -400
  188. package/src/services/version-report.ts +0 -253
  189. package/src/services/workspace-tool-semantics.ts +0 -26
  190. package/src/utils/path-security.ts +0 -96
  191. package/src/utils/production-workspace-guard.test.ts +0 -157
  192. package/src/utils/production-workspace-guard.ts +0 -210
  193. package/tests/bdd/cli-contract.steps.ts +0 -346
  194. package/tests/bdd/principles-stats.steps.ts +0 -211
  195. package/tests/bdd/support/gherkin-loader.ts +0 -147
  196. package/tests/bdd/support/repo-root.ts +0 -62
  197. package/tests/bdd/support/vitest-bdd.ts +0 -179
  198. package/tests/commands/admission-gate-coverage.test.ts +0 -79
  199. package/tests/commands/build-trajectory-evidence.test.ts +0 -508
  200. package/tests/commands/candidate-audit-repair.test.ts +0 -421
  201. package/tests/commands/candidate-intake.test.ts +0 -732
  202. package/tests/commands/candidate-internalization-backfill.test.ts +0 -548
  203. package/tests/commands/candidate-internalize-lineage.test.ts +0 -628
  204. package/tests/commands/candidate-internalize.test.ts +0 -299
  205. package/tests/commands/candidate-route.test.ts +0 -329
  206. package/tests/commands/candidate-show.test.ts +0 -95
  207. package/tests/commands/cli-command-tree.test.ts +0 -144
  208. package/tests/commands/cli-help-snapshot.test.ts +0 -155
  209. package/tests/commands/cli-skill-contract.test.ts +0 -121
  210. package/tests/commands/codex-consent-reversibility.steps.test.ts +0 -299
  211. package/tests/commands/codex-ingest-quarantine.test.ts +0 -136
  212. package/tests/commands/codex-setup.test.ts +0 -297
  213. package/tests/commands/codex-worker-daemon.test.ts +0 -95
  214. package/tests/commands/codex-worker-registration.test.ts +0 -207
  215. package/tests/commands/config-doctor.test.ts +0 -336
  216. package/tests/commands/console-launcher-edge-cases.test.ts +0 -388
  217. package/tests/commands/console-open.test.ts +0 -1381
  218. package/tests/commands/context.test.ts +0 -114
  219. package/tests/commands/demo-story-a.test.ts +0 -318
  220. package/tests/commands/diagnose.test.ts +0 -1803
  221. package/tests/commands/errors-list.test.ts +0 -679
  222. package/tests/commands/evolution-tasks-list.test.ts +0 -41
  223. package/tests/commands/evolution-tasks-show.test.ts +0 -46
  224. package/tests/commands/health-codex.test.ts +0 -349
  225. package/tests/commands/health-host-wiring.test.ts +0 -76
  226. package/tests/commands/health-reliability.test.ts +0 -99
  227. package/tests/commands/health.test.ts +0 -413
  228. package/tests/commands/intent.test.ts +0 -321
  229. package/tests/commands/legacy-cleanup.test.ts +0 -148
  230. package/tests/commands/pain-evidence.test.ts +0 -518
  231. package/tests/commands/pain-list.test.ts +0 -219
  232. package/tests/commands/pain-record-async.test.ts +0 -335
  233. package/tests/commands/pain-record-session-parser.test.ts +0 -228
  234. package/tests/commands/pain-record.test.ts +0 -851
  235. package/tests/commands/pain-retry.test.ts +0 -1121
  236. package/tests/commands/plugin-config-resolution-cutover.test.ts +0 -229
  237. package/tests/commands/pri-393-runtime-config-unification.test.ts +0 -443
  238. package/tests/commands/principles-stats-wiring.test.ts +0 -135
  239. package/tests/commands/principles-stats.test.ts +0 -274
  240. package/tests/commands/product-path-regression.test.ts +0 -86
  241. package/tests/commands/proven-channel-baseline.test.ts +0 -441
  242. package/tests/commands/rulecode.test.ts +0 -177
  243. package/tests/commands/run-rulehost-handler.test.ts +0 -908
  244. package/tests/commands/runtime-activation-approve-flag-wiring.test.ts +0 -227
  245. package/tests/commands/runtime-activation-deactivate-flag-wiring.test.ts +0 -167
  246. package/tests/commands/runtime-activation-dispatch-flag-wiring.test.ts +0 -262
  247. package/tests/commands/runtime-activation-governance-audit.test.ts +0 -95
  248. package/tests/commands/runtime-activation-list-flag-wiring.test.ts +0 -193
  249. package/tests/commands/runtime-activation-promote-flag-wiring.test.ts +0 -248
  250. package/tests/commands/runtime-activation-shadow-telemetry.test.ts +0 -130
  251. package/tests/commands/runtime-activation.test.ts +0 -2052
  252. package/tests/commands/runtime-artifact-repair-registration.test.ts +0 -74
  253. package/tests/commands/runtime-artifact-repair.test.ts +0 -404
  254. package/tests/commands/runtime-canary.test.ts +0 -430
  255. package/tests/commands/runtime-compatibility-scan.test.ts +0 -146
  256. package/tests/commands/runtime-diagnostics-export.test.ts +0 -175
  257. package/tests/commands/runtime-features.test.ts +0 -249
  258. package/tests/commands/runtime-health-snapshot.test.ts +0 -358
  259. package/tests/commands/runtime-init-empty-workspace.test.ts +0 -252
  260. package/tests/commands/runtime-init.test.ts +0 -420
  261. package/tests/commands/runtime-internalization-enqueue-successors.test.ts +0 -782
  262. package/tests/commands/runtime-internalization-integrity-repair.test.ts +0 -239
  263. package/tests/commands/runtime-internalization-integrity.test.ts +0 -140
  264. package/tests/commands/runtime-internalization-queue.test.ts +0 -380
  265. package/tests/commands/runtime-internalization-retry-owner-authority.test.ts +0 -431
  266. package/tests/commands/runtime-internalization-run-once-evaluator-parity.test.ts +0 -269
  267. package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -303
  268. package/tests/commands/runtime-internalization-run-once-real-config.test.ts +0 -237
  269. package/tests/commands/runtime-internalization-run-once-rollout-parity.test.ts +0 -706
  270. package/tests/commands/runtime-internalization-run-once.test.ts +0 -1791
  271. package/tests/commands/runtime-internalization-wake-once.test.ts +0 -171
  272. package/tests/commands/runtime-internalization.test.ts +0 -53
  273. package/tests/commands/runtime-pain-flood-simulation.test.ts +0 -418
  274. package/tests/commands/runtime-pruning.test.ts +0 -694
  275. package/tests/commands/runtime-recovery-failed-tasks.test.ts +0 -328
  276. package/tests/commands/runtime-recovery.test.ts +0 -120
  277. package/tests/commands/runtime-synthetic-baseline.test.ts +0 -249
  278. package/tests/commands/runtime-uat.test.ts +0 -397
  279. package/tests/commands/runtime.test.ts +0 -392
  280. package/tests/commands/samples-list.test.ts +0 -43
  281. package/tests/commands/samples-review.test.ts +0 -41
  282. package/tests/commands/task.test.ts +0 -153
  283. package/tests/commands/telemetry.test.ts +0 -190
  284. package/tests/commands/trace.test.ts +0 -316
  285. package/tests/config-reader.test.ts +0 -143
  286. package/tests/e2e/candidate-intake-e2e.test.ts +0 -338
  287. package/tests/e2e/cli-full-flow.test.ts +0 -233
  288. package/tests/e2e/cross-package-acceptance.test.ts +0 -635
  289. package/tests/helpers/pd-cli-path.ts +0 -5
  290. package/tests/pd-cli-smoke.test.ts +0 -13
  291. package/tests/resolve-workspace.test.ts +0 -196
  292. package/tests/services/config-doctor-signal-health.test.ts +0 -109
  293. package/tests/services/console-launcher.test.ts +0 -114
  294. package/tests/services/demo-rule-compiler.test.ts +0 -242
  295. package/tests/services/mainline-snapshot-assembler.test.ts +0 -572
  296. package/tests/services/pd-config-loader.test.ts +0 -505
  297. package/tests/services/proven-channel-baseline-runner.test.ts +0 -30
  298. package/tests/services/quality-scorecard/strong-model-gate.test.ts +0 -133
  299. package/tests/services/resolve-runtime-from-pd-config.test.ts +0 -485
  300. package/tests/services/rulehost-pipeline-e2e.test.ts +0 -538
  301. package/tests/services/rulehost-pipeline-runner.test.ts +0 -1242
  302. package/tests/services/workspace-tool-semantics.test.ts +0 -76
  303. package/tests/utils/path-security.test.ts +0 -180
  304. package/tsconfig.json +0 -26
  305. package/vitest.config.ts +0 -10
@@ -1,635 +0,0 @@
1
- /**
2
- * Cross-Package Acceptance Test — PRI-408 (P1/P2 fixes)
3
- *
4
- * UNSPLIPPABLE end-to-end test crossing pd-cli + principles-core packages:
5
- * pain → RuleHost pipeline → candidate → auto-enqueue (P1 #1)
6
- * → Owner edit approval (P1 #2) → Owner approve → dispatch with approval binding (P1 #3)
7
- * → activation observable → deactivate → behavior restored
8
- * → include-deactivated shows the record (P2 #5)
9
- *
10
- * Uses real SQLite stores, real pipeline runner, real approval queue, real
11
- * activation dispatcher. The only scripted part is the LLM stage outputs
12
- * (dreamer/philosopher/scribe/artificer/evaluator) — same pattern as the
13
- * existing pipeline runner tests.
14
- *
15
- * This test exists because the original story-a-acceptance.test.ts started
16
- * from a pre-built principle artifact and only checked that activation
17
- * records existed. It did NOT exercise:
18
- * - The pipeline → auto-enqueue chain (P1 #1)
19
- * - The edit entry point (P1 #2)
20
- * - The approval binding security boundary (P1 #3)
21
- * - The include-deactivated query (P2 #5)
22
- *
23
- * ERR checklist:
24
- * - ERR-001: All pipeline output treated as unknown, validated at runtime
25
- * - ERR-002: Every failure path carries reason + nextAction
26
- * - ERR-004/008: Lineage fields from same source, mismatch tested
27
- * - ERR-009: Required fields fail loud
28
- * - ERR-025: Production-path test, not demo helper
29
- */
30
-
31
- import { describe, it, expect, afterEach, vi } from 'vitest';
32
- import * as os from 'node:os';
33
- import * as path from 'node:path';
34
- import * as fs from 'node:fs';
35
- import {
36
- runRuleHostPipeline,
37
- createSandboxGateDeps,
38
- } from '../../src/services/rulehost-pipeline-runner.js';
39
- import { RuleHost } from '../../../openclaw-plugin/src/core/rule-host.js';
40
- import { OPENCLAW_TOOL_SEMANTIC_MAPPINGS } from '../../../openclaw-plugin/src/constants/tool-semantics.js';
41
- import { saveHostToolDeclaration } from '@principles/host-runtime';
42
- import type { CodeRuleCapability } from '../../src/services/rulehost-pipeline-runner.js';
43
- import type { PDRuntimeAdapter, RunHandle, RunStatus, PIArtifactStore, RuntimeCapabilities, RuntimeHealth, RuntimeArtifactRef, ContextItem, StructuredRunOutput, StartRunInput } from '@principles/core/runtime-v2';
44
- import {
45
- RuntimeStateManager,
46
- SqliteApprovalQueueStore,
47
- SqliteActivationStateStore,
48
- SqlitePIArtifactStore,
49
- ActivationDispatcher,
50
- ApprovalCompletionService,
51
- PromptWriter,
52
- DeferArchiveWriter,
53
- RuleHostWriter,
54
- createProductionGateDeps,
55
- makeIdempotencyKey,
56
- createPITaskDiagnosticJson,
57
- } from '@principles/core/runtime-v2';
58
- import type { PIArtifactSnapshot, RuleHostInput } from '@principles/core/runtime-v2';
59
- import { addPrincipleToLedger } from '@principles/core/principle-tree-ledger';
60
-
61
- // ── Scripted adapter (same pattern as rulehost-pipeline-runner.test.ts) ──────
62
-
63
- type StageFactory = (taskId: string, priorArtifactId?: string) => unknown;
64
- type EvaluatorFactory = (taskId: string, artificerArtifactId: string) => unknown;
65
-
66
- class ScriptedAdapter implements PDRuntimeAdapter {
67
- readonly startRunCalls: { taskId: string }[] = [];
68
- readonly startRunInputs = new Map<string, StartRunInput>();
69
- artifactStore: PIArtifactStore | null = null;
70
- constructor(private readonly factories: { dreamer: StageFactory; philosopher: StageFactory; scribe: StageFactory; artificer: StageFactory; evaluator: EvaluatorFactory }) {}
71
-
72
- private kindFor(taskId: string): 'dreamer' | 'philosopher' | 'scribe' | 'artificer' | 'evaluator' {
73
- if (taskId.startsWith('dreamer')) return 'dreamer';
74
- if (taskId.includes('philosopher')) return 'philosopher';
75
- if (taskId.includes('scribe')) return 'scribe';
76
- if (taskId.includes('artificer')) return 'artificer';
77
- return 'evaluator';
78
- }
79
-
80
- private async priorArtifactId(priorKind: string): Promise<string | undefined> {
81
- const calls = this.startRunCalls.filter((c) => this.kindFor(c.taskId) === priorKind);
82
- const last = calls[calls.length - 1];
83
- if (!last || !this.artifactStore) return undefined;
84
- const arts = await this.artifactStore.listBySourceTaskId(last.taskId);
85
- return arts[0]?.artifactId;
86
- }
87
-
88
- async startRun(input: StartRunInput): Promise<RunHandle> {
89
- this.startRunCalls.push({ taskId: input.taskRef.taskId });
90
- const runId = `run-${input.taskRef.taskId}`;
91
- this.startRunInputs.set(runId, input);
92
- return { runId, runtimeKind: 'test-double', startedAt: new Date().toISOString() };
93
- }
94
- async pollRun(_runId: string): Promise<RunStatus> { return { status: 'succeeded', runId: 'run-x' }; }
95
- async fetchOutput(runId: string): Promise<StructuredRunOutput | null> {
96
- const taskId = runId.replace(/^run-/, '');
97
- const kind = this.kindFor(taskId);
98
- let payload: unknown;
99
- if (kind === 'dreamer') payload = this.factories.dreamer(taskId);
100
- else if (kind === 'philosopher') payload = this.factories.philosopher(taskId, await this.priorArtifactId('dreamer'));
101
- else if (kind === 'scribe') payload = this.factories.scribe(taskId, await this.priorArtifactId('philosopher'));
102
- else if (kind === 'artificer') payload = this.factories.artificer(taskId, await this.priorArtifactId('scribe'));
103
- else {
104
- let artificerArtifactId = await this.priorArtifactId('artificer');
105
- if (!artificerArtifactId) {
106
- const inputPayload = this.startRunInputs.get(runId)?.inputPayload;
107
- if (typeof inputPayload === 'string') {
108
- const parsed: unknown = JSON.parse(inputPayload);
109
- if (parsed !== null && typeof parsed === 'object' && Object.hasOwn(parsed, 'sourceArtificerArtifactId')) {
110
- const candidate = Reflect.get(parsed, 'sourceArtificerArtifactId');
111
- if (typeof candidate === 'string') artificerArtifactId = candidate;
112
- }
113
- }
114
- }
115
- payload = this.factories.evaluator(taskId, requireLineage(artificerArtifactId, 'sourceArtificerArtifactId'));
116
- }
117
- return { runId, payload };
118
- }
119
- async cancelRun(_runId: string): Promise<void> { /* noop */ }
120
- async getCapabilities(): Promise<RuntimeCapabilities> {
121
- return {
122
- supportsStructuredJsonOutput: false,
123
- supportsToolUse: false,
124
- supportsWorkingDirectory: false,
125
- supportsModelSelection: false,
126
- supportsLongRunningSessions: false,
127
- supportsCancellation: false,
128
- supportsArtifactWriteBack: false,
129
- supportsConcurrentRuns: false,
130
- supportsStreaming: false,
131
- };
132
- }
133
- async healthCheck(): Promise<RuntimeHealth> {
134
- return { healthy: true, degraded: false, warnings: [], lastCheckedAt: new Date().toISOString() };
135
- }
136
- async fetchArtifacts(_runId: string): Promise<RuntimeArtifactRef[]> { return []; }
137
- async appendContext(_runId: string, _items: ContextItem[]): Promise<void> { /* noop */ }
138
- async refreshCapabilities(): Promise<RuntimeCapabilities> { return this.getCapabilities(); }
139
- kind(): 'test-double' { return 'test-double'; }
140
- }
141
-
142
- function requireLineage(id: string | undefined, field: string): string {
143
- if (!id) throw new Error(`missing required lineage field: ${field}`);
144
- return id;
145
- }
146
-
147
- // ── Scripted LLM stage outputs ───────────────────────────────────────────────
148
-
149
- function dreamerOut(taskId: string, painId: string): unknown {
150
- return {
151
- valid: true, taskId,
152
- candidates: [{ candidateIndex: 0, badDecision: 'Wrote to /etc/passwd', betterDecision: 'Block system path writes', rationale: 'OS damage risk', confidence: 0.85, riskLevel: 'low' as const, strategicPerspective: 'safety' }],
153
- sourcePainId: painId, contextRefs: [], generatedAt: new Date().toISOString(),
154
- };
155
- }
156
-
157
- function philosopherOut(taskId: string, priorId?: string): unknown {
158
- return {
159
- taskId, sourceDreamerArtifactId: requireLineage(priorId, 'sourceDreamerArtifactId'),
160
- thesis: 'System path writes must be blocked',
161
- principleCandidate: { title: 'Block system path writes', rationale: 'OS corruption risk', scope: 'write ops', confidence: 0.9 },
162
- risks: [], generatedAt: new Date().toISOString(),
163
- };
164
- }
165
-
166
- function scribeOut(taskId: string, priorId?: string): unknown {
167
- return {
168
- taskId, sourcePhilosopherArtifactId: requireLineage(priorId, 'sourcePhilosopherArtifactId'),
169
- principleDraft: { title: 'Block system path writes', statement: 'Writes to /etc, /boot, /sys must be blocked.', rationale: 'OS corruption risk', applicability: ['write_file'], antiPatterns: ['Hardcoded allow'], confidence: 0.9 },
170
- sourceTrace: { philosopherArtifactId: requireLineage(priorId, 'sourceTrace.philosopherArtifactId') },
171
- risks: [], generatedAt: new Date().toISOString(),
172
- };
173
- }
174
-
175
- // PRI-780: v2-only generation — the acceptance chain carries an Owner-labelled
176
- // pack whose cases match artificerV2's goldenTraceCases (parity is checked).
177
- const XPKG_RULE_CONTEXT = {
178
- version: 2 as const,
179
- history: { status: 'available' as const, truncated: false, calls: [] },
180
- facts: { priorReadOfTarget: 'unknown' as const, readCount: 0, writeCount: 0, uniqueWritePathCount: 0, sameActionBlockCount: null },
181
- };
182
- const XPKG_TEST_PACK = {
183
- sourceNegativeCase: { caseId: 'neg-1', kind: 'negative' as const, toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' as const, ruleContext: XPKG_RULE_CONTEXT },
184
- ownerDesiredOutcome: 'block system path writes',
185
- positiveCounterexamples: [{ caseId: 'pos-1', kind: 'positive' as const, toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow' as const, ruleContext: XPKG_RULE_CONTEXT }],
186
- evidenceRefs: ['pain://xpkg-1'],
187
- redactionNotes: [],
188
- };
189
-
190
- function artificerV2(taskId: string, priorId?: string): unknown {
191
- return {
192
- taskId, sourceScribeArtifactId: requireLineage(priorId, 'sourceScribeArtifactId'),
193
- requiresContextVersion: 2,
194
- evidenceRefs: ['pain://xpkg-1'],
195
- implementationPlan: { summary: 'Block /etc writes', targetSurface: 'rule-host', changes: ['matcher'], tests: ['unit'], rolloutNotes: ['shadow'], confidence: 0.85 },
196
- implementationCode: 'function evaluate(input, helpers) { const p = String(input?.action?.paramsSummary?.path ?? input?.action?.normalizedPath ?? ""); if (p.startsWith("/etc")) return { decision: "block", matched: true, reason: "system path" }; const ctx = input?.context; if (ctx && ctx.facts && ctx.facts.priorReadOfTarget === "no") { return { decision: "block", matched: true, reason: "no prior read" }; } return { decision: "allow", matched: false, reason: "ok" }; }',
197
- implementationSummary: 'Block system path writes',
198
- goldenTraceCases: [
199
- { caseId: 'pos-1', kind: 'positive', toolName: 'write_file', params: { path: '/project/f.txt' }, expectedDecision: 'allow', ruleContext: XPKG_RULE_CONTEXT },
200
- { caseId: 'neg-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block', ruleContext: XPKG_RULE_CONTEXT },
201
- ],
202
- affectedTools: ['write_file'],
203
- sourceTrace: { scribeArtifactId: requireLineage(priorId, 'sourceTrace.scribeArtifactId') },
204
- risks: [], generatedAt: new Date().toISOString(),
205
- };
206
- }
207
-
208
- function evaluatorApproved(taskId: string, artificerArtifactId: string): unknown {
209
- return {
210
- taskId, sourceArtificerArtifactId: artificerArtifactId,
211
- evaluation: { decision: 'approved', summary: 'approved', score: 0.9, strengths: [], concerns: [], requiredChanges: [] },
212
- sourceTrace: { artificerArtifactId },
213
- risks: [], generatedAt: new Date().toISOString(),
214
- codeReview: { intentConsistency: { aligned: true, explanation: 'ok' }, scopePrecision: { verdict: 'precise' as const, explanation: 'ok' }, traceCoverage: { sufficient: true, gaps: [], explanation: 'ok' } },
215
- adversarialCases: [{ caseId: 'adv-1', attackType: 'boundary' as const, toolName: 'write_file', params: { path: '/etc/shadow' }, expectedDecision: 'block' as const, rationale: 'system path' }],
216
- adversarialResult: { passed: true, failedCases: [] },
217
- };
218
- }
219
-
220
- // ── Test workspace helpers ───────────────────────────────────────────────────
221
-
222
- let tmpDir = '';
223
-
224
- function makeTmpDir(): string {
225
- const dir = path.join(os.tmpdir(), `pd-xpkg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
226
- fs.mkdirSync(dir, { recursive: true });
227
- // PRI-661: the pipeline's evaluator replay resolves the production gate
228
- // context from durable workspace provenance. Seed the declaration the real
229
- // OpenClaw host would persist on startup (same mapping constants), so the
230
- // generation-time replay and the activation gate see the same registry.
231
- saveHostToolDeclaration(dir, {
232
- version: 1,
233
- hostKind: 'openclaw',
234
- mappings: OPENCLAW_TOOL_SEMANTIC_MAPPINGS,
235
- declaredAt: new Date().toISOString(),
236
- });
237
- return dir;
238
- }
239
-
240
- async function seedDreamerWithId(sm: RuntimeStateManager, taskId: string, painId: string, candidateId?: string): Promise<void> {
241
- const baseMetadata = JSON.parse(createPITaskDiagnosticJson({
242
- dependencyTaskIds: [], channel: 'code_tool_hook', timeoutMs: 1000, inputArtifactRefs: [], outputArtifactRefs: [],
243
- })) as Record<string, unknown>;
244
- const diagnosticJson = JSON.stringify({ ...baseMetadata, sourcePainId: painId, ...(candidateId ? { candidateId } : {}) });
245
- await sm.createTask({ taskId, taskKind: 'dreamer', status: 'pending', attemptCount: 0, maxAttempts: 3, diagnosticJson });
246
- }
247
-
248
- function makeAdapter(): ScriptedAdapter {
249
- return new ScriptedAdapter({
250
- dreamer: (taskId) => dreamerOut(taskId, 'pain-xpkg-001'),
251
- philosopher: philosopherOut,
252
- scribe: scribeOut,
253
- artificer: artificerV2,
254
- evaluator: evaluatorApproved,
255
- });
256
- }
257
-
258
- // ── Cross-Package Acceptance Test ────────────────────────────────────────────
259
-
260
- describe('Cross-Package Acceptance Test (PRI-408 P1/P2 fixes) — unsplippable chain', () => {
261
- afterEach(() => {
262
- vi.restoreAllMocks();
263
- if (tmpDir) {
264
- try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
265
- tmpDir = '';
266
- }
267
- });
268
-
269
- it('pain → pipeline → auto-enqueue → edit → approve → activate → observe → deactivate → restore', async () => {
270
- // ── Step 1: Set up workspace and seed pain signal ──────────────────────
271
- tmpDir = makeTmpDir();
272
- const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
273
- await sm.initialize();
274
- // Owner Decision Experience v1 Phase A: the governance publication boundary
275
- // requires a verified chain identity (candidateId → unique ledger entry),
276
- // mirroring what the intake bridge creates in production workspaces.
277
- await seedDreamerWithId(sm, 'dreamer-xpkg-001', 'pain-xpkg-001', 'cand-xpkg-001');
278
- await sm.close();
279
- addPrincipleToLedger(path.join(tmpDir, '.state'), {
280
- id: 'ledger-xpkg-001', version: 1, text: 'cross-package ledger principle', triggerPattern: '', action: '',
281
- status: 'candidate', evaluability: 'weak_heuristic', priority: 'P1', scope: 'general',
282
- valueScore: 0, adherenceRate: 0, painPreventedCount: 0, derivedFromPainIds: ['cand-xpkg-001'],
283
- ruleIds: [], conflictsWithPrincipleIds: [], createdAt: '2026-09-15T00:00:00.000Z', updatedAt: '2026-09-15T00:00:00.000Z',
284
- });
285
-
286
- // ── Step 2: Run RuleHost pipeline → candidate + auto-enqueue (P1 #1) ───
287
- const adapter = makeAdapter();
288
- const capability: CodeRuleCapability = { enabled: true, artificerAdapter: adapter };
289
- const pipelineResult = await runRuleHostPipeline({
290
- workspaceDir: tmpDir,
291
- painId: 'pain-xpkg-001',
292
- runtimeAdapter: adapter,
293
- channel: 'code_tool_hook',
294
- pollIntervalMs: 5,
295
- timeoutMs: 5000,
296
- codeRuleCapability: capability,
297
- behaviorExamplePack: XPKG_TEST_PACK,
298
- onStoreReady: (store) => { adapter.artifactStore = store; },
299
- });
300
-
301
- // Verify pipeline produced a candidate
302
- expect(pipelineResult.decision, JSON.stringify(pipelineResult)).toBe('candidate_ready_for_owner_review');
303
- expect(pipelineResult.ruleArtifactId).not.toBeNull();
304
- const originalArtifactId = pipelineResult.ruleArtifactId!;
305
-
306
- // P1 #1 fix: candidate must be auto-enqueued into the ApprovalQueue
307
- expect(pipelineResult.approvalId).not.toBeNull();
308
- const approvalId = pipelineResult.approvalId!;
309
-
310
- // ── Step 3: Verify the approval is in the queue with pending status ────
311
- const sm2 = new RuntimeStateManager({ workspaceDir: tmpDir });
312
- await sm2.initialize();
313
- const approvalStore = new SqliteApprovalQueueStore(sm2.connection);
314
- const stateStore = new SqliteActivationStateStore(sm2.connection);
315
- const artifactStore = new SqlitePIArtifactStore(sm2.connection);
316
-
317
- const approvalRecord = await approvalStore.getById(approvalId);
318
- expect(approvalRecord).not.toBeNull();
319
- expect(approvalRecord!.status).toBe('pending');
320
- expect(approvalRecord!.artifactId).toBe(originalArtifactId);
321
- expect(approvalRecord!.channel).toBe('code_tool_hook');
322
-
323
- // ── Step 4: Owner edits the approval to swap to a revised artifact (P1 #2)
324
- // Create a revised artifact (simulating owner revision of the rule code)
325
- const revisedArtifactId = 'art-rule-revised-001';
326
- const originalArtifact = await artifactStore.getArtifactById(originalArtifactId);
327
- expect(originalArtifact).not.toBeNull();
328
-
329
- // Store the revised artifact
330
- const revisedContentJson = JSON.stringify({
331
- ...JSON.parse(originalArtifact!.contentJson),
332
- implementationCode: 'function evaluate(input, helpers) { const p = String(input?.action?.paramsSummary?.path ?? input?.action?.normalizedPath ?? ""); return (p.startsWith("/etc") || p.startsWith("/boot")) ? { decision: "block", matched: true, reason: "system path blocked (revised)" } : { decision: "allow", matched: false, reason: "ok" }; }',
333
- revisionNote: 'Owner expanded coverage to include /boot path',
334
- });
335
- await artifactStore.upsertArtifact({
336
- artifactId: revisedArtifactId,
337
- artifactKind: 'rule',
338
- sourceTaskId: originalArtifact!.sourceTaskId,
339
- sourcePrincipleId: originalArtifact!.sourcePrincipleId,
340
- sourceRuleId: originalArtifact!.sourceRuleId,
341
- lineageArtifactIds: [originalArtifactId],
342
- validationStatus: 'validated',
343
- contentJson: revisedContentJson,
344
- createdAt: new Date().toISOString(),
345
- updatedAt: new Date().toISOString(),
346
- });
347
-
348
- // Edit the approval to swap to the revised artifact
349
- const editResult = await approvalStore.edit({
350
- approvalId,
351
- editedBy: 'owner-001',
352
- newArtifactId: revisedArtifactId,
353
- editReason: 'Expanded /boot path coverage after review',
354
- now: new Date().toISOString(),
355
- });
356
- expect(editResult.ok).toBe(true);
357
- if (editResult.ok) {
358
- expect(editResult.record.artifactId).toBe(revisedArtifactId);
359
- expect(editResult.record.previousArtifactId).toBe(originalArtifactId);
360
- expect(editResult.record.editedBy).toBe('owner-001');
361
- }
362
-
363
- // ── Step 5: Owner approves the edited approval ─────────────────────────
364
- const approveResult = await approvalStore.approve(approvalId, 'owner-001', 'Approved after edit');
365
- expect(approveResult.ok).toBe(true);
366
-
367
- // ── Step 6: Dispatch activation with approval binding (P1 #3) ──────────
368
- const revisedArtifact = await artifactStore.getArtifactById(revisedArtifactId);
369
- expect(revisedArtifact).not.toBeNull();
370
- const revisedSnapshot: PIArtifactSnapshot = {
371
- artifactId: revisedArtifact!.artifactId,
372
- artifactKind: revisedArtifact!.artifactKind,
373
- sourceTaskId: revisedArtifact!.sourceTaskId,
374
- sourcePrincipleId: revisedArtifact!.sourcePrincipleId,
375
- sourceRuleId: revisedArtifact!.sourceRuleId,
376
- lineageArtifactIds: revisedArtifact!.lineageArtifactIds,
377
- validationStatus: revisedArtifact!.validationStatus,
378
- contentJson: revisedArtifact!.contentJson,
379
- createdAt: revisedArtifact!.createdAt,
380
- updatedAt: revisedArtifact!.updatedAt,
381
- };
382
-
383
- const artifactReadModel = {
384
- getArtifactById: async (id: string): Promise<PIArtifactSnapshot | null> => {
385
- if (id === revisedArtifactId) return revisedSnapshot;
386
- const rec = await artifactStore.getArtifactById(id);
387
- return rec ? {
388
- artifactId: rec.artifactId,
389
- artifactKind: rec.artifactKind,
390
- sourceTaskId: rec.sourceTaskId,
391
- sourcePrincipleId: rec.sourcePrincipleId,
392
- sourceRuleId: rec.sourceRuleId,
393
- lineageArtifactIds: rec.lineageArtifactIds,
394
- validationStatus: rec.validationStatus,
395
- contentJson: rec.contentJson,
396
- createdAt: rec.createdAt,
397
- updatedAt: rec.updatedAt,
398
- } : null;
399
- },
400
- };
401
-
402
- const dispatcher = new ActivationDispatcher(
403
- artifactReadModel,
404
- stateStore,
405
- {
406
- writers: [
407
- new PromptWriter(),
408
- // PRI-780: rulecode_context_v2 defaults ON — production callers
409
- // inject the real flag probe; mirror that here for the v2 artifact.
410
- new RuleHostWriter({ gateDeps: createProductionGateDeps(), featureFlagProbe: (flagId) => flagId === 'rulecode_context_v2' }),
411
- new DeferArchiveWriter(),
412
- ],
413
- approvalQueueStore: approvalStore,
414
- },
415
- );
416
-
417
- const completionService = new ApprovalCompletionService(
418
- approvalStore,
419
- dispatcher,
420
- stateStore,
421
- );
422
-
423
- const completionResult = await completionService.completeApproval({
424
- approvalId,
425
- actor: { kind: 'human', userId: 'owner-001' },
426
- now: new Date().toISOString(),
427
- });
428
-
429
- expect(completionResult.ok).toBe(true);
430
- if (completionResult.ok) {
431
- expect(completionResult.decision.decision).toBe('activated');
432
- expect(completionResult.activationId).toBeDefined();
433
- }
434
-
435
- // ── Step 7: Verify activation is observable ────────────────────────────
436
- const idempotencyKey = makeIdempotencyKey(revisedArtifactId, 'code_tool_hook');
437
- const activationRecord = await stateStore.getActivationStatus(idempotencyKey);
438
- expect(activationRecord).not.toBeNull();
439
- expect(activationRecord!.artifactId).toBe(revisedArtifactId);
440
- expect(activationRecord!.channel).toBe('code_tool_hook');
441
- expect(activationRecord!.deactivatedAt).toBeNull();
442
- // PRI-489: Owner approval creates a SHADOW activation first (not live).
443
- // Shadow activations are observation-only — they record would-block into
444
- // shadowDecisions but never actually block the tool call. The only
445
- // shadow -> live transition requires the Owner decision service.
446
- expect(activationRecord!.action).toBe('code_tool_hook_shadow_activate');
447
-
448
- // Verify via listCodeToolHookActivations (P2 #5: default excludes deactivated)
449
- const activeActivations = await stateStore.listCodeToolHookActivations();
450
- const ourActivation = activeActivations.find(a => a.artifactId === revisedArtifactId);
451
- expect(ourActivation).toBeDefined();
452
- expect(ourActivation!.deactivatedAt).toBeNull();
453
- expect(ourActivation!.action).toBe('code_tool_hook_shadow_activate');
454
-
455
- // ── Step 7a: Verify shadow activation does NOT block (PRI-489) ────────
456
- // Shadow mode is observation-only. The RuleHost loads the activation but
457
- // records shadowDecisions instead of blocking. evaluate() must return
458
- // undefined (no block) for shadow activations, even for /etc/passwd.
459
- // PRI-780: production RuleHostInput always carries context (flag on) —
460
- // the v2 activation only loads when context.version === 2.
461
- const makeRuleHostInput = (targetPath: string): RuleHostInput => ({
462
- action: { toolName: 'write_file', normalizedPath: targetPath, paramsSummary: { path: targetPath } },
463
- workspace: { isRiskPath: targetPath.startsWith('/etc') },
464
- session: { currentGfi: 0 },
465
- evolution: { epTier: 0 },
466
- derived: { estimatedLineChanges: 1, bashRisk: 'safe' },
467
- context: XPKG_RULE_CONTEXT,
468
- });
469
- const shadowRuleHost = new RuleHost(
470
- path.join(tmpDir, '.state'),
471
- { warn: () => {} },
472
- { workspaceDir: tmpDir },
473
- );
474
- try {
475
- const report = shadowRuleHost.evaluateDetailed(makeRuleHostInput('/etc/passwd'));
476
- expect(report.liveDecision).toBeUndefined();
477
- expect(report.shadowDecisions).toContainEqual(
478
- expect.objectContaining({ activationId: activationRecord!.activationId }),
479
- );
480
- } finally {
481
- shadowRuleHost.dispose();
482
- }
483
-
484
- // ── Step 7b: Promote shadow → live (PRI-489) ──────────────────────────
485
- // This lower-level store call verifies the persistence transition only;
486
- // production callers must enter through RuleCodeOwnerDecisionService.
487
- const activationId = activationRecord!.activationId;
488
- const promoteResult = await stateStore.promoteActivation(activationId, new Date().toISOString());
489
- expect(promoteResult).toBe(true);
490
-
491
- // Verify the action is now live
492
- const liveActivationRecord = await stateStore.getActivationStatus(idempotencyKey);
493
- expect(liveActivationRecord).not.toBeNull();
494
- expect(liveActivationRecord!.action).toBe('code_tool_hook_live_activate');
495
- expect(liveActivationRecord!.promotedAt).not.toBeNull();
496
-
497
- // ── Step 7c: Verify the rule actually blocks in live mode (P1 #4) ────
498
- // The production RuleHost gate loads all active rules, compiles their code,
499
- // and calls evaluate() on each tool call. This step simulates that:
500
- // 1. Load active rules from the activation state store
501
- // 2. Compile the rule code in the production vm sandbox
502
- // 3. Call evaluate() with system-path and normal-path inputs
503
- // 4. Verify the rule blocks system paths and allows normal paths
504
- const ruleHost = new RuleHost(
505
- path.join(tmpDir, '.state'),
506
- { warn: () => {} },
507
- { workspaceDir: tmpDir },
508
- );
509
- const blockResult = ruleHost.evaluate(makeRuleHostInput('/etc/passwd'));
510
- expect(blockResult).toBeDefined();
511
- expect(blockResult.decision).toBe('block');
512
- expect(blockResult.matched).toBe(true);
513
- expect(ruleHost.evaluate(makeRuleHostInput('/project/src/main.ts'))).toBeUndefined();
514
-
515
- // ── Step 8: Owner deactivates (rollback) ───────────────────────────────
516
- const deactivateResult = await stateStore.deactivateActivation(activationId, new Date().toISOString());
517
- expect(deactivateResult).toBe(true);
518
-
519
- // ── Step 9: Verify behavior is restored (P1 #4: gate-level verification) ─
520
- // After deactivation, the RuleHost gate loads active rules — our rule is
521
- // no longer in the active list, so the gate would NOT evaluate it. The
522
- // default behavior (no rules matching) is 'allow'. This proves behavior
523
- // reverts after rollback, not just that the DB record changed.
524
- const activeAfterDeactivate = await stateStore.listCodeToolHookActivations();
525
- const stillActive = activeAfterDeactivate.find(a => a.artifactId === revisedArtifactId);
526
- expect(stillActive).toBeUndefined();
527
-
528
- // Simulate the gate: no active rules → default decision is 'allow' for all inputs
529
- // (The gate iterates active rules; with zero active rules, no rule can block.)
530
- const gateActiveRules = activeAfterDeactivate.filter(a => a.deactivatedAt === null);
531
- expect(gateActiveRules).toHaveLength(0);
532
- const ruleHostAfterRollback = new RuleHost(
533
- path.join(tmpDir, '.state'),
534
- { warn: () => {} },
535
- { workspaceDir: tmpDir },
536
- );
537
- expect(ruleHostAfterRollback.evaluate(makeRuleHostInput('/etc/passwd'))).toBeUndefined();
538
-
539
- // P2 #5 fix: with includeDeactivated=true, the record IS returned
540
- const allAfterDeactivate = await stateStore.listCodeToolHookActivations(true);
541
- const deactivatedRecord = allAfterDeactivate.find(a => a.artifactId === revisedArtifactId);
542
- expect(deactivatedRecord).toBeDefined();
543
- expect(deactivatedRecord!.deactivatedAt).not.toBeNull();
544
-
545
- // ── Step 10: Verify getActivationStatus reflects deactivation (Bug-Q) ─
546
- // Bug-Q fix: getActivationStatus now filters out deactivated records
547
- // (WHERE deactivated_at IS NULL), returning null when no active activation
548
- // exists. This allows re-activation after deactivation. The deactivated
549
- // record itself is already verified via listCodeToolHookActivations(true)
550
- // above (L459-462, Step 9). Here we verify the new null semantics.
551
- const activationAfterDeactivate = await stateStore.getActivationStatus(idempotencyKey);
552
- expect(activationAfterDeactivate).toBeNull();
553
-
554
- await sm2.close();
555
- }, 60_000);
556
-
557
- it('P1 #3: dispatch with approved but no approvalId is refused', async () => {
558
- // This test verifies the approval binding security boundary (P1 #3).
559
- // A caller cannot bypass owner approval by passing rolloutDecision='approved'
560
- // without a verified approvalId.
561
-
562
- tmpDir = makeTmpDir();
563
- const sm = new RuntimeStateManager({ workspaceDir: tmpDir });
564
- await sm.initialize();
565
-
566
- const approvalStore = new SqliteApprovalQueueStore(sm.connection);
567
- const stateStore = new SqliteActivationStateStore(sm.connection);
568
- const artifactStore = new SqlitePIArtifactStore(sm.connection);
569
-
570
- // Create a test artifact
571
- const artifactId = 'art-test-001';
572
- await artifactStore.upsertArtifact({
573
- artifactId,
574
- artifactKind: 'principle',
575
- sourceTaskId: 'task-test-001',
576
- sourcePrincipleId: 'P_test_001',
577
- sourceRuleId: undefined,
578
- lineageArtifactIds: [],
579
- validationStatus: 'validated',
580
- contentJson: JSON.stringify({ text: 'Test principle', language: 'en' }),
581
- createdAt: new Date().toISOString(),
582
- updatedAt: new Date().toISOString(),
583
- });
584
-
585
- const artifact = await artifactStore.getArtifactById(artifactId);
586
- const artifactSnapshot: PIArtifactSnapshot = {
587
- artifactId: artifact!.artifactId,
588
- artifactKind: artifact!.artifactKind,
589
- sourceTaskId: artifact!.sourceTaskId,
590
- sourcePrincipleId: artifact!.sourcePrincipleId,
591
- sourceRuleId: artifact!.sourceRuleId,
592
- lineageArtifactIds: artifact!.lineageArtifactIds,
593
- validationStatus: artifact!.validationStatus,
594
- contentJson: artifact!.contentJson,
595
- createdAt: artifact!.createdAt,
596
- updatedAt: artifact!.updatedAt,
597
- };
598
-
599
- const artifactReadModel = {
600
- getArtifactById: async (id: string): Promise<PIArtifactSnapshot | null> => {
601
- return id === artifactId ? artifactSnapshot : null;
602
- },
603
- };
604
-
605
- const dispatcher = new ActivationDispatcher(
606
- artifactReadModel,
607
- stateStore,
608
- {
609
- writers: [new PromptWriter()],
610
- approvalQueueStore: approvalStore,
611
- },
612
- );
613
-
614
- // Attempt to dispatch with 'approved' but no approvalId — must be refused
615
- const result = await dispatcher.dispatch({
616
- artifactId,
617
- channel: 'prompt',
618
- rolloutDecision: 'approved',
619
- actor: { kind: 'system', source: 'attacker' },
620
- now: new Date().toISOString(),
621
- confirm: true,
622
- // approvalId intentionally omitted — this is the attack vector
623
- });
624
-
625
- expect(result.decision).toBe('refused');
626
- expect(result.reason).toContain('approval_id');
627
-
628
- // Verify no activation was created
629
- const idempotencyKey = makeIdempotencyKey(artifactId, 'prompt');
630
- const activationRecord = await stateStore.getActivationStatus(idempotencyKey);
631
- expect(activationRecord).toBeNull();
632
-
633
- await sm.close();
634
- }, 30_000);
635
- });
@@ -1,5 +0,0 @@
1
- import { join } from 'node:path';
2
-
3
- export function getBuiltPdCliPath(): string {
4
- return join(process.cwd(), 'dist', 'index.js');
5
- }
@@ -1,13 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { execFileSync } from 'node:child_process';
3
- import { getBuiltPdCliPath } from './helpers/pd-cli-path.js';
4
-
5
- describe('PD CLI Smoke Test', () => {
6
- it('should run successfully and load all dependencies including better-sqlite3 without errors', () => {
7
- const output = execFileSync('node', [getBuiltPdCliPath(), '--help'], {
8
- encoding: 'utf8',
9
- cwd: process.cwd(),
10
- });
11
- expect(output).toContain('Usage: pd');
12
- });
13
- });