create-principles-disciple 1.80.0 → 1.82.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 (183) hide show
  1. package/core/dist/runtime-v2/__tests__/admission-gate.test.js +6 -6
  2. package/core/dist/runtime-v2/__tests__/admission-gate.test.js.map +1 -1
  3. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +39 -0
  4. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  5. package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js +52 -0
  6. package/core/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
  7. package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js +245 -1
  8. package/core/dist/runtime-v2/__tests__/evaluator-runner-vslice.test.js.map +1 -1
  9. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js +131 -1
  10. package/core/dist/runtime-v2/__tests__/feedback/redact-sensitive.test.js.map +1 -1
  11. package/core/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js +425 -2
  12. package/core/dist/runtime-v2/__tests__/scribe-runner-vslice.test.js.map +1 -1
  13. package/core/dist/runtime-v2/__tests__/telemetry-event.test.d.ts +2 -0
  14. package/core/dist/runtime-v2/__tests__/telemetry-event.test.d.ts.map +1 -0
  15. package/core/dist/runtime-v2/__tests__/telemetry-event.test.js +69 -0
  16. package/core/dist/runtime-v2/__tests__/telemetry-event.test.js.map +1 -0
  17. package/core/dist/runtime-v2/admission-gate.d.ts.map +1 -1
  18. package/core/dist/runtime-v2/admission-gate.js +0 -8
  19. package/core/dist/runtime-v2/admission-gate.js.map +1 -1
  20. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts +15 -0
  21. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.d.ts.map +1 -0
  22. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +685 -0
  23. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -0
  24. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.d.ts +13 -0
  25. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.d.ts.map +1 -0
  26. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js +447 -0
  27. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js.map +1 -0
  28. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.d.ts +12 -0
  29. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.d.ts.map +1 -0
  30. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +433 -0
  31. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -0
  32. package/core/dist/runtime-v2/config/index.d.ts +14 -0
  33. package/core/dist/runtime-v2/config/index.d.ts.map +1 -0
  34. package/core/dist/runtime-v2/config/index.js +16 -0
  35. package/core/dist/runtime-v2/config/index.js.map +1 -0
  36. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +14 -0
  37. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/config/pd-config-defaults.js +74 -0
  39. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -0
  40. package/core/dist/runtime-v2/config/pd-config-effective.d.ts +14 -0
  41. package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -0
  42. package/core/dist/runtime-v2/config/pd-config-effective.js +118 -0
  43. package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -0
  44. package/core/dist/runtime-v2/config/pd-config-feature-flags.d.ts +35 -0
  45. package/core/dist/runtime-v2/config/pd-config-feature-flags.d.ts.map +1 -0
  46. package/core/dist/runtime-v2/config/pd-config-feature-flags.js +99 -0
  47. package/core/dist/runtime-v2/config/pd-config-feature-flags.js.map +1 -0
  48. package/core/dist/runtime-v2/config/pd-config-redaction.d.ts +22 -0
  49. package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -0
  50. package/core/dist/runtime-v2/config/pd-config-redaction.js +179 -0
  51. package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -0
  52. package/core/dist/runtime-v2/config/pd-config-types.d.ts +124 -0
  53. package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -0
  54. package/core/dist/runtime-v2/config/pd-config-types.js +35 -0
  55. package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -0
  56. package/core/dist/runtime-v2/config/pd-config-validate.d.ts +16 -0
  57. package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -0
  58. package/core/dist/runtime-v2/config/pd-config-validate.js +443 -0
  59. package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -0
  60. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js +10 -10
  61. package/core/dist/runtime-v2/feature-flags/plugin-surface-registry.js.map +1 -1
  62. package/core/dist/runtime-v2/feedback/index.d.ts +1 -1
  63. package/core/dist/runtime-v2/feedback/index.d.ts.map +1 -1
  64. package/core/dist/runtime-v2/feedback/index.js +1 -1
  65. package/core/dist/runtime-v2/feedback/index.js.map +1 -1
  66. package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts +2 -0
  67. package/core/dist/runtime-v2/feedback/redact-sensitive.d.ts.map +1 -1
  68. package/core/dist/runtime-v2/feedback/redact-sensitive.js +32 -3
  69. package/core/dist/runtime-v2/feedback/redact-sensitive.js.map +1 -1
  70. package/core/dist/runtime-v2/index.d.ts +5 -1
  71. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  72. package/core/dist/runtime-v2/index.js +9 -1
  73. package/core/dist/runtime-v2/index.js.map +1 -1
  74. package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts +2 -0
  75. package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.d.ts.map +1 -0
  76. package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js +398 -0
  77. package/core/dist/runtime-v2/internalization/__tests__/philosopher-runner-trust-boundary.test.js.map +1 -0
  78. package/core/dist/runtime-v2/internalization/artificer-output.d.ts +3 -2
  79. package/core/dist/runtime-v2/internalization/artificer-output.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/internalization/artificer-output.js +40 -31
  81. package/core/dist/runtime-v2/internalization/artificer-output.js.map +1 -1
  82. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts +59 -38
  83. package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
  84. package/core/dist/runtime-v2/internalization/artificer-runner.js +121 -395
  85. package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
  86. package/core/dist/runtime-v2/internalization/dreamer-output.d.ts +4 -3
  87. package/core/dist/runtime-v2/internalization/dreamer-output.d.ts.map +1 -1
  88. package/core/dist/runtime-v2/internalization/dreamer-output.js +11 -9
  89. package/core/dist/runtime-v2/internalization/dreamer-output.js.map +1 -1
  90. package/core/dist/runtime-v2/internalization/dreamer-runner.d.ts +31 -92
  91. package/core/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
  92. package/core/dist/runtime-v2/internalization/dreamer-runner.js +79 -495
  93. package/core/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
  94. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +2 -2
  95. package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  96. package/core/dist/runtime-v2/internalization/evaluator-output.js +29 -23
  97. package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  98. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +74 -48
  99. package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  100. package/core/dist/runtime-v2/internalization/evaluator-runner.js +164 -444
  101. package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  102. package/core/dist/runtime-v2/internalization/philosopher-output.d.ts +8 -2
  103. package/core/dist/runtime-v2/internalization/philosopher-output.d.ts.map +1 -1
  104. package/core/dist/runtime-v2/internalization/philosopher-output.js +16 -11
  105. package/core/dist/runtime-v2/internalization/philosopher-output.js.map +1 -1
  106. package/core/dist/runtime-v2/internalization/philosopher-runner.d.ts +36 -55
  107. package/core/dist/runtime-v2/internalization/philosopher-runner.d.ts.map +1 -1
  108. package/core/dist/runtime-v2/internalization/philosopher-runner.js +117 -401
  109. package/core/dist/runtime-v2/internalization/philosopher-runner.js.map +1 -1
  110. package/core/dist/runtime-v2/internalization/scribe-output.d.ts +8 -2
  111. package/core/dist/runtime-v2/internalization/scribe-output.d.ts.map +1 -1
  112. package/core/dist/runtime-v2/internalization/scribe-output.js +33 -17
  113. package/core/dist/runtime-v2/internalization/scribe-output.js.map +1 -1
  114. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +56 -49
  115. package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  116. package/core/dist/runtime-v2/internalization/scribe-runner.js +116 -398
  117. package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  118. package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts +2 -0
  119. package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.d.ts.map +1 -0
  120. package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js +379 -0
  121. package/core/dist/runtime-v2/runner/__tests__/base-peer-runner-trust-boundary.test.js.map +1 -0
  122. package/core/dist/runtime-v2/runner/base-peer-runner.d.ts +122 -0
  123. package/core/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -0
  124. package/core/dist/runtime-v2/runner/base-peer-runner.js +528 -0
  125. package/core/dist/runtime-v2/runner/base-peer-runner.js.map +1 -0
  126. package/core/dist/runtime-v2/runner/peer-runner-types.d.ts +107 -0
  127. package/core/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -0
  128. package/core/dist/runtime-v2/runner/peer-runner-types.js +13 -0
  129. package/core/dist/runtime-v2/runner/peer-runner-types.js.map +1 -0
  130. package/core/dist/telemetry-event.d.ts +2 -2
  131. package/core/dist/telemetry-event.d.ts.map +1 -1
  132. package/core/dist/telemetry-event.js +89 -0
  133. package/core/dist/telemetry-event.js.map +1 -1
  134. package/dist/installer.d.ts +1 -1
  135. package/dist/installer.d.ts.map +1 -1
  136. package/dist/installer.js +24 -10
  137. package/dist/installer.js.map +1 -1
  138. package/dist/mvp-config.d.ts +33 -0
  139. package/dist/mvp-config.d.ts.map +1 -1
  140. package/dist/mvp-config.js +174 -0
  141. package/dist/mvp-config.js.map +1 -1
  142. package/package.json +1 -1
  143. package/pd-cli/dist/commands/config-doctor.d.ts +17 -0
  144. package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -0
  145. package/pd-cli/dist/commands/config-doctor.js +143 -0
  146. package/pd-cli/dist/commands/config-doctor.js.map +1 -0
  147. package/pd-cli/dist/commands/console.d.ts +18 -0
  148. package/pd-cli/dist/commands/console.d.ts.map +1 -1
  149. package/pd-cli/dist/commands/console.js +439 -0
  150. package/pd-cli/dist/commands/console.js.map +1 -1
  151. package/pd-cli/dist/commands/runtime-features.d.ts +23 -8
  152. package/pd-cli/dist/commands/runtime-features.d.ts.map +1 -1
  153. package/pd-cli/dist/commands/runtime-features.js +72 -31
  154. package/pd-cli/dist/commands/runtime-features.js.map +1 -1
  155. package/pd-cli/dist/index.d.ts +1 -1
  156. package/pd-cli/dist/index.js +83 -31
  157. package/pd-cli/dist/index.js.map +1 -1
  158. package/pd-cli/dist/services/config-doctor.d.ts +102 -0
  159. package/pd-cli/dist/services/config-doctor.d.ts.map +1 -0
  160. package/pd-cli/dist/services/config-doctor.js +426 -0
  161. package/pd-cli/dist/services/config-doctor.js.map +1 -0
  162. package/pd-cli/dist/services/console-launcher.d.ts +110 -0
  163. package/pd-cli/dist/services/console-launcher.d.ts.map +1 -0
  164. package/pd-cli/dist/services/console-launcher.js +282 -0
  165. package/pd-cli/dist/services/console-launcher.js.map +1 -0
  166. package/pd-cli/dist/services/pd-config-loader.d.ts +64 -0
  167. package/pd-cli/dist/services/pd-config-loader.d.ts.map +1 -0
  168. package/pd-cli/dist/services/pd-config-loader.js +156 -0
  169. package/pd-cli/dist/services/pd-config-loader.js.map +1 -0
  170. package/pd-cli/package.json +3 -1
  171. package/plugin/dist/core/event-log.d.ts +10 -0
  172. package/plugin/dist/core/event-log.js +85 -1
  173. package/plugin/dist/core/pd-config-loader.d.ts +96 -0
  174. package/plugin/dist/core/pd-config-loader.js +307 -0
  175. package/plugin/dist/core/runtime-v2-prompt-activation-reader.d.ts +4 -0
  176. package/plugin/dist/core/runtime-v2-prompt-activation-reader.js +12 -56
  177. package/plugin/dist/core/surface-guard.d.ts +12 -8
  178. package/plugin/dist/core/surface-guard.js +51 -2
  179. package/plugin/dist/hooks/gate.js +3 -0
  180. package/plugin/dist/index.d.ts +4 -0
  181. package/plugin/dist/index.js +8 -52
  182. package/plugin/dist/service/correction-observer-service.d.ts +8 -0
  183. package/plugin/dist/service/correction-observer-service.js +48 -29
