create-principles-disciple 1.97.1 → 1.99.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 (189) hide show
  1. package/console/dist/server/models/ActivationsConsoleModel.js +29 -3
  2. package/console/dist/server/models/ApprovalsConsoleModel.d.ts +13 -0
  3. package/console/dist/server/models/ApprovalsConsoleModel.js +95 -3
  4. package/console/dist/server/models/FeedbackReportConsoleModel.js +1 -1
  5. package/console/dist/ui/api.js +1 -1
  6. package/console/dist/ui/pages/pain/PainEvidenceValidators.js +1 -1
  7. package/console/dist/web/assets/app.js +10 -6
  8. package/console/package.json +1 -1
  9. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +8 -7
  10. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  11. package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js +45 -0
  12. package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
  13. package/core/dist/runtime-v2/__tests__/dreamer-runner.test.js +15 -2
  14. package/core/dist/runtime-v2/__tests__/dreamer-runner.test.js.map +1 -1
  15. package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js +44 -0
  16. package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice-v2.test.js.map +1 -1
  17. package/core/dist/runtime-v2/activation/__tests__/activation-re-dispatch.test.d.ts +2 -0
  18. package/core/dist/runtime-v2/activation/__tests__/activation-re-dispatch.test.d.ts.map +1 -0
  19. package/core/dist/runtime-v2/activation/__tests__/activation-re-dispatch.test.js +149 -0
  20. package/core/dist/runtime-v2/activation/__tests__/activation-re-dispatch.test.js.map +1 -0
  21. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-state-store.test.js +72 -2
  22. package/core/dist/runtime-v2/activation/__tests__/sqlite-activation-state-store.test.js.map +1 -1
  23. package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
  24. package/core/dist/runtime-v2/activation/activation-dispatcher.js +5 -0
  25. package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
  26. package/core/dist/runtime-v2/activation/activation-types.d.ts +1 -0
  27. package/core/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
  28. package/core/dist/runtime-v2/activation/activation-types.js.map +1 -1
  29. package/core/dist/runtime-v2/activation/memory-activation-state-store.d.ts.map +1 -1
  30. package/core/dist/runtime-v2/activation/memory-activation-state-store.js +6 -1
  31. package/core/dist/runtime-v2/activation/memory-activation-state-store.js.map +1 -1
  32. package/core/dist/runtime-v2/activation/memory-approval-store.js +1 -1
  33. package/core/dist/runtime-v2/activation/memory-approval-store.js.map +1 -1
  34. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.d.ts +1 -0
  35. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.d.ts.map +1 -1
  36. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js +64 -10
  37. package/core/dist/runtime-v2/activation/sqlite-activation-state-store.js.map +1 -1
  38. package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts.map +1 -1
  39. package/core/dist/runtime-v2/activation/sqlite-approval-store.js +9 -1
  40. package/core/dist/runtime-v2/activation/sqlite-approval-store.js.map +1 -1
  41. package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js +18 -9
  42. package/core/dist/runtime-v2/activation/writers/__tests__/rule-host-writer.test.js.map +1 -1
  43. package/core/dist/runtime-v2/activation/writers/rule-host-writer.d.ts.map +1 -1
  44. package/core/dist/runtime-v2/activation/writers/rule-host-writer.js +8 -1
  45. package/core/dist/runtime-v2/activation/writers/rule-host-writer.js.map +1 -1
  46. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.d.ts +2 -0
  47. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.d.ts.map +1 -0
  48. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js +134 -0
  49. package/core/dist/runtime-v2/adapter/__tests__/principle-tree-ledger-adapter.test.js.map +1 -0
  50. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js +1 -1
  51. package/core/dist/runtime-v2/adapter/openclaw-cli-runtime-adapter.js.map +1 -1
  52. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +2 -2
  53. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  54. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +15 -0
  55. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
  56. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +23 -1
  57. package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
  58. package/core/dist/runtime-v2/control-plane-triage.js +2 -2
  59. package/core/dist/runtime-v2/control-plane-triage.js.map +1 -1
  60. package/core/dist/runtime-v2/error-categories.js +1 -1
  61. package/core/dist/runtime-v2/error-categories.js.map +1 -1
  62. package/core/dist/runtime-v2/golden-trace.js +1 -1
  63. package/core/dist/runtime-v2/golden-trace.js.map +1 -1
  64. package/core/dist/runtime-v2/index.d.ts +1 -1
  65. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  66. package/core/dist/runtime-v2/index.js +1 -1
  67. package/core/dist/runtime-v2/index.js.map +1 -1
  68. package/core/dist/runtime-v2/intent/__tests__/intent-browser.test.d.ts +2 -0
  69. package/core/dist/runtime-v2/intent/__tests__/intent-browser.test.d.ts.map +1 -0
  70. package/core/dist/runtime-v2/intent/__tests__/intent-browser.test.js +93 -0
  71. package/core/dist/runtime-v2/intent/__tests__/intent-browser.test.js.map +1 -0
  72. package/core/dist/runtime-v2/intent/__tests__/intent-doc-version-edge-cases.test.d.ts +2 -0
  73. package/core/dist/runtime-v2/intent/__tests__/intent-doc-version-edge-cases.test.d.ts.map +1 -0
  74. package/core/dist/runtime-v2/intent/__tests__/intent-doc-version-edge-cases.test.js +182 -0
  75. package/core/dist/runtime-v2/intent/__tests__/intent-doc-version-edge-cases.test.js.map +1 -0
  76. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js +20 -0
  77. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder-v2.test.js.map +1 -1
  78. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js +69 -13
  79. package/core/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js.map +1 -1
  80. package/core/dist/runtime-v2/internalization/__tests__/artificer-rule-output.test.js +13 -6
  81. package/core/dist/runtime-v2/internalization/__tests__/artificer-rule-output.test.js.map +1 -1
  82. package/core/dist/runtime-v2/internalization/__tests__/split-diagnostician-runner-retry.test.js +113 -0
  83. package/core/dist/runtime-v2/internalization/__tests__/split-diagnostician-runner-retry.test.js.map +1 -1
  84. package/core/dist/runtime-v2/internalization/artificer-output.d.ts.map +1 -1
  85. package/core/dist/runtime-v2/internalization/artificer-output.js +8 -2
  86. package/core/dist/runtime-v2/internalization/artificer-output.js.map +1 -1
  87. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts +6 -1
  88. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
  89. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +33 -7
  90. package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
  91. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts +5 -0
  92. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
  93. package/core/dist/runtime-v2/internalization/artificer-runner.js +68 -3
  94. package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
  95. package/core/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
  96. package/core/dist/runtime-v2/internalization/dreamer-runner.js +4 -0
  97. package/core/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
  98. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  99. package/core/dist/runtime-v2/internalization/evaluator-runner.js +3 -1
  100. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  101. package/core/dist/runtime-v2/internalization/intake-to-internalization-bridge.d.ts.map +1 -1
  102. package/core/dist/runtime-v2/internalization/intake-to-internalization-bridge.js +1 -0
  103. package/core/dist/runtime-v2/internalization/intake-to-internalization-bridge.js.map +1 -1
  104. package/core/dist/runtime-v2/internalization/split-diagnostician-runner.d.ts.map +1 -1
  105. package/core/dist/runtime-v2/internalization/split-diagnostician-runner.js +27 -0
  106. package/core/dist/runtime-v2/internalization/split-diagnostician-runner.js.map +1 -1
  107. package/core/dist/runtime-v2/internalization-chain-integrity-read-model.d.ts.map +1 -1
  108. package/core/dist/runtime-v2/internalization-chain-integrity-read-model.js +4 -1
  109. package/core/dist/runtime-v2/internalization-chain-integrity-read-model.js.map +1 -1
  110. package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  111. package/core/dist/runtime-v2/pain-signal-bridge.js +1 -0
  112. package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  113. package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  114. package/core/dist/runtime-v2/pain-signal-runtime-factory.js +1 -0
  115. package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  116. package/core/dist/runtime-v2/proven-channel-baseline.js +1 -1
  117. package/core/dist/runtime-v2/proven-channel-baseline.js.map +1 -1
  118. package/core/dist/runtime-v2/store/commit/diagnostician-committer.d.ts.map +1 -1
  119. package/core/dist/runtime-v2/store/commit/diagnostician-committer.js +1 -0
  120. package/core/dist/runtime-v2/store/commit/diagnostician-committer.js.map +1 -1
  121. package/core/dist/runtime-v2/store/context/resilient-context-assembler.js +1 -1
  122. package/core/dist/runtime-v2/store/context/resilient-context-assembler.js.map +1 -1
  123. package/core/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  124. package/core/dist/runtime-v2/store/sqlite-connection.js +4 -0
  125. package/core/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  126. package/core/dist/runtime-v2/types/event-types.d.ts +4 -0
  127. package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
  128. package/core/dist/runtime-v2/types/event-types.js +5 -0
  129. package/core/dist/runtime-v2/types/event-types.js.map +1 -1
  130. package/dist/installer.js +1 -1
  131. package/dist/installer.js.map +1 -1
  132. package/dist/mvp-config.js +1 -1
  133. package/dist/mvp-config.js.map +1 -1
  134. package/dist/uninstaller.js +2 -2
  135. package/dist/uninstaller.js.map +1 -1
  136. package/package.json +2 -2
  137. package/pd-cli/dist/commands/__tests__/run-rulehost-flag-wiring.test.js +9 -0
  138. package/pd-cli/dist/commands/__tests__/run-rulehost-flag-wiring.test.js.map +1 -1
  139. package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
  140. package/pd-cli/dist/commands/candidate.js +3 -0
  141. package/pd-cli/dist/commands/candidate.js.map +1 -1
  142. package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
  143. package/pd-cli/dist/commands/diagnose.js +72 -4
  144. package/pd-cli/dist/commands/diagnose.js.map +1 -1
  145. package/pd-cli/dist/commands/pain-record.d.ts.map +1 -1
  146. package/pd-cli/dist/commands/pain-record.js +21 -0
  147. package/pd-cli/dist/commands/pain-record.js.map +1 -1
  148. package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
  149. package/pd-cli/dist/commands/pain-retry.js +1 -0
  150. package/pd-cli/dist/commands/pain-retry.js.map +1 -1
  151. package/pd-cli/dist/commands/runtime-activation.d.ts +26 -0
  152. package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
  153. package/pd-cli/dist/commands/runtime-activation.js +319 -3
  154. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  155. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
  156. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js +3 -1
  157. package/pd-cli/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
  158. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts +1 -0
  159. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  160. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js +149 -6
  161. package/pd-cli/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  162. package/pd-cli/dist/commands/runtime-uat.d.ts.map +1 -1
  163. package/pd-cli/dist/commands/runtime-uat.js +4 -3
  164. package/pd-cli/dist/commands/runtime-uat.js.map +1 -1
  165. package/pd-cli/dist/config-reader.js +1 -1
  166. package/pd-cli/dist/config-reader.js.map +1 -1
  167. package/pd-cli/dist/index.js +22 -1
  168. package/pd-cli/dist/index.js.map +1 -1
  169. package/pd-cli/dist/services/pd-config-loader.js +2 -2
  170. package/pd-cli/dist/services/pd-config-loader.js.map +1 -1
  171. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts +5 -1
  172. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  173. package/pd-cli/dist/services/rulehost-pipeline-runner.js +4 -1
  174. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  175. package/plugin/dist/core/behavior-example-pack-assembler.d.ts +6 -11
  176. package/plugin/dist/core/behavior-example-pack-assembler.js +44 -84
  177. package/plugin/dist/core/pd-config-loader.js +1 -1
  178. package/plugin/dist/core/rule-host.d.ts +22 -1
  179. package/plugin/dist/core/rule-host.js +107 -18
  180. package/plugin/dist/core/trajectory-types.d.ts +4 -0
  181. package/plugin/dist/core/trajectory.d.ts +6 -2
  182. package/plugin/dist/core/trajectory.js +41 -0
  183. package/plugin/dist/core/workflow-funnel-loader.js +2 -2
  184. package/plugin/dist/core/workspace-context.d.ts +4 -0
  185. package/plugin/dist/core/workspace-context.js +13 -0
  186. package/plugin/dist/hooks/gate.js +42 -50
  187. package/plugin/dist/rulehost-evidence.d.ts +4 -0
  188. package/plugin/dist/rulehost-evidence.js +3 -0
  189. package/plugin/package.json +5 -1
