@principles/pd-cli 1.152.7 → 1.152.8

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 (67) hide show
  1. package/dist/commands/__tests__/runtime-probe-config.test.js +2 -1
  2. package/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -1
  3. package/dist/commands/__tests__/version.test.js +104 -0
  4. package/dist/commands/__tests__/version.test.js.map +1 -1
  5. package/dist/commands/diagnose.d.ts.map +1 -1
  6. package/dist/commands/diagnose.js +3 -6
  7. package/dist/commands/diagnose.js.map +1 -1
  8. package/dist/commands/pain-retry.d.ts.map +1 -1
  9. package/dist/commands/pain-retry.js +3 -6
  10. package/dist/commands/pain-retry.js.map +1 -1
  11. package/dist/commands/runtime-activation.d.ts.map +1 -1
  12. package/dist/commands/runtime-activation.js +9 -3
  13. package/dist/commands/runtime-activation.js.map +1 -1
  14. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  15. package/dist/commands/runtime-internalization-run-once.js +5 -11
  16. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  17. package/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
  18. package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
  19. package/dist/commands/runtime-internalization-run-rulehost.js +67 -15
  20. package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
  21. package/dist/index.js +0 -12
  22. package/dist/index.js.map +1 -1
  23. package/dist/services/__tests__/rulehost-readiness.test.js +0 -3
  24. package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
  25. package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
  26. package/dist/services/pain-flood-simulation-runner.js +0 -4
  27. package/dist/services/pain-flood-simulation-runner.js.map +1 -1
  28. package/dist/services/rulehost-pipeline-runner.d.ts +90 -21
  29. package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  30. package/dist/services/rulehost-pipeline-runner.js +328 -24
  31. package/dist/services/rulehost-pipeline-runner.js.map +1 -1
  32. package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
  33. package/dist/services/synthetic-baseline-runner.js +0 -4
  34. package/dist/services/synthetic-baseline-runner.js.map +1 -1
  35. package/dist/services/version-report.d.ts +16 -0
  36. package/dist/services/version-report.d.ts.map +1 -1
  37. package/dist/services/version-report.js +37 -2
  38. package/dist/services/version-report.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/commands/__tests__/runtime-probe-config.test.ts +2 -1
  41. package/src/commands/__tests__/version.test.ts +110 -0
  42. package/src/commands/diagnose.ts +3 -6
  43. package/src/commands/pain-retry.ts +3 -6
  44. package/src/commands/runtime-activation.ts +9 -3
  45. package/src/commands/runtime-internalization-run-once.ts +5 -11
  46. package/src/commands/runtime-internalization-run-rulehost.ts +69 -15
  47. package/src/index.ts +0 -13
  48. package/src/services/__tests__/rulehost-readiness.test.ts +0 -3
  49. package/src/services/pain-flood-simulation-runner.ts +0 -4
  50. package/src/services/rulehost-pipeline-runner.ts +368 -35
  51. package/src/services/synthetic-baseline-runner.ts +0 -4
  52. package/src/services/version-report.ts +52 -2
  53. package/tests/commands/config-doctor.test.ts +0 -2
  54. package/tests/commands/pain-evidence.test.ts +1 -1
  55. package/tests/commands/run-rulehost-handler.test.ts +43 -0
  56. package/tests/commands/runtime-features.test.ts +0 -1
  57. package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -1
  58. package/tests/e2e/cross-package-acceptance.test.ts +13 -3
  59. package/tests/services/pd-config-loader.test.ts +0 -1
  60. package/tests/services/rulehost-pipeline-runner.test.ts +378 -12
  61. package/dist/commands/runtime-internalization-context-trace.d.ts +0 -25
  62. package/dist/commands/runtime-internalization-context-trace.d.ts.map +0 -1
  63. package/dist/commands/runtime-internalization-context-trace.js +0 -374
  64. package/dist/commands/runtime-internalization-context-trace.js.map +0 -1
  65. package/src/commands/runtime-internalization-context-trace.ts +0 -445
  66. package/tests/bdd/context-trace.steps.ts +0 -229
  67. package/tests/commands/runtime-internalization-context-trace.test.ts +0 -110
@@ -26,16 +26,6 @@
26
26
  */
27
27
  import { RuntimeStateManager, StoreEventEmitter } from '@principles/core/runtime-v2';
28
28
  import type { AdversarialLoopResult, PDRuntimeAdapter, RefinerRuleHostGateDeps, PIArtifactStore, BehaviorExamplePack, EvaluatorRunnerDeps, EvaluatorValidator } from '@principles/core/runtime-v2';