@@ -2,6 +2,7 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { createEmptyDailyStats } from '../types/event-types.js';
4
4
  import { atomicWriteFileSync } from '../utils/io.js';
5
+ import { redactTelemetryString } from '@principles/core/runtime-v2';
5
6
  const EVENT_LOG_RETENTION_DAYS = 7;
6
7
  export class EventLog {
7
8
  logsDir;
@@ -142,16 +143,99 @@ export class EventLog {
142
143
  recordRuntimeV2ActivationsInjected(data) {
143
144
  this.record('runtime_v2_prompt_activations_injected', 'injected', data.sessionId, data);
144
145
  }
146
+ /**
147
+ * Redact telemetry-sensitive string values in event data before persistence.
148
+ * Applies redactTelemetryString to known high-risk fields (filePath, command,
149
+ * reason, args, new_string, old_string, text, paramsSummary values) and to all
150
+ * string values in tool_call/rulehost_* data as a safety net.
151
+ *
152
+ * ERR-002: never throws; returns data unchanged on error.
153
+ * ERR-045/046: covers composite command strings, Authorization headers, env vars.
154
+ */
155
+ redactEventData(type, data) {
156
+ try {
157
+ // Known high-risk event types — telemetry that carries tool commands / paths
158
+ const telemetryTypes = new Set([
159
+ 'tool_call',
160
+ 'rulehost_evaluated',
161
+ 'rulehost_blocked',
162
+ 'rulehost_requireApproval',
163
+ 'rulehost_auto_correct_proposed',
164
+ 'rulehost_auto_correct_applied',
165
+ 'rule_enforced',
166
+ 'hook_execution',
167
+ 'gate_block',
168
+ 'gate_bypass',
169
+ ]);
170
+ if (!telemetryTypes.has(type))
171
+ return data;
172
+ const redacted = {};
173
+ for (const [key, value] of Object.entries(data)) {
174
+ if (typeof value === 'string') {
175
+ redacted[key] = redactTelemetryString(value);
176
+ }
177
+ else if (Array.isArray(value)) {
178
+ // Recurse into arrays (e.g. correctedFields with original/applied)
179
+ redacted[key] = value.map((item) => {
180
+ if (typeof item === 'string') {
181
+ return redactTelemetryString(item);
182
+ }
183
+ if (typeof item === 'object' && item !== null) {
184
+ const nested = {};
185
+ for (const [nk, nv] of Object.entries(item)) {
186
+ nested[nk] = typeof nv === 'string' ? redactTelemetryString(nv) : nv;
187
+ }
188
+ return nested;
189
+ }
190
+ return item;
191
+ });
192
+ }
193
+ else if (typeof value === 'object' && value !== null) {
194
+ // Recurse one level for nested objects (e.g. paramsSummary)
195
+ const nested = {};
196
+ for (const [nk, nv] of Object.entries(value)) {
197
+ if (typeof nv === 'string') {
198
+ nested[nk] = redactTelemetryString(nv);
199
+ }
200
+ else {
201
+ nested[nk] = nv;
202
+ }
203
+ }
204
+ redacted[key] = nested;
205
+ }
206
+ else {
207
+ redacted[key] = value;
208
+ }
209
+ }
210
+ return redacted;
211
+ }
212
+ catch (e) {
213
+ // ERR-002: fail safe — never write raw payload on redaction failure.
214
+ // Return a masked payload with context so downstream knows what happened.
215
+ const errStr = e instanceof Error ? e.message.slice(0, 200) : String(e).slice(0, 200);
216
+ const masked = {
217
+ redactionFailure: true,
218
+ redactionStatus: 'failed',
219
+ 'redaction.status': 'failed',
220
+ redactionReason: errStr || 'unknown error',
221
+ redactionDataDropped: true,
222
+ originalType: type,
223
+ originalSessionId: data.sessionId ?? null,
224
+ };
225
+ return masked;
226
+ }
227
+ }
145
228
  record(type, category, sessionId, data) {
146
229
  const now = new Date();
147
230
  const date = this.formatDate(now);
231
+ const redactedData = this.redactEventData(type, data);
148
232
  const entry = {
149
233
  ts: now.toISOString(),
150
234
  date,
151
235
  type,
152
236
  category,
153
237
  sessionId,
154
- data: data,
238
+ data: redactedData,
155
239
  };
156
240
  this.eventBuffer.push(entry);
157
241
  this.updateStats(entry);
@@ -0,0 +1,96 @@
1
+ /**
2
+ * PD Config Loader (Plugin I/O boundary) — PRI-307
3
+ *
4
+ * Reads `.pd/config.yaml`, validates via core, computes effective config.
5
+ * Replaces the old `.pd/feature-flags.yaml` and `.state/workflows.yaml` reading
6
+ * for plugin production paths.
7
+ *
8
+ * ADR-0016: PD owns exactly one user config file.
9
+ * - Missing config → defaults with nextAction
10
+ * - Malformed config → fail loud with errors and nextAction
11
+ * - No secrets in output
12
+ * - Observer disabled → no start / no noisy log cycling
13
+ * - Observer enabled + missing setup → structured needs_setup + nextAction
14
+ */
15
+ import type { EffectivePdConfig } from '@principles/core/runtime-v2';
16
+ export declare const PD_CONFIG_DIR = ".pd";
17
+ export declare const PD_CONFIG_FILENAME = "config.yaml";
18
+ export type ObserverReadiness = 'disabled' | 'needs_setup' | 'ready' | 'not_ready' | 'config_malformed';
19
+ export interface ObserverConfigResult {
20
+ /** Whether the observer feature is enabled in config */
21
+ enabled: boolean;
22
+ /** Observer readiness state */
23
+ readiness: ObserverReadiness;
24
+ /** Config source: 'defaults' | 'user_config' | 'malformed' */
25
+ source: string;
26
+ /** Reason for current state */
27
+ reason: string;
28
+ /** What the user should do next */
29
+ nextAction: string;
30
+ /** The runtime profile ID for this observer, if configured */
31
+ runtimeProfileId: string | null;
32
+ /** The runtime profile type, if configured */
33
+ runtimeProfileType: string | null;
34
+ /** The apiKeyEnv for the runtime profile, if applicable */
35
+ apiKeyEnv: string | null;
36
+ /** Whether the apiKeyEnv is present in process.env */
37
+ apiKeyPresent: boolean;
38
+ /** Provider name from runtime profile */
39
+ provider: string | null;
40
+ /** Model name from runtime profile */
41
+ model: string | null;
42
+ /** Timeout from runtime profile */
43
+ timeoutMs: number | null;
44
+ /** Base URL from runtime profile */
45
+ baseUrl: string | null;
46
+ /** Config validation errors (only present when readiness=config_malformed) */
47
+ configErrors?: Array<{
48
+ path: string;
49
+ reason: string;
50
+ nextAction: string;
51
+ }>;
52
+ }
53
+ export interface PluginConfigLoadResult {
54
+ ok: boolean;
55
+ effective: EffectivePdConfig;
56
+ source: 'defaults' | 'user_config' | 'malformed';
57
+ configPath: string;
58
+ warnings: string[];
59
+ errors: Array<{
60
+ path: string;
61
+ reason: string;
62
+ nextAction: string;
63
+ }>;
64
+ }
65
+ export declare function getPdConfigPath(workspaceDir: string): string;
66
+ /**
67
+ * Load and validate `.pd/config.yaml` from the workspace.
68
+ * Never throws on malformed input. Always provides a usable fallback.
69
+ */
70
+ export declare function loadPdConfigForPlugin(workspaceDir: string): PluginConfigLoadResult;
71
+ /**
72
+ * Get a single feature flag's enabled state from .pd/config.yaml.
73
+ * Replaces the old `loadFeatureFlagFromWorkspace` which read .pd/feature-flags.yaml.
74
+ */
75
+ export declare function loadFeatureFlagFromConfig(workspaceDir: string, flagId: string, logger?: {
76
+ warn?: (msg: string) => void;
77
+ info?: (msg: string) => void;
78
+ }): {
79
+ enabled: boolean;
80
+ source: string;
81
+ };
82
+ /**
83
+ * Resolve observer configuration from .pd/config.yaml.
84
+ *
85
+ * Returns structured state:
86
+ * - config_malformed: config file is invalid — no guessing, fail loud
87
+ * - disabled: observer feature flag is off OR agent.enabled=false → no start, no noisy logs
88
+ * - needs_setup: observer enabled but runtime profile missing, API key not set, or unsupported profile type
89
+ * - ready: observer enabled and fully configured (pi-ai with key present)
90
+ * - not_ready: observer enabled, API key present, but runtime availability unknown
91
+ */
92
+ export declare function resolveObserverConfig(workspaceDir: string, observerFlagId: string, observerAgentName: string, _logger?: {
93
+ warn?: (msg: string) => void;
94
+ info?: (msg: string) => void;
95
+ debug?: (msg: string) => void;
96
+ }): ObserverConfigResult;
@@ -0,0 +1,307 @@
1
+ /**
2
+ * PD Config Loader (Plugin I/O boundary) — PRI-307
3
+ *
4
+ * Reads `.pd/config.yaml`, validates via core, computes effective config.
5
+ * Replaces the old `.pd/feature-flags.yaml` and `.state/workflows.yaml` reading
6
+ * for plugin production paths.
7
+ *
8
+ * ADR-0016: PD owns exactly one user config file.
9
+ * - Missing config → defaults with nextAction
10
+ * - Malformed config → fail loud with errors and nextAction
11
+ * - No secrets in output
12
+ * - Observer disabled → no start / no noisy log cycling
13
+ * - Observer enabled + missing setup → structured needs_setup + nextAction
14
+ */
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+ import yaml from 'js-yaml';
18
+ import { validatePdConfig, computeEffectivePdConfig, computeFeatureFlagsFromConfig, INTERNAL_AGENT_NAMES, } from '@principles/core/runtime-v2';
19
+ // ── Constants ────────────────────────────────────────────────────────────────
20
+ export const PD_CONFIG_DIR = '.pd';
21
+ export const PD_CONFIG_FILENAME = 'config.yaml';
22
+ // ── Config Path ──────────────────────────────────────────────────────────────
23
+ export function getPdConfigPath(workspaceDir) {
24
+ return path.join(workspaceDir, PD_CONFIG_DIR, PD_CONFIG_FILENAME);
25
+ }
26
+ // ── Load PD Config ───────────────────────────────────────────────────────────
27
+ /**
28
+ * Load and validate `.pd/config.yaml` from the workspace.
29
+ * Never throws on malformed input. Always provides a usable fallback.
30
+ */
31
+ export function loadPdConfigForPlugin(workspaceDir) {
32
+ const configPath = getPdConfigPath(workspaceDir);
33
+ // 1) Config file missing → use defaults
34
+ if (!fs.existsSync(configPath)) {
35
+ const effective = computeEffectivePdConfig(null);
36
+ return {
37
+ ok: true,
38
+ effective,
39
+ source: 'defaults',
40
+ configPath,
41
+ warnings: effective.warnings,
42
+ errors: [],
43
+ };
44
+ }
45
+ // 2) Read the file
46
+ let raw;
47
+ try {
48
+ raw = fs.readFileSync(configPath, 'utf8');
49
+ }
50
+ catch (err) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ const effective = computeEffectivePdConfig(null);
53
+ return {
54
+ ok: false,
55
+ effective,
56
+ source: 'malformed',
57
+ configPath,
58
+ warnings: [],
59
+ errors: [{ path: '', reason: `Failed to read .pd/config.yaml: ${message}`, nextAction: 'Check file permissions for .pd/config.yaml' }],
60
+ };
61
+ }
62
+ // 3) Parse YAML — treat as unknown (ERR-001)
63
+ let parsed;
64
+ try {
65
+ parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
66
+ }
67
+ catch (err) {
68
+ const message = err instanceof Error ? err.message : String(err);
69
+ const effective = computeEffectivePdConfig(null);
70
+ return {
71
+ ok: false,
72
+ effective,
73
+ source: 'malformed',
74
+ configPath,
75
+ warnings: [],
76
+ errors: [{ path: '', reason: `YAML parse error in .pd/config.yaml: ${message}`, nextAction: 'Fix YAML syntax in .pd/config.yaml' }],
77
+ };
78
+ }
79
+ // 4) Validate via core (ERR-001, ERR-005: no `as` bypasses)
80
+ const validationResult = validatePdConfig(parsed);
81
+ if (!validationResult.ok) {
82
+ const effective = computeEffectivePdConfig(null);
83
+ return {
84
+ ok: false,
85
+ effective,
86
+ source: 'malformed',
87
+ configPath,
88
+ warnings: [],
89
+ errors: validationResult.errors.map(e => ({
90
+ path: e.path,
91
+ reason: e.reason,
92
+ nextAction: e.nextAction,
93
+ })),
94
+ };
95
+ }
96
+ // 5) Compute effective config
97
+ const effective = computeEffectivePdConfig(validationResult.value);
98
+ return {
99
+ ok: true,
100
+ effective,
101
+ source: 'user_config',
102
+ configPath,
103
+ warnings: effective.warnings,
104
+ errors: [],
105
+ };
106
+ }
107
+ // ── Feature Flag from Config ─────────────────────────────────────────────────
108
+ /**
109
+ * Get a single feature flag's enabled state from .pd/config.yaml.
110
+ * Replaces the old `loadFeatureFlagFromWorkspace` which read .pd/feature-flags.yaml.
111
+ */
112
+ export function loadFeatureFlagFromConfig(workspaceDir, flagId, logger) {
113
+ const result = loadPdConfigForPlugin(workspaceDir);
114
+ const flags = computeFeatureFlagsFromConfig(result.effective);
115
+ const flag = flags.flags[flagId];
116
+ if (!result.ok) {
117
+ logger?.warn?.(`[PD:Config] Config validation failed: ${result.errors.map(e => e.reason).join('; ')} — using defaults`);
118
+ }
119
+ return {
120
+ enabled: flag?.enabled ?? false,
121
+ source: result.source,
122
+ };
123
+ }
124
+ // ── Observer Config Resolution ───────────────────────────────────────────────
125
+ /**
126
+ * Resolve observer configuration from .pd/config.yaml.
127
+ *
128
+ * Returns structured state:
129
+ * - config_malformed: config file is invalid — no guessing, fail loud
130
+ * - disabled: observer feature flag is off OR agent.enabled=false → no start, no noisy logs
131
+ * - needs_setup: observer enabled but runtime profile missing, API key not set, or unsupported profile type
132
+ * - ready: observer enabled and fully configured (pi-ai with key present)
133
+ * - not_ready: observer enabled, API key present, but runtime availability unknown
134
+ */
135
+ export function resolveObserverConfig(workspaceDir, observerFlagId, observerAgentName, _logger) {
136
+ const result = loadPdConfigForPlugin(workspaceDir);
137
+ // 0) Malformed config → fail loud, do NOT swallow as "disabled"
138
+ if (!result.ok) {
139
+ return {
140
+ enabled: false,
141
+ readiness: 'config_malformed',
142
+ source: 'malformed',
143
+ reason: `Config validation failed: ${result.errors.map(e => e.reason).join('; ')}`,
144
+ nextAction: result.errors[0]?.nextAction ?? 'Fix .pd/config.yaml and retry',
145
+ runtimeProfileId: null,
146
+ runtimeProfileType: null,
147
+ apiKeyEnv: null,
148
+ apiKeyPresent: false,
149
+ provider: null,
150
+ model: null,
151
+ timeoutMs: null,
152
+ baseUrl: null,
153
+ configErrors: result.errors,
154
+ };
155
+ }
156
+ const config = result.effective.config;
157
+ // 1) Check if the observer feature flag is enabled
158
+ const featureFlag = config.features[observerFlagId];
159
+ if (!featureFlag || !featureFlag.enabled) {
160
+ return {
161
+ enabled: false,
162
+ readiness: 'disabled',
163
+ source: result.source,
164
+ reason: `${observerFlagId} is disabled in .pd/config.yaml`,
165
+ nextAction: `Set features.${observerFlagId}.enabled=true in .pd/config.yaml to enable`,
166
+ runtimeProfileId: null,
167
+ runtimeProfileType: null,
168
+ apiKeyEnv: null,
169
+ apiKeyPresent: false,
170
+ provider: null,
171
+ model: null,
172
+ timeoutMs: null,
173
+ baseUrl: null,
174
+ };
175
+ }
176
+ // 2) Check if the agent itself is enabled (feature flag ≠ agent.enabled)
177
+ const knownNames = INTERNAL_AGENT_NAMES;
178
+ if (!knownNames.includes(observerAgentName)) {
179
+ return {
180
+ enabled: false,
181
+ readiness: 'needs_setup',
182
+ source: result.source,
183
+ reason: `Unknown agent name '${observerAgentName}'`,
184
+ nextAction: `Use one of the known agent names: ${INTERNAL_AGENT_NAMES.join(', ')}`,
185
+ runtimeProfileId: null,
186
+ runtimeProfileType: null,
187
+ apiKeyEnv: null,
188
+ apiKeyPresent: false,
189
+ provider: null,
190
+ model: null,
191
+ timeoutMs: null,
192
+ baseUrl: null,
193
+ };
194
+ }
195
+ const agentKey = observerAgentName;
196
+ const agentConfig = config.internalAgents.agents[agentKey];
197
+ // Feature flag on but agent.enabled=false → disabled (not enabled)
198
+ if (!agentConfig || !agentConfig.enabled) {
199
+ return {
200
+ enabled: false,
201
+ readiness: 'disabled',
202
+ source: result.source,
203
+ reason: `${observerFlagId} feature flag is enabled but internalAgents.agents.${observerAgentName}.enabled is false`,
204
+ nextAction: `Set internalAgents.agents.${observerAgentName}.enabled=true in .pd/config.yaml, or disable features.${observerFlagId}`,
205
+ runtimeProfileId: null,
206
+ runtimeProfileType: null,
207
+ apiKeyEnv: null,
208
+ apiKeyPresent: false,
209
+ provider: null,
210
+ model: null,
211
+ timeoutMs: null,
212
+ baseUrl: null,
213
+ };
214
+ }
215
+ // 3) Find the agent's runtime profile
216
+ const runtimeProfileId = agentConfig.runtimeProfile ?? config.internalAgents.defaultRuntime;
217
+ const profile = config.runtimeProfiles[runtimeProfileId];
218
+ if (!profile) {
219
+ return {
220
+ enabled: true,
221
+ readiness: 'needs_setup',
222
+ source: result.source,
223
+ reason: `Runtime profile '${runtimeProfileId}' not found in .pd/config.yaml`,
224
+ nextAction: `Add runtime profile '${runtimeProfileId}' to .pd/config.yaml runtimeProfiles`,
225
+ runtimeProfileId,
226
+ runtimeProfileType: null,
227
+ apiKeyEnv: null,
228
+ apiKeyPresent: false,
229
+ provider: null,
230
+ model: null,
231
+ timeoutMs: null,
232
+ baseUrl: null,
233
+ };
234
+ }
235
+ // 4) For pi-ai profiles, check API key
236
+ if (profile.type === 'pi-ai') {
237
+ const apiKeyEnv = profile.apiKeyEnv ?? null;
238
+ const apiKeyPresent = !!apiKeyEnv && Object.prototype.hasOwnProperty.call(process.env, apiKeyEnv) && !!process.env[apiKeyEnv];
239
+ if (!apiKeyEnv) {
240
+ return {
241
+ enabled: true,
242
+ readiness: 'needs_setup',
243
+ source: result.source,
244
+ reason: `pi-ai profile '${runtimeProfileId}' missing apiKeyEnv`,
245
+ nextAction: `Add apiKeyEnv to runtime profile '${runtimeProfileId}' in .pd/config.yaml`,
246
+ runtimeProfileId,
247
+ runtimeProfileType: profile.type,
248
+ apiKeyEnv: null,
249
+ apiKeyPresent: false,
250
+ provider: profile.provider ?? null,
251
+ model: profile.model ?? null,
252
+ timeoutMs: profile.timeoutMs ?? null,
253
+ baseUrl: profile.baseUrl ?? null,
254
+ };
255
+ }
256
+ if (!apiKeyPresent) {
257
+ return {
258
+ enabled: true,
259
+ readiness: 'needs_setup',
260
+ source: result.source,
261
+ reason: `Environment variable '${apiKeyEnv}' is not set or empty`,
262
+ nextAction: `Set the environment variable '${apiKeyEnv}' with a valid API key`,
263
+ runtimeProfileId,
264
+ runtimeProfileType: profile.type,
265
+ apiKeyEnv,
266
+ apiKeyPresent: false,
267
+ provider: profile.provider ?? null,
268
+ model: profile.model ?? null,
269
+ timeoutMs: profile.timeoutMs ?? null,
270
+ baseUrl: profile.baseUrl ?? null,
271
+ };
272
+ }
273
+ // pi-ai with key present — runtime availability unknown without actual probe
274
+ return {
275
+ enabled: true,
276
+ readiness: 'not_ready',
277
+ source: result.source,
278
+ reason: `pi-ai profile configured with apiKeyEnv='${apiKeyEnv}' (key present); runtime availability unknown`,
279
+ nextAction: 'Run pd runtime probe to verify end-to-end connectivity',
280
+ runtimeProfileId,
281
+ runtimeProfileType: profile.type,
282
+ apiKeyEnv,
283
+ apiKeyPresent: true,
284
+ provider: profile.provider ?? null,
285
+ model: profile.model ?? null,
286
+ timeoutMs: profile.timeoutMs ?? null,
287
+ baseUrl: profile.baseUrl ?? null,
288
+ };
289
+ }
290
+ // 5) OpenClaw profile — CorrectionObserver does NOT support OpenClaw runtime
291
+ // Mark as needs_setup with nextAction to configure a pi-ai profile
292
+ return {
293
+ enabled: true,
294
+ readiness: 'needs_setup',
295
+ source: result.source,
296
+ reason: `OpenClaw profile '${runtimeProfileId}' is not supported for observer runtime. Observers require a pi-ai profile with an API key.`,
297
+ nextAction: `Configure a pi-ai runtime profile for ${observerAgentName} in .pd/config.yaml (e.g., add a pi-ai profile with provider, model, and apiKeyEnv)`,
298
+ runtimeProfileId,
299
+ runtimeProfileType: profile.type,
300
+ apiKeyEnv: null,
301
+ apiKeyPresent: false,
302
+ provider: profile.provider ?? null,
303
+ model: profile.model ?? null,
304
+ timeoutMs: null,
305
+ baseUrl: null,
306
+ };
307
+ }
@@ -14,5 +14,9 @@ export declare class PromptActivationReader {
14
14
  constructor(workspaceDir: string, deps?: PromptActivationReaderDeps);
15
15
  readActivatedPrinciples(): Promise<PromptActivationReaderResult>;
16
16
  private queryArtifactRow;
17
+ /**
18
+ * PRI-305/PRI-307: Load feature flags from .pd/config.yaml instead of .pd/feature-flags.yaml.
19
+ * Uses the shared plugin config loader for consistency.
20
+ */
17
21
  private loadFeatureFlags;
18
22
  }
@@ -1,12 +1,6 @@
1
- import * as path from 'path';
2
- import * as fs from 'fs';
3
- import * as yaml from 'js-yaml';
4
- import { SqliteConnection, SqliteActivationStateStore, computeEffectiveFlags, DEFAULT_FEATURE_FLAGS, filterPromptActivations, resolvePrincipleFromArtifact } from '@principles/core/runtime-v2';
1
+ import { SqliteConnection, SqliteActivationStateStore, computeFeatureFlagsFromConfig, filterPromptActivations, resolvePrincipleFromArtifact } from '@principles/core/runtime-v2';
2
+ import { loadPdConfigForPlugin } from './pd-config-loader.js';
5
3
  export { RUNTIME_V2_PRINCIPLE_BUDGET } from '@principles/core/runtime-v2';
6
- const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
7
- function isRecord(value) {
8
- return value !== null && typeof value === 'object' && !Array.isArray(value);
9
- }
10
4
  export class PromptActivationReader {
11
5
  workspaceDir;
12
6
  deps;
@@ -88,56 +82,18 @@ export class PromptActivationReader {
88
82
  throw new Error(`artifact_query_failed: artifactId=${artifactId} reason=${msg}; nextAction=check_pi_artifacts_table`);
89
83
  }
90
84
  }
85
+ /**
86
+ * PRI-305/PRI-307: Load feature flags from .pd/config.yaml instead of .pd/feature-flags.yaml.
87
+ * Uses the shared plugin config loader for consistency.
88
+ */
91
89
  loadFeatureFlags() {
92
- const configPath = path.join(this.workspaceDir, '.pd', 'feature-flags.yaml');
93
- if (!fs.existsSync(configPath)) {
94
- return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
95
- }
96
- let raw;
97
- try {
98
- raw = fs.readFileSync(configPath, 'utf8');
99
- }
100
- catch (e) {
101
- const msg = e instanceof Error ? e.message : String(e);
102
- this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags unreadable: ${msg} — using defaults`);
103
- return computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath);
104
- }
105
- let parsed;
106
- try {
107
- parsed = yaml.load(raw, { schema: yaml.JSON_SCHEMA });
108
- }
109
- catch {
110
- this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags YAML parse error — using defaults`);
111
- return {
112
- ...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
113
- warnings: ['feature-flags.yaml: YAML parse error, using defaults'],
114
- };
115
- }
116
- if (!isRecord(parsed)) {
117
- this.deps.logger?.warn?.(`[PD:RuntimeV2] Feature flags not a mapping — using defaults`);
118
- return {
119
- ...computeEffectiveFlags({}, DEFAULT_FEATURE_FLAGS, configPath),
120
- warnings: ['feature-flags.yaml: expected a mapping, using defaults'],
121
- };
122
- }
123
- const parsedRecord = Object.create(null);
124
- const yamlWarnings = [];
125
- for (const key of Object.keys(parsed)) {
126
- if (DANGEROUS_KEYS.has(key)) {
127
- yamlWarnings.push(`feature-flags.yaml: dangerous key '${key}' rejected`);
128
- continue;
129
- }
130
- if (Object.hasOwn(parsed, key)) {
131
- parsedRecord[key] = parsed[key];
132
- }
133
- }
134
- const result = computeEffectiveFlags(parsedRecord, DEFAULT_FEATURE_FLAGS, configPath);
135
- if (yamlWarnings.length > 0) {
136
- result.warnings = [...yamlWarnings, ...result.warnings];
137
- for (const w of yamlWarnings) {
138
- this.deps.logger?.warn?.(`[PD:RuntimeV2] ${w}`);
90
+ const result = loadPdConfigForPlugin(this.workspaceDir);
91
+ const flags = computeFeatureFlagsFromConfig(result.effective);
92
+ if (!result.ok) {
93
+ for (const err of result.errors) {
94
+ this.deps.logger?.warn?.(`[PD:RuntimeV2] Config error at ${err.path}: ${err.reason}`);
139
95
  }
140
96
  }
141
- return result;
97
+ return flags;
142
98
  }
143
99
  }
@@ -7,6 +7,16 @@ export interface SurfaceGuardResult {
7
7
  violations: string[];
8
8
  warnings: string[];
9
9
  }
10
+ type LoggerLike = {
11
+ info?: (msg: string) => void;
12
+ debug?: (msg: string) => void;
13
+ };
14
+ /**
15
+ * Reset the per-process surface-guard skip log bookkeeping. Intended for tests
16
+ * that need to assert on the first-fire log without cross-test pollution.
17
+ * Not part of the production API surface; do not call from runtime code.
18
+ */
19
+ export declare function __resetSurfaceGuardSkipLogStateForTests(): void;
10
20
  export declare function checkSurfaceGuard(): SurfaceGuardResult;
11
21
  export declare function getSurfaceIdForHook(hookEvent: string, label?: string): string;
12
22
  export declare function getSurfaceIdForService(serviceName: string): string;
@@ -15,13 +25,7 @@ export declare function isSurfaceEnabled(surfaceId: string, overrides?: Record<s
15
25
  reason?: string;
16
26
  };
17
27
  export type HookHandler<E, C, R> = (event: E, ctx: C) => R | Promise<R>;
18
- export declare function guardHook<E, C, R>(surfaceId: string, logger: {
19
- info?: (msg: string) => void;
20
- debug?: (msg: string) => void;
21
- } | undefined, handler: HookHandler<E, C, R>): HookHandler<E, C, R>;
22
- export declare function guardService<T extends OpenClawPluginService>(surfaceId: string, service: T, logger?: {
23
- info?: (msg: string) => void;
24
- debug?: (msg: string) => void;
25
- }): T | null;
28
+ export declare function guardHook<E, C, R>(surfaceId: string, logger: LoggerLike | undefined, handler: HookHandler<E, C, R>): HookHandler<E, C, R>;
29
+ export declare function guardService<T extends OpenClawPluginService>(surfaceId: string, service: T, logger?: LoggerLike): T | null;
26
30
  export { PLUGIN_SURFACE_REGISTRY, validateSurfaceRegistry, getSurfacesByCategory };
27
31
  export type { PluginSurfaceEntry, MvpCategory };