@@ -48,11 +48,25 @@ export class RuleHost {
48
48
  logger;
49
49
  workspaceDir;
50
50
  implementationSources = new Map();
51
+ activationFingerprint = null;
52
+ cachedImplementations = [];
53
+ sqliteConnection = null;
51
54
  constructor(stateDir, logger = console, options) {
52
55
  this.stateDir = stateDir;
53
56
  this.logger = logger;
54
57
  this.workspaceDir = options?.workspaceDir ?? null;
55
58
  }
59
+ /**
60
+ * Update the logger sink on a cached RuleHost instance.
61
+ *
62
+ * WorkspaceContext caches the RuleHost singleton, but each gate call may
63
+ * pass a request-level logger. Without this update, warn/unhealthy logs
64
+ * would forever go to the first logger sink, making the new path hard to
65
+ * debug.
66
+ */
67
+ updateLogger(logger) {
68
+ this.logger = logger;
69
+ }
56
70
  /**
57
71
  * Evaluate the input against all active code implementations.
58
72
  *
@@ -63,9 +77,36 @@ export class RuleHost {
63
77
  * - { decision: 'requireApproval', ... } when any implementation returns requireApproval
64
78
  */
65
79
  evaluate(input) {
80
+ return this.evaluateDetailed(input).liveDecision;
81
+ }
82
+ dispose() {
83
+ this.sqliteConnection?.close();
84
+ this.sqliteConnection = null;
85
+ this.cachedImplementations = [];
86
+ this.activationFingerprint = null;
87
+ this.implementationSources.clear();
88
+ }
89
+ evaluateDetailed(input) {
66
90
  try {
67
- const activeImpls = this._loadActiveCodeImplementations();
68
- return mergeDecisions(activeImpls, input, {
91
+ const activeImpls = this._loadActiveCodeImplementations(input.context?.version === 2);
92
+ const liveImpls = activeImpls.filter((impl) => impl.activationMode === 'live');
93
+ const shadowImpls = activeImpls.filter((impl) => impl.activationMode === 'shadow');
94
+ const shadowDecisions = [];
95
+ for (const impl of shadowImpls) {
96
+ try {
97
+ const result = impl.evaluate(input);
98
+ shadowDecisions.push({ ...result, activationId: impl.activationId });
99
+ }
100
+ catch (evalError) {
101
+ const reason = `evaluation failed: ${String(evalError)}`;
102
+ this.logger.warn?.(`[RuleHost] Shadow implementation ${impl.implId} ${reason}`);
103
+ const source = this.implementationSources.get(impl.implId);
104
+ if (source) {
105
+ this._recordUnhealthy(source.activationId, source.artifactId, source.ruleId, reason, 'Fix the RuleCode runtime error or return shape, then re-activate the rule');
106
+ }
107
+ }
108
+ }
109
+ const liveDecision = mergeDecisions(liveImpls, input, {
69
110
  warn: this.logger.warn,
70
111
  onImplementationUnhealthy: (impl, reason) => {
71
112
  const source = this.implementationSources.get(impl.implId);
@@ -76,11 +117,12 @@ export class RuleHost {
76
117
  this._recordUnhealthy(source.activationId, source.artifactId, source.ruleId, reason, 'Fix the RuleCode runtime error or return shape, then re-activate the rule');
77
118
  },
78
119
  });
120
+ return { liveDecision, shadowDecisions };
79
121
  }
80
122
  catch (hostError) {
81
123
  // Conservative degradation: log and return undefined (D-08)
82
124
  this.logger.warn?.(`[RuleHost] Host evaluation failed, degrading conservatively: ${String(hostError)}`);
83
- return undefined;
125
+ return { liveDecision: undefined, shadowDecisions: [] };
84
126
  }
85
127
  }
86
128
  /**
@@ -93,12 +135,12 @@ export class RuleHost {
93
135
  * Source: activations table (code_tool_hook channel, deactivated_at IS NULL)
94
136
  * → JOIN pi_artifacts for content_json → extract implementationCode → compile
95
137
  */
96
- _loadActiveCodeImplementations() {
138
+ _loadActiveCodeImplementations(supportsContextV2) {
97
139
  if (!this.workspaceDir) {
98
140
  return [];
99
141
  }
100
142
  try {
101
- return this._loadFromActivationsTable(this.workspaceDir);
143
+ return this._loadFromActivationsTable(this.workspaceDir, supportsContextV2);
102
144
  }
103
145
  catch (activationError) {
104
146
  this.logger.warn?.(`[RuleHost] Failed to load code_tool_hook activations: ${String(activationError)}`);
@@ -120,13 +162,13 @@ export class RuleHost {
120
162
  *
121
163
  * All data from SQLite is treated as unknown and validated before use.
122
164
  */
123
- _loadFromActivationsTable(workspaceDir) {
124
- this.implementationSources.clear();
125
- const sqliteConn = new SqliteConnection(workspaceDir);
126
- try {
165
+ _loadFromActivationsTable(workspaceDir, supportsContextV2) {
166
+ const sqliteConn = this.sqliteConnection ?? new SqliteConnection(workspaceDir);
167
+ this.sqliteConnection = sqliteConn;
168
+ {
127
169
  const db = sqliteConn.getDb();
128
170
  const rows = db.prepare(`
129
- SELECT a.activation_id, a.artifact_id, a.target_ref,
171
+ SELECT a.activation_id, a.artifact_id, a.target_ref, a.action,
130
172
  p.content_json, p.source_rule_id
131
173
  FROM activations a
132
174
  JOIN pi_artifacts p ON a.artifact_id = p.artifact_id
@@ -137,6 +179,20 @@ export class RuleHost {
137
179
  this.logger.warn?.('[RuleHost] Activations table query returned non-array, skipping');
138
180
  return [];
139
181
  }
182
+ const fingerprintParts = [supportsContextV2 ? 'context-v2' : 'context-v1'];
183
+ for (const row of rows) {
184
+ if (!row || typeof row !== 'object' || Array.isArray(row))
185
+ continue;
186
+ const record = row;
187
+ fingerprintParts.push([
188
+ record['activation_id'], record['artifact_id'], record['target_ref'], record['action'], record['content_json'], record['source_rule_id'],
189
+ ].map((value) => typeof value === 'string' ? value : '').join('\u0001'));
190
+ }
191
+ const fingerprint = fingerprintParts.join('\u0002');
192
+ if (fingerprint === this.activationFingerprint) {
193
+ return [...this.cachedImplementations];
194
+ }
195
+ this.implementationSources.clear();
140
196
  // PRI-436: Group active rows by target_ref to detect duplicates.
141
197
  // At most one active activation per rule (target_ref) is allowed.
142
198
  // Duplicate groups are skipped entirely (zero executions) and emit
@@ -199,10 +255,35 @@ export class RuleHost {
199
255
  const artifactId = typeof r['artifact_id'] === 'string' ? r['artifact_id'] : '';
200
256
  const contentJson = typeof r['content_json'] === 'string' ? r['content_json'] : '';
201
257
  const sourceRuleId = typeof r['source_rule_id'] === 'string' ? r['source_rule_id'] : null;
258
+ const action = typeof r['action'] === 'string' ? r['action'] : '';
202
259
  if (!activationId || !artifactId || !contentJson) {
203
260
  this.logger.warn?.(`[RuleHost] Activation row missing required fields, skipping`);
204
261
  continue;
205
262
  }
263
+ const activationMode = action === 'code_tool_hook_shadow_activate'
264
+ ? 'shadow'
265
+ : action === 'code_tool_hook_live_activate'
266
+ ? 'live'
267
+ : null;
268
+ if (!activationMode) {
269
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported action ${action || '(missing)'}, skipping. ` +
270
+ 'nextAction=deactivate and recreate the activation through RuleHostWriter');
271
+ continue;
272
+ }
273
+ if (activationMode === 'shadow') {
274
+ // CodeRabbit PR #1121: shadow activations are observation-only and do
275
+ // NOT enter mergeDecisions (no block / requireApproval). Historical
276
+ // owner-approved records persisted with action=code_tool_hook_shadow_activate
277
+ // (before RuleHostWriter was fixed to write code_tool_hook_live_activate)
278
+ // silently lost execution after the dual-mode change. Surface a
279
+ // structured reason + nextAction so the degradation is observable
280
+ // (rc-9-no-silent-fallback), not silent. Fires once per fingerprint
281
+ // change (cached), not per evaluation.
282
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: loaded in shadow (observation-only) mode; ` +
283
+ 'it will NOT block or require approval (shadowDecisions only). ' +
284
+ `nextAction=run \`pd runtime activation promote --activation-id ${activationId} --confirm\` to enable live blocking, ` +
285
+ 'or leave as-is for shadow observation.');
286
+ }
206
287
  try {
207
288
  const content = JSON.parse(contentJson);
208
289
  if (!content || typeof content !== 'object' || Array.isArray(content)) {
@@ -210,6 +291,18 @@ export class RuleHost {
210
291
  continue;
211
292
  }
212
293
  const contentObj = content;
294
+ if (Object.hasOwn(contentObj, 'requiresContextVersion')) {
295
+ if (contentObj['requiresContextVersion'] !== 2) {
296
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported context version; skipping. ` +
297
+ 'nextAction=regenerate the rule artifact with requiresContextVersion: 2 or omit the field for v1');
298
+ continue;
299
+ }
300
+ if (!supportsContextV2) {
301
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: suspended because rulecode_context_v2 is disabled or unavailable; skipping. ` +
302
+ 'nextAction=enable rulecode_context_v2 with valid config or deactivate this activation');
303
+ continue;
304
+ }
305
+ }
213
306
  const implementationCode = contentObj['implementationCode'];
214
307
  if (typeof implementationCode !== 'string' || implementationCode.length === 0) {
215
308
  this.logger.warn?.(`[RuleHost] Activation ${activationId}: no implementationCode in artifact, skipping`);
@@ -244,6 +337,8 @@ export class RuleHost {
244
337
  implId,
245
338
  ruleId,
246
339
  meta,
340
+ activationId,
341
+ activationMode,
247
342
  evaluate: (input) => {
248
343
  const frozenHelpers = createRuleHostHelpers(input);
249
344
  // PRI-437: Execute inside vm context with timeout boundary.
@@ -269,16 +364,10 @@ export class RuleHost {
269
364
  this._recordUnhealthy(activationId, artifactId, sourceRuleId ?? artifactId, reason, 'Fix the RuleCode syntax/compilation error, then re-activate the rule');
270
365
  }
271
366
  }
367
+ this.activationFingerprint = fingerprint;
368
+ this.cachedImplementations = loaded;
272
369
  return loaded;
273
370
  }
274
- finally {
275
- try {
276
- sqliteConn.close();
277
- }
278
- catch {
279
- // best-effort cleanup
280
- }
281
- }
282
371
  }
283
372
  /**
284
373
  * PRI-437: Record an unhealthy activation state to EventLog.
@@ -232,6 +232,10 @@ export interface RuleHostContextRow {
232
232
  readonly outcome: string;
233
233
  readonly paramsJson: string;
234
234
  }
235
+ /** A single tool call selected by an Owner as labelled RuleHost evidence. */
236
+ export interface RuleHostEvidenceRow extends RuleHostContextRow {
237
+ readonly sessionId: string;
238
+ }
235
239
  /**
236
240
  * Result of getRuleHostContextRows: FIFO-ordered rows + truncated flag.
237
241
  * truncated=true when more than `limit` rows existed (limit+1 read trick).
@@ -1,5 +1,5 @@
1
- import type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextResult } from './trajectory-types.js';
2
- export type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, DailyMetricRow, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, TaskKind, TaskPriority, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextRow, RuleHostContextResult, } from './trajectory-types.js';
1
+ import type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextResult, RuleHostEvidenceRow } from './trajectory-types.js';
2
+ export type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, DailyMetricRow, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, TaskKind, TaskPriority, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextRow, RuleHostContextResult, RuleHostEvidenceRow, } from './trajectory-types.js';
3
3
  /**
4
4
  * Initialize trajectory.db schema at the given workspace directory.
5
5
  *
@@ -130,6 +130,10 @@ export declare class TrajectoryDatabase {
130
130
  * Spec: §5.1, §5.2. ERR-026: reuses production schema (no hand-written DDL).
131
131
  */
132
132
  getRuleHostContextRows(sessionId: string, limit?: number): RuleHostContextResult;
133
+ /** Resolve one Owner-selected tool call without inferring its desired label. */
134
+ getRuleHostEvidenceRow(id: number): RuleHostEvidenceRow | null;
135
+ /** Return FIFO history strictly before an Owner-selected tool call. */
136
+ getRuleHostContextRowsBefore(sessionId: string, beforeId: number, limit?: number): RuleHostContextResult;
133
137
  /**
134
138
  * List pain events for a session.
135
139
  *
@@ -961,6 +961,47 @@ export class TrajectoryDatabase {
961
961
  truncated,
962
962
  };
963
963
  }
964
+ /** Resolve one Owner-selected tool call without inferring its desired label. */
965
+ getRuleHostEvidenceRow(id) {
966
+ const row = this.db.prepare(`
967
+ SELECT id, session_id, tool_name, outcome, params_json
968
+ FROM tool_calls
969
+ WHERE id = ?
970
+ LIMIT 1
971
+ `).get(id);
972
+ if (!isStringRecord(row))
973
+ return null;
974
+ if (typeof row.id !== 'number' || typeof row.session_id !== 'string')
975
+ return null;
976
+ if (typeof row.tool_name !== 'string' || typeof row.outcome !== 'string' || typeof row.params_json !== 'string')
977
+ return null;
978
+ return {
979
+ id: row.id,
980
+ sessionId: row.session_id,
981
+ toolName: row.tool_name,
982
+ outcome: row.outcome,
983
+ paramsJson: row.params_json,
984
+ };
985
+ }
986
+ /** Return FIFO history strictly before an Owner-selected tool call. */
987
+ getRuleHostContextRowsBefore(sessionId, beforeId, limit = 20) {
988
+ const cappedLimit = Math.max(1, Math.floor(limit));
989
+ const rows = this.db.prepare(`
990
+ SELECT id, tool_name, outcome, params_json
991
+ FROM tool_calls
992
+ WHERE session_id = ? AND id < ?
993
+ ORDER BY id DESC
994
+ LIMIT ?
995
+ `).all(sessionId, beforeId, cappedLimit + 1);
996
+ const truncated = rows.length > cappedLimit;
997
+ const output = (truncated ? rows.slice(0, cappedLimit) : rows).reverse();
998
+ return {
999
+ rows: output.map((row) => ({
1000
+ id: Number(row.id), toolName: String(row.tool_name), outcome: String(row.outcome), paramsJson: String(row.params_json),
1001
+ })),
1002
+ truncated,
1003
+ };
1004
+ }
964
1005
  /**
965
1006
  * List pain events for a session.
966
1007
  *
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import yaml from 'js-yaml';
12
+ import * as yaml from 'js-yaml';
13
13
  // ─────────────────────────────────────────────────────────────────────────────
14
14
  // WorkflowFunnelLoader
15
15
  // ─────────────────────────────────────────────────────────────────────────────
@@ -33,7 +33,7 @@ export class WorkflowFunnelLoader {
33
33
  }
34
34
  try {
35
35
  const content = fs.readFileSync(this.configPath, 'utf-8');
36
- const config = yaml.load(content, { schema: yaml.DEFAULT_SCHEMA });
36
+ const config = yaml.load(content, { schema: yaml.CORE_SCHEMA });
37
37
  if (!config || typeof config.version !== 'string' || !Array.isArray(config.funnels)) {
38
38
  const msg = 'workflows.yaml validation failed: missing version or funnels array. Preserving last valid config.';
39
39
  console.warn(`[WorkflowFunnelLoader] ${msg}`);
@@ -8,6 +8,8 @@ import type { TrajectoryDatabase } from './trajectory.js';
8
8
  import { PrincipleLifecycleService } from './principle-internalization/principle-lifecycle-service.js';
9
9
  import { type PrincipleSubtree, type LedgerPrinciple, type PrincipleValueMetrics } from './principle-tree-ledger.js';
10
10
  import type { Principle as ActivePrinciple } from './evolution-types.js';
11
+ import { RuleHost } from './rule-host.js';
12
+ import type { RuleHostLogger } from './rule-host.js';
11
13
  interface PrincipleTreeLedgerAccessor {
12
14
  getPrincipleSubtree(_principleId: string): PrincipleSubtree | undefined;
13
15
  updatePrinciple(_principleId: string, updates: Partial<LedgerPrinciple>): LedgerPrinciple;
@@ -30,6 +32,7 @@ export declare class WorkspaceContext {
30
32
  private _trajectory?;
31
33
  private _principleTreeLedger?;
32
34
  private _principleLifecycle?;
35
+ private _ruleHost?;
33
36
  private constructor();
34
37
  /**
35
38
  * Governance configuration for this workspace.
@@ -55,6 +58,7 @@ export declare class WorkspaceContext {
55
58
  * Trajectory database for analytics and sample curation.
56
59
  */
57
60
  get trajectory(): TrajectoryDatabase;
61
+ getRuleHost(logger: RuleHostLogger): RuleHost;
58
62
  /**
59
63
  * Locked ledger access for principle tree reads and metric writes in this workspace.
60
64
  */
@@ -9,6 +9,7 @@ import { EvolutionReducerImpl } from './evolution-reducer.js';
9
9
  import { TrajectoryRegistry } from './trajectory.js';
10
10
  import { PrincipleLifecycleService } from './principle-internalization/principle-lifecycle-service.js';
11
11
  import { getPrincipleSubtree, updatePrinciple, updatePrincipleValueMetrics, } from './principle-tree-ledger.js';
12
+ import { RuleHost } from './rule-host.js';
12
13
  /**
13
14
  * WorkspaceContext - Centralized management of workspace-specific paths and services.
14
15
  * Implements a cached singleton pattern per workspace directory.
@@ -26,6 +27,7 @@ export class WorkspaceContext {
26
27
  _trajectory;
27
28
  _principleTreeLedger;
28
29
  _principleLifecycle;
30
+ _ruleHost;
29
31
  constructor(workspaceDir, stateDir) {
30
32
  this.workspaceDir = workspaceDir;
31
33
  this.stateDir = stateDir;
@@ -84,6 +86,15 @@ export class WorkspaceContext {
84
86
  }
85
87
  return this._trajectory;
86
88
  }
89
+ getRuleHost(logger) {
90
+ if (!this._ruleHost) {
91
+ this._ruleHost = new RuleHost(this.stateDir, logger, { workspaceDir: this.workspaceDir });
92
+ }
93
+ else {
94
+ this._ruleHost.updateLogger(logger);
95
+ }
96
+ return this._ruleHost;
97
+ }
87
98
  /**
88
99
  * Locked ledger access for principle tree reads and metric writes in this workspace.
89
100
  */
@@ -214,6 +225,8 @@ export class WorkspaceContext {
214
225
  * Resets internal caches for services and paths.
215
226
  */
216
227
  invalidate() {
228
+ this._ruleHost?.dispose();
229
+ this._ruleHost = undefined;
217
230
  this._config = undefined;
218
231
  this._eventLog = undefined;
219
232
  this._dictionary = undefined;
@@ -8,11 +8,9 @@
8
8
  * 1. Early Return: Skip if not write/bash/agent tool or no workspace
9
9
  * 2. Rule Host: Dynamic principle-based evaluation (sole gate)
10
10
  */
11
- import { normalizePath } from '../utils/io.js';
12
11
  import { WorkspaceContext } from '../core/workspace-context.js';
13
12
  import { recordGateBlockAndReturn } from './gate-block-helper.js';
14
- import { RuleHost } from '../core/rule-host.js';
15
- import { validateCorrectionProposal, validateProposedPathBounds, computeFeatureFlagsFromConfig, UNAVAILABLE_RULE_CONTEXT } from '@principles/core/runtime-v2';
13
+ import { buildRuleHostAction, validateCorrectionProposal, validateProposedPathBounds, computeFeatureFlagsFromConfig, UNAVAILABLE_RULE_CONTEXT } from '@principles/core/runtime-v2';
16
14
  import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js';
17
15
  import { getSession, hasRecentThinking } from '../core/session-tracker.js';
18
16
  import { getEvolutionEngine } from '../core/evolution-engine.js';
@@ -30,41 +28,33 @@ export function handleBeforeToolCall(event, ctx) {
30
28
  return;
31
29
  }
32
30
  const wctx = WorkspaceContext.fromHookContext(ctx);
33
- // 2. Resolve the target file path
34
- let filePath = event.params?.file_path || event.params?.path || event.params?.file || event.params?.target;
35
- // Heuristic for bash mutation detection
36
- if (isBash && !filePath) {
37
- const command = String(event.params?.command || event.params?.args || '');
38
- const mutationMatch = /(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/.exec(command);
39
- if (mutationMatch) {
40
- filePath = mutationMatch[1];
41
- }
42
- else {
43
- // Bash command without a clear file target — let it through to Rule Host
44
- filePath = command;
45
- }
46
- }
47
- // Write tools without a file path must still go through RuleHost evaluation.
48
- // Use a synthetic path so RuleHost can evaluate and potentially block.
49
- if (!filePath && isWriteTool) {
50
- filePath = `<tool:${event.toolName}>`;
51
- }
52
- if (typeof filePath !== 'string')
31
+ // 2. Use the same action builder as Golden Trace replay. This is the single
32
+ // path extraction + normalization contract for production and evaluation.
33
+ // CodeRabbit PR2 Comment 1: pass the normalized workspace root from
34
+ // WorkspaceContext (wctx.workspaceDir) rather than the raw ctx.workspaceDir,
35
+ // so action.normalizedPath is consistent with the rest of the hook path
36
+ // (which uses WorkspaceContext.fromHookContext's normalized root). Mixing
37
+ // the raw value here produced paths that disagreed with the normalized root
38
+ // used downstream by the rule host / rule-context assembler.
39
+ const action = buildRuleHostAction(event.toolName, event.params ?? {}, wctx.workspaceDir, {
40
+ isBashTool: isBash,
41
+ isWriteTool,
42
+ });
43
+ const relPath = action.normalizedPath;
44
+ // buildRuleHostAction returns null when no path can be extracted (e.g. bash
45
+ // command with no file target and no clear mutation operator). Mirror the
46
+ // legacy guard: no path no gate evaluation, let the tool through.
47
+ if (relPath === null)
53
48
  return;
54
- const relPath = normalizePath(filePath, ctx.workspaceDir);
55
49
  // 3. Rule Host Evaluation — sole gate
56
50
  try {
57
- const ruleHost = new RuleHost(wctx.stateDir, logger, { workspaceDir: ctx.workspaceDir });
51
+ const ruleHost = wctx.getRuleHost(logger);
58
52
  // PRI-483 Phase 4: assemble RuleContextV2 when `rulecode_context_v2` flag is ON.
59
53
  // flag OFF → undefined (v1 zero-change, no trajectory access).
60
54
  // flag ON → RuleContextV2 (available or unavailable). Never throws (ERR-024).
61
55
  const ruleContext = _buildRuleContextIfEnabled(wctx, relPath, ctx.sessionId, logger);
62
56
  const hostInput = {
63
- action: {
64
- toolName: event.toolName,
65
- normalizedPath: relPath,
66
- paramsSummary: _extractParamsSummary(event.params ?? {}),
67
- },
57
+ action,
68
58
  workspace: {
69
59
  isRiskPath: false, // Rule Host determines risk dynamically
70
60
  // DEPRECATED (PRI-286): planStatus/hasPlanFile are legacy compatibility fields.
@@ -88,7 +78,27 @@ export function handleBeforeToolCall(event, ctx) {
88
78
  },
89
79
  context: ruleContext,
90
80
  };
91
- const hostResult = ruleHost.evaluate(hostInput);
81
+ const report = typeof ruleHost.evaluateDetailed === 'function'
82
+ ? ruleHost.evaluateDetailed(hostInput)
83
+ : { liveDecision: ruleHost.evaluate(hostInput), shadowDecisions: [] };
84
+ const hostResult = report.liveDecision;
85
+ for (const shadowDecision of report.shadowDecisions) {
86
+ try {
87
+ const eventLog = EventLogService.get(wctx.stateDir, logger);
88
+ eventLog.recordRuleHostEvaluated({
89
+ toolName: event.toolName,
90
+ filePath: relPath,
91
+ matched: shadowDecision.matched,
92
+ decision: shadowDecision.decision,
93
+ ruleId: shadowDecision.ruleId,
94
+ activationId: shadowDecision.activationId,
95
+ activationMode: 'shadow',
96
+ });
97
+ }
98
+ catch (evErr) {
99
+ logger?.warn?.(`[PD_GATE] Failed to record shadow rulehost_evaluated: ${String(evErr)}`);
100
+ }
101
+ }
92
102
  // Always emit rulehost_evaluated
93
103
  try {
94
104
  const eventLog = EventLogService.get(wctx.stateDir, logger);
@@ -98,6 +108,7 @@ export function handleBeforeToolCall(event, ctx) {
98
108
  matched: hostResult?.matched ?? false,
99
109
  decision: hostResult?.decision ?? 'allow',
100
110
  ruleId: hostResult?.ruleId,
111
+ activationMode: 'live',
101
112
  });
102
113
  }
103
114
  catch (evErr) {
@@ -321,25 +332,6 @@ export function handleBeforeToolCall(event, ctx) {
321
332
  // ---------------------------------------------------------------------------
322
333
  // Private helpers
323
334
  // ---------------------------------------------------------------------------
324
- function _extractParamsSummary(params) {
325
- const summary = {};
326
- // NOTE: Do NOT redact here — this feeds into RuleHost.evaluate() which
327
- // may match against paramsSummary.command. Redaction happens at
328
- // EventLog.record() before persistence.
329
- if (params.file_path)
330
- summary.file_path = params.file_path;
331
- if (params.path)
332
- summary.path = params.path;
333
- if (params.command)
334
- summary.command = params.command;
335
- if (params.args)
336
- summary.args = params.args;
337
- if (params.old_string)
338
- summary.old_string = params.old_string;
339
- if (params.new_string)
340
- summary.new_string = params.new_string;
341
- return summary;
342
- }
343
335
  function _getCurrentGfi(sessionId) {
344
336
  if (!sessionId)
345
337
  return 0;
@@ -0,0 +1,4 @@
1
+ /** Side-effect-free public boundary for RuleHost evidence assembly. */
2
+ export { BehaviorExamplePackAssembler } from './core/behavior-example-pack-assembler.js';
3
+ export type { BehaviorExamplePackAssemblerInput, BehaviorExamplePackAssemblerOptions, } from './core/behavior-example-pack-assembler.js';
4
+ export { TrajectoryRegistry as RuleHostEvidenceRegistry } from './core/trajectory.js';
@@ -0,0 +1,3 @@
1
+ /** Side-effect-free public boundary for RuleHost evidence assembly. */
2
+ export { BehaviorExamplePackAssembler } from './core/behavior-example-pack-assembler.js';
3
+ export { TrajectoryRegistry as RuleHostEvidenceRegistry } from './core/trajectory.js';
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./rulehost-evidence": {
14
+ "types": "./dist/rulehost-evidence.d.ts",
15
+ "default": "./dist/rulehost-evidence.js"
12
16
  }
13
17
  },
14
18
  "repository": {
@@ -83,7 +87,7 @@
83
87
  "@sinclair/typebox": "^0.34.48",
84
88
  "better-sqlite3": "^12.9.0",
85
89
  "commander": "^12.0.0",
86
- "js-yaml": "^4.1.1",
90
+ "js-yaml": "^5.2.0",
87
91
  "micromatch": "^4.0.8"
88
92
  }
89
93
  }