29
- /**
30
- * Layer 0 content-hash function for the internalization progressive
31
- * disclosure (design §6.1). Core never imports `node:crypto`, so the plugin/CLI
32
- * layer injects the algorithm. Consumed only when the
33
- * `artifact_summary_redundancy` flag is on; harmless otherwise.
34
- *
35
- * Exported so other CLI command modules (e.g. `runtime-internalization-run-once`)
36
- * can reuse the same algorithm without each re-importing `node:crypto`.
37
- */
38
- export declare const contentHashFn: (input: string) => string;
39
29
  /**
40
30
  * Code-rule capability (atomic: ArtificerL2 + Evaluator).
41
31
  *
@@ -133,8 +123,8 @@ export interface RuleHostPipelineStage {
133
123
  * rule artifact exists and is WAITING for owner review. This is NOT owner
134
124
  * approval — it means the candidate is ready for the owner to review.
135
125
  * - `text_principle_only`: code-rule capability OFF (artificer or evaluator
136
- * disabled). No rule artifact; a text principle artifact is produced for
137
- * prompt-channel fallback.
126
+ * disabled). No rule artifact; a text principle artifact is produced for the
127
+ * prompt channel and enqueued into the existing approval queue (PRI-804).
138
128
  * - `generation_rejected`: pipeline failed (no dreamer task, stage failure, or
139
129
  * evaluator rejected the candidate). No rule artifact.
140
130
  */
@@ -152,10 +142,10 @@ export interface RuleHostPipelineResult {
152
142
  readonly principleArtifactId: string | null;
153
143
  /**
154
144
  * Approval ID when the candidate was auto-enqueued into the ApprovalQueue.
155
- * Present when decision='candidate_ready_for_owner_review' and the pipeline
156
- * successfully enqueued the candidate for owner review (P1 #1 fix).
157
- * Null when the candidate was not enqueued (text_principle_only, rejected,
158
- * or enqueue failed — check degradationReason for details).
145
+ * Present for decision='candidate_ready_for_owner_review' (rule artifact,
146
+ * code_tool_hook channel) AND for decision='text_principle_only' (principle
147
+ * artifact, prompt channel — PRI-804 wiring). Null when the enqueue itself
148
+ * failed — check degradationReason for the structured reason.
159
149
  */
160
150
  readonly approvalId: string | null;
161
151
  /** Structured reason when decision is not candidate_ready_for_owner_review. */
@@ -183,11 +173,6 @@ export interface CreateEvaluatorRunnerDepsInputs {
183
173
  readonly artifactStore: PIArtifactStore;
184
174
  /** Workspace directory containing `.pd/config.yaml` (flag source). */
185
175
  readonly workspaceDir: string;
186
- /**
187
- * Layer 0 content-hash function (design §6.1). Optional — defaults to the
188
- * module-level sha256-hex `contentHashFn`. Allow override for tests.
189
- */
190
- readonly contentHashFn?: (input: string) => string;
191
176
  }
192
177
  /**
193
178
  * Build the full `EvaluatorRunnerDeps` for CLI production paths, including the
@@ -204,4 +189,88 @@ export interface CreateEvaluatorRunnerDepsInputs {
204
189
  * Returns the newly created repair task's ID (UUIDv4, rc-7: fresh per call).
205
190
  */
206
191
  export declare function createEvaluatorRunnerDeps(inputs: CreateEvaluatorRunnerDepsInputs): EvaluatorRunnerDeps;
192
+ /**
193
+ * Owner Decision Experience v1 Phase A (SPEC §9.1) — structured ledger
194
+ * identity binding outcome for the internalization chain.
195
+ *
196
+ * `bound`/`already_bound` mean the scribe principle artifact carries a
197
+ * source_principle_id that was READ BACK from the PI store after the write
198
+ * and, when the chain candidate resolved, that it equals the unique existing
199
+ * ledger principle. Every other status is a publication blocker: the reason
200
+ * codes follow the SPEC's suggested vocabulary
201
+ * (candidate_unresolved / ledger_missing / ledger_ambiguous /
202
+ * identity_conflict / binding_write_failed).
203
+ */
204
+ export type ScribeIdentityBinding = {
205
+ status: 'bound';
206
+ principleId: string;
207
+ } | {
208
+ status: 'already_bound';
209
+ principleId: string;
210
+ } | {
211
+ status: 'no_principle_artifact';
212
+ } | {
213
+ status: 'candidate_unresolved';
214
+ } | {
215
+ status: 'ledger_missing';
216
+ candidateId: string;
217
+ } | {
218
+ status: 'ledger_ambiguous';
219
+ candidateId: string;
220
+ principleIds: string[];
221
+ } | {
222
+ status: 'identity_conflict';
223
+ existingPrincipleId: string;
224
+ resolvedPrincipleId: string;
225
+ } | {
226
+ status: 'bound_target_missing';
227
+ principleId: string;
228
+ } | {
229
+ status: 'binding_write_failed';
230
+ errorMessage: string;
231
+ } | {
232
+ status: 'binding_readback_failed';
233
+ expectedPrincipleId: string;
234
+ errorMessage: string;
235
+ };
236
+ /** Only these two statuses authorize governance publication (approval enqueue). */
237
+ export declare function isIdentityBindingVerified(binding: ScribeIdentityBinding): binding is Extract<ScribeIdentityBinding, {
238
+ status: 'bound' | 'already_bound';
239
+ }>;
240
+ /** Stable machine reason code for observability/logs (SPEC §9.1 vocabulary). */
241
+ export declare function scribeIdentityReasonCode(binding: ScribeIdentityBinding): string;
242
+ /** Human/telemetry note for the scribe stage; '' on a verified binding. */
243
+ export declare function scribeIdentityNote(binding: ScribeIdentityBinding): string;
244
+ interface BackfillIdentityParams {
245
+ readonly stateManager: RuntimeStateManager;
246
+ readonly artifactStore: PIArtifactStore;
247
+ readonly scribeTaskId: string;
248
+ readonly dreamerTaskId: string;
249
+ readonly workspaceDir: string;
250
+ readonly now: string;
251
+ }
252
+ /**
253
+ * EP002-R4 follow-up #2 + Phase A: backfill the scribe principle artifact's
254
+ * source_principle_id with the LEDGER principle UUID (shared by the rule and
255
+ * text paths — runs right after the scribe stage succeeds). Every downstream
256
+ * identity resolution reads this column FIRST (extractPrincipleId /
257
+ * EvaluatorRunner.extractPrincipleIdFromArtifact), so this single write is
258
+ * what binds approvals, Console grouping, ledger upgrades, and rule
259
+ * artifacts to the ledger UUID instead of the title namespace.
260
+ *
261
+ * Phase A contract changes vs the note-only version:
262
+ * - the outcome is structured (ScribeIdentityBinding), not a free string;
263
+ * - an existing binding that DISAGREES with the resolved ledger target is an
264
+ * identity_conflict — never overwritten;
265
+ * - a successful write is verified by reading the artifact back from the
266
+ * PI store (log success alone is not binding evidence — SPEC §9.1);
267
+ * - a pre-existing binding is only trusted when its target exists in the
268
+ * ledger read (listForCandidate is not applicable — we check presence by
269
+ * resolving through the same ledger load).
270
+ *
271
+ * Still never throws — identity enrichment must not break the generation
272
+ * chain; it only gates the later governance publication.
273
+ */
274
+ export declare function backfillScribeIdentity(params: BackfillIdentityParams): Promise<ScribeIdentityBinding>;
275
+ export {};
207
276
  //# sourceMappingURL=rulehost-pipeline-runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rulehost-pipeline-runner.d.ts","sourceRoot":"","sources":["../../src/services/rulehost-pipeline-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rulehost-pipeline-runner.d.ts","sourceRoot":"","sources":["../../src/services/rulehost-pipeline-runner.ts"],"names":[],"mappings":"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"}
@@ -31,22 +31,14 @@ import { RuntimeStateManager, StoreEventEmitter, DreamerRunner, DefaultDreamerVa
31
31
  computeFeatureFlagsFromConfig, isFeatureEnabled,
32
32
  // PRI-714: resolve outputLanguage from the same effective config the
33
33
  // runners already receive (EP-07: canonical resolved value, not raw input).
34
- resolveOutputLanguage, buildArtificerHostSemanticContext, } from '@principles/core/runtime-v2';
35
- import { createHash } from 'node:crypto';
34
+ resolveOutputLanguage, buildArtificerHostSemanticContext,
35
+ // PRI-804(a): ledger identity lookup for the source_principle_id backfill.
36
+ PrincipleTreeLedgerAdapter, } from '@principles/core/runtime-v2';
37
+ import * as path from 'node:path';
36
38
  import { loadPdConfig } from './pd-config-loader.js';
37
39
  import { createEvaluatorRuntimeContext } from '@principles/host-runtime';
38
40
  /* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after main, matching codebase convention */
39
41
  import { compileDemoRule } from './demo-rule-compiler.js';
40
- /**
41
- * Layer 0 content-hash function for the internalization progressive
42
- * disclosure (design §6.1). Core never imports `node:crypto`, so the plugin/CLI
43
- * layer injects the algorithm. Consumed only when the
44
- * `artifact_summary_redundancy` flag is on; harmless otherwise.
45
- *
46
- * Exported so other CLI command modules (e.g. `runtime-internalization-run-once`)
47
- * can reuse the same algorithm without each re-importing `node:crypto`.
48
- */
49
- export const contentHashFn = (input) => createHash('sha256').update(input).digest('hex');
50
42
  // ── gateDeps builder ─────────────────────────────────────────────────────────
51
43
  /**
52
44
  * Build the sandbox gate deps for adversarial replay.
@@ -149,7 +141,7 @@ export async function runRuleHostPipeline(opts) {
149
141
  stages.push({ name: 'dreamer', taskId: dreamerSeedTaskId, status: 'succeeded' });
150
142
  }
151
143
  else {
152
- const dreamerRunner = new DreamerRunner({ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore, contentHashFn }, runnerOptsFor(agentAdapters.dreamer));
144
+ const dreamerRunner = new DreamerRunner({ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore }, runnerOptsFor(agentAdapters.dreamer));
153
145
  const dreamerResult = await runStage(dreamerRunner, dreamerSeedTaskId, { maxStageRetries, pollIntervalMs });
154
146
  stages.push(stageFromResult('dreamer', dreamerSeedTaskId, dreamerResult));
155
147
  if (dreamerResult.status !== 'succeeded') {
@@ -162,7 +154,7 @@ export async function runRuleHostPipeline(opts) {
162
154
  onProgress('philosopher', 'start');
163
155
  const philosopherTaskId = `${correlation}-philosopher-${Date.now().toString(36)}`;
164
156
  await createInternalizationTask(stateManager, philosopherTaskId, 'philosopher', [dreamerSeedTaskId], channel, timeoutMs);
165
- const philosopherRunner = new PhilosopherRunner({ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore, contentHashFn }, runnerOptsFor(agentAdapters.philosopher));
157
+ const philosopherRunner = new PhilosopherRunner({ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore }, runnerOptsFor(agentAdapters.philosopher));
166
158
  const philosopherResult = await runStage(philosopherRunner, philosopherTaskId, { maxStageRetries, pollIntervalMs });
167
159
  stages.push(stageFromResult('philosopher', philosopherTaskId, philosopherResult));
168
160
  if (philosopherResult.status !== 'succeeded') {
@@ -174,7 +166,7 @@ export async function runRuleHostPipeline(opts) {
174
166
  onProgress('scribe', 'start');
175
167
  const scribeTaskId = `${correlation}-scribe-${Date.now().toString(36)}`;
176
168
  await createInternalizationTask(stateManager, scribeTaskId, 'scribe', [philosopherTaskId], channel, timeoutMs);
177
- const scribeRunner = new ScribeRunner({ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore, contentHashFn }, runnerOptsFor(agentAdapters.scribe));
169
+ const scribeRunner = new ScribeRunner({ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore }, runnerOptsFor(agentAdapters.scribe));
178
170
  const scribeResult = await runStage(scribeRunner, scribeTaskId, { maxStageRetries, pollIntervalMs });
179
171
  stages.push(stageFromResult('scribe', scribeTaskId, scribeResult));
180
172
  if (scribeResult.status !== 'succeeded') {
@@ -182,6 +174,29 @@ export async function runRuleHostPipeline(opts) {
182
174
  return rejectedResult(opts.painId, stages, `scribe_failed: ${scribeResult.failureReason ?? scribeResult.status}`);
183
175
  }
184
176
  onProgress('scribe', 'succeeded');
177
+ // EP002-R4 follow-up #2 + Owner Decision Experience v1 Phase A: backfill
178
+ // the LEDGER principle UUID on the scribe artifact for BOTH paths (rule +
179
+ // text). Downstream identity resolution (extractPrincipleId's column-first
180
+ // order) then binds the rule artifact, the approval grouping, and the
181
+ // post-approve ledger upgrade to the ledger UUID instead of falling back
182
+ // to principleDraft.title — the title namespace is what left rule-channel
183
+ // approvals stranded as title-keyed groups the Console detail page cannot
184
+ // reach.
185
+ //
186
+ // Phase A (SPEC §9.1): the binding outcome is STRUCTURED. An unverified
187
+ // binding no longer silently continues into Owner approval publication —
188
+ // the enqueue sites below refuse to create an approval subject for an
189
+ // artifact whose stable ledger identity is not established, while the
190
+ // generation chain and its artifacts are preserved for later replay.
191
+ const identityBinding = await backfillScribeIdentity({
192
+ stateManager, artifactStore, scribeTaskId, dreamerTaskId: dreamerSeedTaskId,
193
+ workspaceDir: opts.workspaceDir, now: new Date().toISOString(),
194
+ });
195
+ if (scribeIdentityNote(identityBinding) !== '') {
196
+ const scribeStage = stages.find((s) => s.name === 'scribe');
197
+ if (scribeStage)
198
+ scribeStage.reason = scribeIdentityNote(identityBinding).replace(/^; /, '');
199
+ }
185
200
  // ── Atomic capability branching ──
186
201
  // Per user correction (2026-06-18): ArtificerL2 + Evaluator are atomic.
187
202
  // When OFF (or not provided), skip the adversarial loop entirely and
@@ -190,7 +205,14 @@ export async function runRuleHostPipeline(opts) {
190
205
  if (!capabilityEnabled) {
191
206
  stages.push({ name: 'adversarial_loop', status: 'skipped', reason: capabilityDisabledReason });
192
207
  onProgress('adversarial_loop', 'skipped', capabilityDisabledReason);
193
- return await textPrincipleOnlyResult({ painId: opts.painId, stages, scribeTaskId, disabledReason: capabilityDisabledReason, artifactStore });
208
+ // PRI-804: text principles enter the existing prompt-channel approval
209
+ // queue (Owner governance, same gate as RuleCode) instead of dead-ending.
210
+ const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
211
+ return await textPrincipleOnlyResult({
212
+ painId: opts.painId, stages, scribeTaskId, disabledReason: capabilityDisabledReason,
213
+ artifactStore, approvalStore, now: new Date().toISOString(),
214
+ identityBinding,
215
+ });
194
216
  }
195
217
  // ── Stage: adversarial loop (artificer↔evaluator) ──
196
218
  // Capability ON: use the artificerAdapter (L2 write-test-fix) for the
@@ -234,7 +256,7 @@ export async function runRuleHostPipeline(opts) {
234
256
  const hostSemanticContext = buildArtificerHostSemanticContext(evaluatorContext.registry, evaluatorContext.hostKinds ?? []);
235
257
  const artificerRunner = new ArtificerRunner({
236
258
  stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
237
- behaviorExamplePack: opts.behaviorExamplePack, contentHashFn,
259
+ behaviorExamplePack: opts.behaviorExamplePack,
238
260
  }, {
239
261
  ...runnerOptsFor(capability.artificerAdapter),
240
262
  hostSemanticContext,
@@ -285,8 +307,32 @@ export async function runRuleHostPipeline(opts) {
285
307
  // owner can review it. Without this, the pipeline produces a candidate
286
308
  // artifact but it never enters the approval queue — the production chain
287
309
  // is broken at step 2→3. Tests manually called enqueue(); production did not.
310
+ //
311
+ // Owner Decision Experience v1 Phase A (SPEC §9.1.4/§9.1.6): creating an
312
+ // Owner approval subject is a governance publication — it requires a
313
+ // verified stable ledger identity. When the chain's candidate could not be
314
+ // bound to exactly one existing ledger principle, the rule artifact must
315
+ // NOT enter the approvable set, even though the artifact itself (and every
316
+ // intermediate artifact) is preserved. Recovery is replay: after the
317
+ // ledger/identity repair, re-running the pipeline re-attempts the binding
318
+ // idempotently and then publishes.
288
319
  let approvalId = null;
289
320
  if (pipelineDecision === 'candidate_ready_for_owner_review' && loopResult.ruleArtifactId) {
321
+ const ruleIdentity = await resolveRuleArtifactIdentity(artifactStore, loopResult.ruleArtifactId, identityBinding);
322
+ if (ruleIdentity === null) {
323
+ const reasonCode = scribeIdentityReasonCode(identityBinding);
324
+ return {
325
+ decision: pipelineDecision,
326
+ painId: opts.painId,
327
+ stages,
328
+ scribeTaskId,
329
+ adversarialLoop: loopResult,
330
+ ruleArtifactId: loopResult.ruleArtifactId,
331
+ principleArtifactId: loopResult.principleArtifactId,
332
+ approvalId: null,
333
+ degradationReason: `identity_binding_unverified: ${reasonCode}. Rule artifact NOT enqueued for Owner approval (governance publication boundary). Artifacts preserved; repair the ledger identity and re-run this pipeline to publish.`,
334
+ };
335
+ }
290
336
  try {
291
337
  const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
292
338
  const riskLevel = getChannelRiskLevel(channel);
@@ -418,10 +464,6 @@ export function createEvaluatorRunnerDeps(inputs) {
418
464
  eventEmitter,
419
465
  validator,
420
466
  artifactStore,
421
- // Layer 0 (design §6.1): inject the content-hash function so the evaluator
422
- // writer can attach a `predecessorSummary.contentHash` for staleness
423
- // detection. Defaults to the module-level sha256-hex constant.
424
- contentHashFn: inputs.contentHashFn ?? contentHashFn,
425
467
  isRepairLoopEnabled: () => {
426
468
  // rc-9: never throw on malformed config — fail safe to false so the
427
469
  // legacy (non-repair) path runs. The malformed config is already
@@ -530,12 +572,274 @@ function rejectedResult(painId, stages, degradationReason) {
530
572
  degradationReason,
531
573
  };
532
574
  }
575
+ /** Only these two statuses authorize governance publication (approval enqueue). */
576
+ export function isIdentityBindingVerified(binding) {
577
+ return binding.status === 'bound' || binding.status === 'already_bound';
578
+ }
579
+ /** Stable machine reason code for observability/logs (SPEC §9.1 vocabulary). */
580
+ export function scribeIdentityReasonCode(binding) {
581
+ switch (binding.status) {
582
+ case 'bound':
583
+ case 'already_bound':
584
+ return 'bound';
585
+ case 'no_principle_artifact':
586
+ return 'no_principle_artifact';
587
+ case 'candidate_unresolved':
588
+ return 'candidate_unresolved';
589
+ case 'ledger_missing':
590
+ return 'ledger_missing';
591
+ case 'ledger_ambiguous':
592
+ return 'ledger_ambiguous';
593
+ case 'identity_conflict':
594
+ return 'identity_conflict';
595
+ case 'bound_target_missing':
596
+ return 'identity_conflict';
597
+ case 'binding_write_failed':
598
+ return 'binding_write_failed';
599
+ case 'binding_readback_failed':
600
+ return 'binding_write_failed';
601
+ }
602
+ }
603
+ /** Human/telemetry note for the scribe stage; '' on a verified binding. */
604
+ export function scribeIdentityNote(binding) {
605
+ switch (binding.status) {
606
+ case 'bound':
607
+ case 'already_bound':
608
+ return '';
609
+ case 'no_principle_artifact':
610
+ return '; source_principle_id_backfill_skipped: no principle artifact from scribe';
611
+ case 'candidate_unresolved':
612
+ return '; source_principle_id_backfill_skipped: no candidateId on the dreamer seed (candidate_unresolved)';
613
+ case 'ledger_missing':
614
+ return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches no ledger principle (ledger_missing)`;
615
+ case 'ledger_ambiguous':
616
+ return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches ${binding.principleIds.length} ledger principles (ledger_ambiguous)`;
617
+ case 'identity_conflict':
618
+ return `; source_principle_id_backfill_refused: artifact already bound to ${binding.existingPrincipleId}, chain resolves to ${binding.resolvedPrincipleId} (identity_conflict)`;
619
+ case 'bound_target_missing':
620
+ return `; source_principle_id_backfill_refused: artifact bound to ${binding.principleId} which is absent from the ledger (identity_conflict)`;
621
+ case 'binding_write_failed':
622
+ return `; source_principle_id_backfill_failed: ${binding.errorMessage}`;
623
+ case 'binding_readback_failed':
624
+ return `; source_principle_id_backfill_unverified: read-back mismatch for ${binding.expectedPrincipleId}: ${binding.errorMessage}`;
625
+ }
626
+ }
627
+ /**
628
+ * PRI-804(a) + Phase A: resolve the UNIQUE ledger principle for the chain.
629
+ *
630
+ * The ledger principle is the identity the Console groups approvals by and the
631
+ * one `upgradeLedgerPrinciple` activates after approval. The only durable link
632
+ * from the internalization chain to the ledger is the candidateId carried in
633
+ * the dreamer task's diagnosticJson (set by the intake bridge) — the ledger
634
+ * entry stores it in derivedFromPainIds. Phase A hardens the lookup:
635
+ * `listForCandidate` (plural, same adapter the intake bridge uses — one
636
+ * authority, no second truth) rejects zero and multi matches instead of
637
+ * silently taking the first hit.
638
+ */
639
+ async function resolveLedgerIdentity(stateManager, dreamerTaskId, workspaceDir) {
640
+ let candidateId;
641
+ try {
642
+ const dreamerTask = await stateManager.getTask(dreamerTaskId);
643
+ if (typeof dreamerTask?.diagnosticJson === 'string') {
644
+ const parsed = JSON.parse(dreamerTask.diagnosticJson);
645
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
646
+ && Object.hasOwn(parsed, 'candidateId')) {
647
+ const stored = Reflect.get(parsed, 'candidateId');
648
+ if (typeof stored === 'string' && stored.length > 0)
649
+ candidateId = stored;
650
+ }
651
+ }
652
+ }
653
+ catch {
654
+ return { status: 'candidate_unresolved' }; // malformed dreamer diagnosticJson
655
+ }
656
+ if (!candidateId)
657
+ return { status: 'candidate_unresolved' };
658
+ let matches;
659
+ try {
660
+ const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
661
+ matches = ledger.listForCandidate(candidateId);
662
+ }
663
+ catch {
664
+ // Ledger unreadable cannot be distinguished from "no ledger" here — the
665
+ // write attempt below will surface the concrete error; treat as missing
666
+ // with the skip note (same observable class as before Phase A).
667
+ return { status: 'ledger_missing', candidateId };
668
+ }
669
+ if (matches.length === 0)
670
+ return { status: 'ledger_missing', candidateId };
671
+ if (matches.length > 1) {
672
+ return { status: 'ledger_ambiguous', candidateId, principleIds: matches.map((m) => m.id) };
673
+ }
674
+ const [unique] = matches;
675
+ if (unique === undefined)
676
+ return { status: 'ledger_missing', candidateId };
677
+ return { status: 'resolved', principleId: unique.id };
678
+ }
679
+ /**
680
+ * EP002-R4 follow-up #2 + Phase A: backfill the scribe principle artifact's
681
+ * source_principle_id with the LEDGER principle UUID (shared by the rule and
682
+ * text paths — runs right after the scribe stage succeeds). Every downstream
683
+ * identity resolution reads this column FIRST (extractPrincipleId /
684
+ * EvaluatorRunner.extractPrincipleIdFromArtifact), so this single write is
685
+ * what binds approvals, Console grouping, ledger upgrades, and rule
686
+ * artifacts to the ledger UUID instead of the title namespace.
687
+ *
688
+ * Phase A contract changes vs the note-only version:
689
+ * - the outcome is structured (ScribeIdentityBinding), not a free string;
690
+ * - an existing binding that DISAGREES with the resolved ledger target is an
691
+ * identity_conflict — never overwritten;
692
+ * - a successful write is verified by reading the artifact back from the
693
+ * PI store (log success alone is not binding evidence — SPEC §9.1);
694
+ * - a pre-existing binding is only trusted when its target exists in the
695
+ * ledger read (listForCandidate is not applicable — we check presence by
696
+ * resolving through the same ledger load).
697
+ *
698
+ * Still never throws — identity enrichment must not break the generation
699
+ * chain; it only gates the later governance publication.
700
+ */
701
+ export async function backfillScribeIdentity(params) {
702
+ const { stateManager, artifactStore, scribeTaskId, dreamerTaskId, workspaceDir, now } = params;
703
+ try {
704
+ const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
705
+ const principleArt = arts.find((a) => a.artifactKind === 'principle');
706
+ if (!principleArt)
707
+ return { status: 'no_principle_artifact' };
708
+ const resolution = await resolveLedgerIdentity(stateManager, dreamerTaskId, workspaceDir);
709
+ if (principleArt.sourcePrincipleId) {
710
+ // Already bound (rerun). Idempotent success only when the binding is
711
+ // consistent with what the chain resolves to now.
712
+ if (resolution.status === 'resolved') {
713
+ if (resolution.principleId === principleArt.sourcePrincipleId) {
714
+ return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
715
+ }
716
+ return {
717
+ status: 'identity_conflict',
718
+ existingPrincipleId: principleArt.sourcePrincipleId,
719
+ resolvedPrincipleId: resolution.principleId,
720
+ };
721
+ }
722
+ if (resolution.status === 'ledger_ambiguous')
723
+ return resolution;
724
+ // Chain candidate unresolved/missing on this replay — the durable
725
+ // binding still stands only if its target is a real ledger principle.
726
+ const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
727
+ if (ledger.hasPrinciple(principleArt.sourcePrincipleId)) {
728
+ return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
729
+ }
730
+ return { status: 'bound_target_missing', principleId: principleArt.sourcePrincipleId };
731
+ }
732
+ if (resolution.status !== 'resolved')
733
+ return resolution;
734
+ const ledgerPrincipleId = resolution.principleId;
735
+ await artifactStore.upsertArtifact({ ...principleArt, sourcePrincipleId: ledgerPrincipleId, updatedAt: now });
736
+ // Read-back verification: the PI store must return the bound ID.
737
+ try {
738
+ const readBack = await artifactStore.getArtifactById(principleArt.artifactId);
739
+ if (readBack?.sourcePrincipleId !== ledgerPrincipleId) {
740
+ return {
741
+ status: 'binding_readback_failed',
742
+ expectedPrincipleId: ledgerPrincipleId,
743
+ errorMessage: `read-back sourcePrincipleId=${readBack?.sourcePrincipleId ?? 'null'}`,
744
+ };
745
+ }
746
+ }
747
+ catch (readErr) {
748
+ const msg = readErr instanceof Error ? readErr.message : String(readErr);
749
+ return { status: 'binding_readback_failed', expectedPrincipleId: ledgerPrincipleId, errorMessage: msg };
750
+ }
751
+ return { status: 'bound', principleId: ledgerPrincipleId };
752
+ }
753
+ catch (err) {
754
+ const msg = err instanceof Error ? err.message : String(err);
755
+ return { status: 'binding_write_failed', errorMessage: msg };
756
+ }
757
+ }
758
+ /**
759
+ * Phase A (SPEC §9.1.6): before the rule artifact enters the approvable set,
760
+ * verify the chain's ledger identity is VERIFIED and the rule artifact carries
761
+ * exactly that binding. A chain without a verified unique ledger target must
762
+ * not publish, even if the artifact itself carries some id (e.g. a legacy
763
+ * title-namespace value the evaluator would have echoed). Returns the
764
+ * verified principle id, or null when publication must be refused.
765
+ */
766
+ async function resolveRuleArtifactIdentity(artifactStore, ruleArtifactId, identityBinding) {
767
+ if (!isIdentityBindingVerified(identityBinding))
768
+ return null;
769
+ let artifactSourcePrincipleId;
770
+ try {
771
+ const ruleArt = await artifactStore.getArtifactById(ruleArtifactId);
772
+ artifactSourcePrincipleId = ruleArt?.sourcePrincipleId;
773
+ }
774
+ catch {
775
+ return null;
776
+ }
777
+ if (artifactSourcePrincipleId === undefined || artifactSourcePrincipleId !== identityBinding.principleId)
778
+ return null;
779
+ return artifactSourcePrincipleId;
780
+ }
533
781
  async function textPrincipleOnlyResult(params) {
534
- const { painId, stages, scribeTaskId, disabledReason, artifactStore } = params;
782
+ const { painId, stages, scribeTaskId, disabledReason, artifactStore, approvalStore, now, identityBinding } = params;
535
783
  // Look up the principle artifact produced by the scribe stage.
536
784
  try {
537
785
  const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
538
786
  const principleArt = arts.find((a) => a.artifactKind === 'principle');
787
+ // Identity backfill already ran on the shared post-scribe step
788
+ // (backfillScribeIdentity) for BOTH paths; nothing to do here.
789
+ // On the text path the evaluator never runs, so nothing else flips the
790
+ // validation status — mark it validated here (same store API the
791
+ // evaluator uses; scribe task success already implies its validator
792
+ // passed — peer-runner contract: output_invalid tasks never succeed).
793
+ let identityNote = '';
794
+ if (principleArt && principleArt.validationStatus !== 'validated') {
795
+ try {
796
+ await artifactStore.updateValidationStatus(principleArt.artifactId, 'validated');
797
+ }
798
+ catch (err) {
799
+ const msg = err instanceof Error ? err.message : String(err);
800
+ identityNote = `; validation_status_mark_failed: ${msg}`;
801
+ }
802
+ }
803
+ // PRI-804: a text principle is a prompt-channel intervention and follows the
804
+ // same Owner governance as RuleCode (Owner decision 2026-09-15): enqueue it
805
+ // into the EXISTING approval queue so the Owner can approve it in Console.
806
+ // Deterministic approval id (apr_prompt_<artifactId>) + INSERT OR IGNORE make
807
+ // replays idempotent. Enqueue failure degrades observably (rc-9), never
808
+ // throwing away the artifact.
809
+ //
810
+ // Phase A governance publication boundary: an enqueue is only allowed when
811
+ // the artifact's ledger identity binding is VERIFIED. Unverified bindings
812
+ // keep the artifact (and its validation status) but must not create an
813
+ // Owner approval subject; recovery is a replay after identity repair.
814
+ let approvalId = null;
815
+ let enqueueNote = '';
816
+ if (!principleArt) {
817
+ enqueueNote = '; approval_enqueue_skipped: no principle artifact from scribe';
818
+ }
819
+ else if (!isIdentityBindingVerified(identityBinding)) {
820
+ const reasonCode = scribeIdentityReasonCode(identityBinding);
821
+ enqueueNote = `; approval_enqueue_skipped: identity_binding_unverified (${reasonCode}). Principle artifact preserved; repair the ledger identity and re-run this pipeline to publish.`;
822
+ }
823
+ else if (identityBinding.principleId !== principleArt.sourcePrincipleId) {
824
+ enqueueNote = `; approval_enqueue_skipped: identity_binding_unverified (identity_conflict: chain resolves to ${identityBinding.principleId}, artifact carries ${principleArt.sourcePrincipleId ?? 'no binding'}). Principle artifact preserved; repair the ledger identity and re-run this pipeline to publish.`;
825
+ }
826
+ else {
827
+ try {
828
+ const record = await approvalStore.enqueue({
829
+ artifactId: principleArt.artifactId,
830
+ channel: 'prompt',
831
+ riskLevel: getChannelRiskLevel('prompt'),
832
+ summary: `Text principle candidate for pain ${painId}`,
833
+ triggerReason: `text_principle_only: pain=${painId}, principle=${principleArt.artifactId}; code-rule capability off (${disabledReason})`,
834
+ }, now);
835
+ const { approvalId: enqueuedApprovalId } = record;
836
+ approvalId = enqueuedApprovalId;
837
+ }
838
+ catch (err) {
839
+ const msg = err instanceof Error ? err.message : String(err);
840
+ enqueueNote = `; approval_enqueue_failed: ${msg}. Manual enqueue required: pd activation dispatch --artifact-id ${principleArt.artifactId} --channel prompt`;
841
+ }
842
+ }
539
843
  return {
540
844
  decision: 'text_principle_only',
541
845
  painId,
@@ -543,8 +847,8 @@ async function textPrincipleOnlyResult(params) {
543
847
  scribeTaskId,
544
848
  ruleArtifactId: null,
545
849
  principleArtifactId: principleArt?.artifactId ?? null,
546
- approvalId: null,
547
- degradationReason: `code_rule_capability_off: ${disabledReason}`,
850
+ approvalId,
851
+ degradationReason: `code_rule_capability_off: ${disabledReason}${identityNote}${enqueueNote}`,
548
852
  };
549
853
  }
550
854
  catch (error) {