@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.
- package/dist/commands/__tests__/runtime-probe-config.test.js +2 -1
- package/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -1
- package/dist/commands/__tests__/version.test.js +104 -0
- package/dist/commands/__tests__/version.test.js.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +3 -6
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/pain-retry.d.ts.map +1 -1
- package/dist/commands/pain-retry.js +3 -6
- package/dist/commands/pain-retry.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +9 -3
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-once.js +5 -11
- package/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.d.ts +14 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +67 -15
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/index.js +0 -12
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/rulehost-readiness.test.js +0 -3
- package/dist/services/__tests__/rulehost-readiness.test.js.map +1 -1
- package/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
- package/dist/services/pain-flood-simulation-runner.js +0 -4
- package/dist/services/pain-flood-simulation-runner.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts +90 -21
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +328 -24
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/dist/services/synthetic-baseline-runner.d.ts.map +1 -1
- package/dist/services/synthetic-baseline-runner.js +0 -4
- package/dist/services/synthetic-baseline-runner.js.map +1 -1
- package/dist/services/version-report.d.ts +16 -0
- package/dist/services/version-report.d.ts.map +1 -1
- package/dist/services/version-report.js +37 -2
- package/dist/services/version-report.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/__tests__/runtime-probe-config.test.ts +2 -1
- package/src/commands/__tests__/version.test.ts +110 -0
- package/src/commands/diagnose.ts +3 -6
- package/src/commands/pain-retry.ts +3 -6
- package/src/commands/runtime-activation.ts +9 -3
- package/src/commands/runtime-internalization-run-once.ts +5 -11
- package/src/commands/runtime-internalization-run-rulehost.ts +69 -15
- package/src/index.ts +0 -13
- package/src/services/__tests__/rulehost-readiness.test.ts +0 -3
- package/src/services/pain-flood-simulation-runner.ts +0 -4
- package/src/services/rulehost-pipeline-runner.ts +368 -35
- package/src/services/synthetic-baseline-runner.ts +0 -4
- package/src/services/version-report.ts +52 -2
- package/tests/commands/config-doctor.test.ts +0 -2
- package/tests/commands/pain-evidence.test.ts +1 -1
- package/tests/commands/run-rulehost-handler.test.ts +43 -0
- package/tests/commands/runtime-features.test.ts +0 -1
- package/tests/commands/runtime-internalization-run-once-language.test.ts +0 -1
- package/tests/e2e/cross-package-acceptance.test.ts +13 -3
- package/tests/services/pd-config-loader.test.ts +0 -1
- package/tests/services/rulehost-pipeline-runner.test.ts +378 -12
- package/dist/commands/runtime-internalization-context-trace.d.ts +0 -25
- package/dist/commands/runtime-internalization-context-trace.d.ts.map +0 -1
- package/dist/commands/runtime-internalization-context-trace.js +0 -374
- package/dist/commands/runtime-internalization-context-trace.js.map +0 -1
- package/src/commands/runtime-internalization-context-trace.ts +0 -445
- package/tests/bdd/context-trace.steps.ts +0 -229
- package/tests/commands/runtime-internalization-context-trace.test.ts +0 -110
|
@@ -54,6 +54,8 @@ import {
|
|
|
54
54
|
// runners already receive (EP-07: canonical resolved value, not raw input).
|
|
55
55
|
resolveOutputLanguage,
|
|
56
56
|
buildArtificerHostSemanticContext,
|
|
57
|
+
// PRI-804(a): ledger identity lookup for the source_principle_id backfill.
|
|
58
|
+
PrincipleTreeLedgerAdapter,
|
|
57
59
|
} from '@principles/core/runtime-v2';
|
|
58
60
|
import type {
|
|
59
61
|
AdversarialLoopResult,
|
|
@@ -70,23 +72,12 @@ import type {
|
|
|
70
72
|
SeedArtificerRepairParams,
|
|
71
73
|
EvaluatorValidator,
|
|
72
74
|
} from '@principles/core/runtime-v2';
|
|
73
|
-
import
|
|
75
|
+
import * as path from 'node:path';
|
|
74
76
|
import { loadPdConfig } from './pd-config-loader.js';
|
|
75
77
|
import { createEvaluatorRuntimeContext } from '@principles/host-runtime';
|
|
76
78
|
/* eslint-disable @typescript-eslint/no-use-before-define -- helpers declared after main, matching codebase convention */
|
|
77
79
|
import { compileDemoRule } from './demo-rule-compiler.js';
|
|
78
80
|
|
|
79
|
-
/**
|
|
80
|
-
* Layer 0 content-hash function for the internalization progressive
|
|
81
|
-
* disclosure (design §6.1). Core never imports `node:crypto`, so the plugin/CLI
|
|
82
|
-
* layer injects the algorithm. Consumed only when the
|
|
83
|
-
* `artifact_summary_redundancy` flag is on; harmless otherwise.
|
|
84
|
-
*
|
|
85
|
-
* Exported so other CLI command modules (e.g. `runtime-internalization-run-once`)
|
|
86
|
-
* can reuse the same algorithm without each re-importing `node:crypto`.
|
|
87
|
-
*/
|
|
88
|
-
export const contentHashFn = (input: string): string => createHash('sha256').update(input).digest('hex');
|
|
89
|
-
|
|
90
81
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
91
82
|
|
|
92
83
|
/**
|
|
@@ -191,8 +182,8 @@ export interface RuleHostPipelineStage {
|
|
|
191
182
|
* rule artifact exists and is WAITING for owner review. This is NOT owner
|
|
192
183
|
* approval — it means the candidate is ready for the owner to review.
|
|
193
184
|
* - `text_principle_only`: code-rule capability OFF (artificer or evaluator
|
|
194
|
-
* disabled). No rule artifact; a text principle artifact is produced for
|
|
195
|
-
* prompt
|
|
185
|
+
* disabled). No rule artifact; a text principle artifact is produced for the
|
|
186
|
+
* prompt channel and enqueued into the existing approval queue (PRI-804).
|
|
196
187
|
* - `generation_rejected`: pipeline failed (no dreamer task, stage failure, or
|
|
197
188
|
* evaluator rejected the candidate). No rule artifact.
|
|
198
189
|
*/
|
|
@@ -210,10 +201,10 @@ export interface RuleHostPipelineResult {
|
|
|
210
201
|
readonly principleArtifactId: string | null;
|
|
211
202
|
/**
|
|
212
203
|
* Approval ID when the candidate was auto-enqueued into the ApprovalQueue.
|
|
213
|
-
* Present
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
204
|
+
* Present for decision='candidate_ready_for_owner_review' (rule artifact,
|
|
205
|
+
* code_tool_hook channel) AND for decision='text_principle_only' (principle
|
|
206
|
+
* artifact, prompt channel — PRI-804 wiring). Null when the enqueue itself
|
|
207
|
+
* failed — check degradationReason for the structured reason.
|
|
217
208
|
*/
|
|
218
209
|
readonly approvalId: string | null;
|
|
219
210
|
/** Structured reason when decision is not candidate_ready_for_owner_review. */
|
|
@@ -331,7 +322,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
331
322
|
stages.push({ name: 'dreamer', taskId: dreamerSeedTaskId, status: 'succeeded' });
|
|
332
323
|
} else {
|
|
333
324
|
const dreamerRunner = new DreamerRunner(
|
|
334
|
-
{ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore
|
|
325
|
+
{ stateManager, runtimeAdapter: agentAdapters.dreamer, eventEmitter, validator: new DefaultDreamerValidator(), artifactStore },
|
|
335
326
|
runnerOptsFor(agentAdapters.dreamer),
|
|
336
327
|
);
|
|
337
328
|
const dreamerResult = await runStage(dreamerRunner, dreamerSeedTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -348,7 +339,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
348
339
|
const philosopherTaskId = `${correlation}-philosopher-${Date.now().toString(36)}`;
|
|
349
340
|
await createInternalizationTask(stateManager, philosopherTaskId, 'philosopher', [dreamerSeedTaskId], channel, timeoutMs);
|
|
350
341
|
const philosopherRunner = new PhilosopherRunner(
|
|
351
|
-
{ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore
|
|
342
|
+
{ stateManager, runtimeAdapter: agentAdapters.philosopher, eventEmitter, validator: new DefaultPhilosopherValidator(), artifactStore },
|
|
352
343
|
runnerOptsFor(agentAdapters.philosopher),
|
|
353
344
|
);
|
|
354
345
|
const philosopherResult = await runStage(philosopherRunner, philosopherTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -364,7 +355,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
364
355
|
const scribeTaskId = `${correlation}-scribe-${Date.now().toString(36)}`;
|
|
365
356
|
await createInternalizationTask(stateManager, scribeTaskId, 'scribe', [philosopherTaskId], channel, timeoutMs);
|
|
366
357
|
const scribeRunner = new ScribeRunner(
|
|
367
|
-
{ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore
|
|
358
|
+
{ stateManager, runtimeAdapter: agentAdapters.scribe, eventEmitter, validator: new DefaultScribeValidator(), artifactStore },
|
|
368
359
|
runnerOptsFor(agentAdapters.scribe),
|
|
369
360
|
);
|
|
370
361
|
const scribeResult = await runStage(scribeRunner, scribeTaskId, { maxStageRetries, pollIntervalMs });
|
|
@@ -375,6 +366,29 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
375
366
|
}
|
|
376
367
|
onProgress('scribe', 'succeeded');
|
|
377
368
|
|
|
369
|
+
// EP002-R4 follow-up #2 + Owner Decision Experience v1 Phase A: backfill
|
|
370
|
+
// the LEDGER principle UUID on the scribe artifact for BOTH paths (rule +
|
|
371
|
+
// text). Downstream identity resolution (extractPrincipleId's column-first
|
|
372
|
+
// order) then binds the rule artifact, the approval grouping, and the
|
|
373
|
+
// post-approve ledger upgrade to the ledger UUID instead of falling back
|
|
374
|
+
// to principleDraft.title — the title namespace is what left rule-channel
|
|
375
|
+
// approvals stranded as title-keyed groups the Console detail page cannot
|
|
376
|
+
// reach.
|
|
377
|
+
//
|
|
378
|
+
// Phase A (SPEC §9.1): the binding outcome is STRUCTURED. An unverified
|
|
379
|
+
// binding no longer silently continues into Owner approval publication —
|
|
380
|
+
// the enqueue sites below refuse to create an approval subject for an
|
|
381
|
+
// artifact whose stable ledger identity is not established, while the
|
|
382
|
+
// generation chain and its artifacts are preserved for later replay.
|
|
383
|
+
const identityBinding = await backfillScribeIdentity({
|
|
384
|
+
stateManager, artifactStore, scribeTaskId, dreamerTaskId: dreamerSeedTaskId,
|
|
385
|
+
workspaceDir: opts.workspaceDir, now: new Date().toISOString(),
|
|
386
|
+
});
|
|
387
|
+
if (scribeIdentityNote(identityBinding) !== '') {
|
|
388
|
+
const scribeStage = stages.find((s) => s.name === 'scribe');
|
|
389
|
+
if (scribeStage) (scribeStage as { reason?: string }).reason = scribeIdentityNote(identityBinding).replace(/^; /, '');
|
|
390
|
+
}
|
|
391
|
+
|
|
378
392
|
// ── Atomic capability branching ──
|
|
379
393
|
// Per user correction (2026-06-18): ArtificerL2 + Evaluator are atomic.
|
|
380
394
|
// When OFF (or not provided), skip the adversarial loop entirely and
|
|
@@ -383,7 +397,14 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
383
397
|
if (!capabilityEnabled) {
|
|
384
398
|
stages.push({ name: 'adversarial_loop', status: 'skipped', reason: capabilityDisabledReason });
|
|
385
399
|
onProgress('adversarial_loop', 'skipped', capabilityDisabledReason);
|
|
386
|
-
|
|
400
|
+
// PRI-804: text principles enter the existing prompt-channel approval
|
|
401
|
+
// queue (Owner governance, same gate as RuleCode) instead of dead-ending.
|
|
402
|
+
const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
|
|
403
|
+
return await textPrincipleOnlyResult({
|
|
404
|
+
painId: opts.painId, stages, scribeTaskId, disabledReason: capabilityDisabledReason,
|
|
405
|
+
artifactStore, approvalStore, now: new Date().toISOString(),
|
|
406
|
+
identityBinding,
|
|
407
|
+
});
|
|
387
408
|
}
|
|
388
409
|
|
|
389
410
|
// ── Stage: adversarial loop (artificer↔evaluator) ──
|
|
@@ -429,7 +450,7 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
429
450
|
const artificerRunner = new ArtificerRunner(
|
|
430
451
|
{
|
|
431
452
|
stateManager, runtimeAdapter: capability.artificerAdapter, eventEmitter, validator: new DefaultArtificerValidator(), artifactStore,
|
|
432
|
-
behaviorExamplePack: opts.behaviorExamplePack,
|
|
453
|
+
behaviorExamplePack: opts.behaviorExamplePack,
|
|
433
454
|
},
|
|
434
455
|
{
|
|
435
456
|
...runnerOptsFor(capability.artificerAdapter),
|
|
@@ -488,8 +509,32 @@ export async function runRuleHostPipeline(opts: RuleHostPipelineOptions): Promis
|
|
|
488
509
|
// owner can review it. Without this, the pipeline produces a candidate
|
|
489
510
|
// artifact but it never enters the approval queue — the production chain
|
|
490
511
|
// is broken at step 2→3. Tests manually called enqueue(); production did not.
|
|
512
|
+
//
|
|
513
|
+
// Owner Decision Experience v1 Phase A (SPEC §9.1.4/§9.1.6): creating an
|
|
514
|
+
// Owner approval subject is a governance publication — it requires a
|
|
515
|
+
// verified stable ledger identity. When the chain's candidate could not be
|
|
516
|
+
// bound to exactly one existing ledger principle, the rule artifact must
|
|
517
|
+
// NOT enter the approvable set, even though the artifact itself (and every
|
|
518
|
+
// intermediate artifact) is preserved. Recovery is replay: after the
|
|
519
|
+
// ledger/identity repair, re-running the pipeline re-attempts the binding
|
|
520
|
+
// idempotently and then publishes.
|
|
491
521
|
let approvalId: string | null = null;
|
|
492
522
|
if (pipelineDecision === 'candidate_ready_for_owner_review' && loopResult.ruleArtifactId) {
|
|
523
|
+
const ruleIdentity = await resolveRuleArtifactIdentity(artifactStore, loopResult.ruleArtifactId, identityBinding);
|
|
524
|
+
if (ruleIdentity === null) {
|
|
525
|
+
const reasonCode = scribeIdentityReasonCode(identityBinding);
|
|
526
|
+
return {
|
|
527
|
+
decision: pipelineDecision,
|
|
528
|
+
painId: opts.painId,
|
|
529
|
+
stages,
|
|
530
|
+
scribeTaskId,
|
|
531
|
+
adversarialLoop: loopResult,
|
|
532
|
+
ruleArtifactId: loopResult.ruleArtifactId,
|
|
533
|
+
principleArtifactId: loopResult.principleArtifactId,
|
|
534
|
+
approvalId: null,
|
|
535
|
+
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.`,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
493
538
|
try {
|
|
494
539
|
const approvalStore = new SqliteApprovalQueueStore(stateManager.connection);
|
|
495
540
|
const riskLevel = getChannelRiskLevel(channel);
|
|
@@ -657,11 +702,6 @@ export interface CreateEvaluatorRunnerDepsInputs {
|
|
|
657
702
|
readonly artifactStore: PIArtifactStore;
|
|
658
703
|
/** Workspace directory containing `.pd/config.yaml` (flag source). */
|
|
659
704
|
readonly workspaceDir: string;
|
|
660
|
-
/**
|
|
661
|
-
* Layer 0 content-hash function (design §6.1). Optional — defaults to the
|
|
662
|
-
* module-level sha256-hex `contentHashFn`. Allow override for tests.
|
|
663
|
-
*/
|
|
664
|
-
readonly contentHashFn?: (input: string) => string;
|
|
665
705
|
}
|
|
666
706
|
|
|
667
707
|
/**
|
|
@@ -686,10 +726,6 @@ export function createEvaluatorRunnerDeps(inputs: CreateEvaluatorRunnerDepsInput
|
|
|
686
726
|
eventEmitter,
|
|
687
727
|
validator,
|
|
688
728
|
artifactStore,
|
|
689
|
-
// Layer 0 (design §6.1): inject the content-hash function so the evaluator
|
|
690
|
-
// writer can attach a `predecessorSummary.contentHash` for staleness
|
|
691
|
-
// detection. Defaults to the module-level sha256-hex constant.
|
|
692
|
-
contentHashFn: inputs.contentHashFn ?? contentHashFn,
|
|
693
729
|
isRepairLoopEnabled: (): boolean => {
|
|
694
730
|
// rc-9: never throw on malformed config — fail safe to false so the
|
|
695
731
|
// legacy (non-repair) path runs. The malformed config is already
|
|
@@ -846,16 +882,313 @@ interface TextPrincipleOnlyParams {
|
|
|
846
882
|
readonly scribeTaskId: string;
|
|
847
883
|
readonly disabledReason: string;
|
|
848
884
|
readonly artifactStore: PIArtifactStore;
|
|
885
|
+
/** PRI-804: approval queue used to enqueue the text principle (prompt channel). */
|
|
886
|
+
readonly approvalStore: SqliteApprovalQueueStore;
|
|
887
|
+
readonly now: string;
|
|
888
|
+
/** Phase A: structured identity outcome from the shared post-scribe backfill. */
|
|
889
|
+
readonly identityBinding: ScribeIdentityBinding;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Owner Decision Experience v1 Phase A (SPEC §9.1) — structured ledger
|
|
894
|
+
* identity binding outcome for the internalization chain.
|
|
895
|
+
*
|
|
896
|
+
* `bound`/`already_bound` mean the scribe principle artifact carries a
|
|
897
|
+
* source_principle_id that was READ BACK from the PI store after the write
|
|
898
|
+
* and, when the chain candidate resolved, that it equals the unique existing
|
|
899
|
+
* ledger principle. Every other status is a publication blocker: the reason
|
|
900
|
+
* codes follow the SPEC's suggested vocabulary
|
|
901
|
+
* (candidate_unresolved / ledger_missing / ledger_ambiguous /
|
|
902
|
+
* identity_conflict / binding_write_failed).
|
|
903
|
+
*/
|
|
904
|
+
export type ScribeIdentityBinding =
|
|
905
|
+
| { status: 'bound'; principleId: string }
|
|
906
|
+
| { status: 'already_bound'; principleId: string }
|
|
907
|
+
| { status: 'no_principle_artifact' }
|
|
908
|
+
| { status: 'candidate_unresolved' }
|
|
909
|
+
| { status: 'ledger_missing'; candidateId: string }
|
|
910
|
+
| { status: 'ledger_ambiguous'; candidateId: string; principleIds: string[] }
|
|
911
|
+
| { status: 'identity_conflict'; existingPrincipleId: string; resolvedPrincipleId: string }
|
|
912
|
+
| { status: 'bound_target_missing'; principleId: string }
|
|
913
|
+
| { status: 'binding_write_failed'; errorMessage: string }
|
|
914
|
+
| { status: 'binding_readback_failed'; expectedPrincipleId: string; errorMessage: string };
|
|
915
|
+
|
|
916
|
+
/** Only these two statuses authorize governance publication (approval enqueue). */
|
|
917
|
+
export function isIdentityBindingVerified(
|
|
918
|
+
binding: ScribeIdentityBinding,
|
|
919
|
+
): binding is Extract<ScribeIdentityBinding, { status: 'bound' | 'already_bound' }> {
|
|
920
|
+
return binding.status === 'bound' || binding.status === 'already_bound';
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** Stable machine reason code for observability/logs (SPEC §9.1 vocabulary). */
|
|
924
|
+
export function scribeIdentityReasonCode(binding: ScribeIdentityBinding): string {
|
|
925
|
+
switch (binding.status) {
|
|
926
|
+
case 'bound':
|
|
927
|
+
case 'already_bound':
|
|
928
|
+
return 'bound';
|
|
929
|
+
case 'no_principle_artifact':
|
|
930
|
+
return 'no_principle_artifact';
|
|
931
|
+
case 'candidate_unresolved':
|
|
932
|
+
return 'candidate_unresolved';
|
|
933
|
+
case 'ledger_missing':
|
|
934
|
+
return 'ledger_missing';
|
|
935
|
+
case 'ledger_ambiguous':
|
|
936
|
+
return 'ledger_ambiguous';
|
|
937
|
+
case 'identity_conflict':
|
|
938
|
+
return 'identity_conflict';
|
|
939
|
+
case 'bound_target_missing':
|
|
940
|
+
return 'identity_conflict';
|
|
941
|
+
case 'binding_write_failed':
|
|
942
|
+
return 'binding_write_failed';
|
|
943
|
+
case 'binding_readback_failed':
|
|
944
|
+
return 'binding_write_failed';
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/** Human/telemetry note for the scribe stage; '' on a verified binding. */
|
|
949
|
+
export function scribeIdentityNote(binding: ScribeIdentityBinding): string {
|
|
950
|
+
switch (binding.status) {
|
|
951
|
+
case 'bound':
|
|
952
|
+
case 'already_bound':
|
|
953
|
+
return '';
|
|
954
|
+
case 'no_principle_artifact':
|
|
955
|
+
return '; source_principle_id_backfill_skipped: no principle artifact from scribe';
|
|
956
|
+
case 'candidate_unresolved':
|
|
957
|
+
return '; source_principle_id_backfill_skipped: no candidateId on the dreamer seed (candidate_unresolved)';
|
|
958
|
+
case 'ledger_missing':
|
|
959
|
+
return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches no ledger principle (ledger_missing)`;
|
|
960
|
+
case 'ledger_ambiguous':
|
|
961
|
+
return `; source_principle_id_backfill_skipped: candidate ${binding.candidateId} matches ${binding.principleIds.length} ledger principles (ledger_ambiguous)`;
|
|
962
|
+
case 'identity_conflict':
|
|
963
|
+
return `; source_principle_id_backfill_refused: artifact already bound to ${binding.existingPrincipleId}, chain resolves to ${binding.resolvedPrincipleId} (identity_conflict)`;
|
|
964
|
+
case 'bound_target_missing':
|
|
965
|
+
return `; source_principle_id_backfill_refused: artifact bound to ${binding.principleId} which is absent from the ledger (identity_conflict)`;
|
|
966
|
+
case 'binding_write_failed':
|
|
967
|
+
return `; source_principle_id_backfill_failed: ${binding.errorMessage}`;
|
|
968
|
+
case 'binding_readback_failed':
|
|
969
|
+
return `; source_principle_id_backfill_unverified: read-back mismatch for ${binding.expectedPrincipleId}: ${binding.errorMessage}`;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* PRI-804(a) + Phase A: resolve the UNIQUE ledger principle for the chain.
|
|
975
|
+
*
|
|
976
|
+
* The ledger principle is the identity the Console groups approvals by and the
|
|
977
|
+
* one `upgradeLedgerPrinciple` activates after approval. The only durable link
|
|
978
|
+
* from the internalization chain to the ledger is the candidateId carried in
|
|
979
|
+
* the dreamer task's diagnosticJson (set by the intake bridge) — the ledger
|
|
980
|
+
* entry stores it in derivedFromPainIds. Phase A hardens the lookup:
|
|
981
|
+
* `listForCandidate` (plural, same adapter the intake bridge uses — one
|
|
982
|
+
* authority, no second truth) rejects zero and multi matches instead of
|
|
983
|
+
* silently taking the first hit.
|
|
984
|
+
*/
|
|
985
|
+
async function resolveLedgerIdentity(
|
|
986
|
+
stateManager: RuntimeStateManager,
|
|
987
|
+
dreamerTaskId: string,
|
|
988
|
+
workspaceDir: string,
|
|
989
|
+
): Promise<ScribeIdentityBinding | { status: 'resolved'; principleId: string }> {
|
|
990
|
+
let candidateId: string | undefined;
|
|
991
|
+
try {
|
|
992
|
+
const dreamerTask = await stateManager.getTask(dreamerTaskId);
|
|
993
|
+
if (typeof dreamerTask?.diagnosticJson === 'string') {
|
|
994
|
+
const parsed: unknown = JSON.parse(dreamerTask.diagnosticJson);
|
|
995
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
996
|
+
&& Object.hasOwn(parsed, 'candidateId')) {
|
|
997
|
+
const stored = Reflect.get(parsed, 'candidateId');
|
|
998
|
+
if (typeof stored === 'string' && stored.length > 0) candidateId = stored;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
} catch {
|
|
1002
|
+
return { status: 'candidate_unresolved' }; // malformed dreamer diagnosticJson
|
|
1003
|
+
}
|
|
1004
|
+
if (!candidateId) return { status: 'candidate_unresolved' };
|
|
1005
|
+
let matches: { id: string }[];
|
|
1006
|
+
try {
|
|
1007
|
+
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
1008
|
+
matches = ledger.listForCandidate(candidateId);
|
|
1009
|
+
} catch {
|
|
1010
|
+
// Ledger unreadable cannot be distinguished from "no ledger" here — the
|
|
1011
|
+
// write attempt below will surface the concrete error; treat as missing
|
|
1012
|
+
// with the skip note (same observable class as before Phase A).
|
|
1013
|
+
return { status: 'ledger_missing', candidateId };
|
|
1014
|
+
}
|
|
1015
|
+
if (matches.length === 0) return { status: 'ledger_missing', candidateId };
|
|
1016
|
+
if (matches.length > 1) {
|
|
1017
|
+
return { status: 'ledger_ambiguous', candidateId, principleIds: matches.map((m) => m.id) };
|
|
1018
|
+
}
|
|
1019
|
+
const [unique] = matches;
|
|
1020
|
+
if (unique === undefined) return { status: 'ledger_missing', candidateId };
|
|
1021
|
+
return { status: 'resolved', principleId: unique.id };
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
interface BackfillIdentityParams {
|
|
1025
|
+
readonly stateManager: RuntimeStateManager;
|
|
1026
|
+
readonly artifactStore: PIArtifactStore;
|
|
1027
|
+
readonly scribeTaskId: string;
|
|
1028
|
+
readonly dreamerTaskId: string;
|
|
1029
|
+
readonly workspaceDir: string;
|
|
1030
|
+
readonly now: string;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* EP002-R4 follow-up #2 + Phase A: backfill the scribe principle artifact's
|
|
1035
|
+
* source_principle_id with the LEDGER principle UUID (shared by the rule and
|
|
1036
|
+
* text paths — runs right after the scribe stage succeeds). Every downstream
|
|
1037
|
+
* identity resolution reads this column FIRST (extractPrincipleId /
|
|
1038
|
+
* EvaluatorRunner.extractPrincipleIdFromArtifact), so this single write is
|
|
1039
|
+
* what binds approvals, Console grouping, ledger upgrades, and rule
|
|
1040
|
+
* artifacts to the ledger UUID instead of the title namespace.
|
|
1041
|
+
*
|
|
1042
|
+
* Phase A contract changes vs the note-only version:
|
|
1043
|
+
* - the outcome is structured (ScribeIdentityBinding), not a free string;
|
|
1044
|
+
* - an existing binding that DISAGREES with the resolved ledger target is an
|
|
1045
|
+
* identity_conflict — never overwritten;
|
|
1046
|
+
* - a successful write is verified by reading the artifact back from the
|
|
1047
|
+
* PI store (log success alone is not binding evidence — SPEC §9.1);
|
|
1048
|
+
* - a pre-existing binding is only trusted when its target exists in the
|
|
1049
|
+
* ledger read (listForCandidate is not applicable — we check presence by
|
|
1050
|
+
* resolving through the same ledger load).
|
|
1051
|
+
*
|
|
1052
|
+
* Still never throws — identity enrichment must not break the generation
|
|
1053
|
+
* chain; it only gates the later governance publication.
|
|
1054
|
+
*/
|
|
1055
|
+
export async function backfillScribeIdentity(params: BackfillIdentityParams): Promise<ScribeIdentityBinding> {
|
|
1056
|
+
const { stateManager, artifactStore, scribeTaskId, dreamerTaskId, workspaceDir, now } = params;
|
|
1057
|
+
try {
|
|
1058
|
+
const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
|
|
1059
|
+
const principleArt = arts.find((a) => a.artifactKind === 'principle');
|
|
1060
|
+
if (!principleArt) return { status: 'no_principle_artifact' };
|
|
1061
|
+
const resolution = await resolveLedgerIdentity(stateManager, dreamerTaskId, workspaceDir);
|
|
1062
|
+
if (principleArt.sourcePrincipleId) {
|
|
1063
|
+
// Already bound (rerun). Idempotent success only when the binding is
|
|
1064
|
+
// consistent with what the chain resolves to now.
|
|
1065
|
+
if (resolution.status === 'resolved') {
|
|
1066
|
+
if (resolution.principleId === principleArt.sourcePrincipleId) {
|
|
1067
|
+
return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
status: 'identity_conflict',
|
|
1071
|
+
existingPrincipleId: principleArt.sourcePrincipleId,
|
|
1072
|
+
resolvedPrincipleId: resolution.principleId,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (resolution.status === 'ledger_ambiguous') return resolution;
|
|
1076
|
+
// Chain candidate unresolved/missing on this replay — the durable
|
|
1077
|
+
// binding still stands only if its target is a real ledger principle.
|
|
1078
|
+
const ledger = new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') });
|
|
1079
|
+
if (ledger.hasPrinciple(principleArt.sourcePrincipleId)) {
|
|
1080
|
+
return { status: 'already_bound', principleId: principleArt.sourcePrincipleId };
|
|
1081
|
+
}
|
|
1082
|
+
return { status: 'bound_target_missing', principleId: principleArt.sourcePrincipleId };
|
|
1083
|
+
}
|
|
1084
|
+
if (resolution.status !== 'resolved') return resolution;
|
|
1085
|
+
const ledgerPrincipleId = resolution.principleId;
|
|
1086
|
+
await artifactStore.upsertArtifact({ ...principleArt, sourcePrincipleId: ledgerPrincipleId, updatedAt: now });
|
|
1087
|
+
// Read-back verification: the PI store must return the bound ID.
|
|
1088
|
+
try {
|
|
1089
|
+
const readBack = await artifactStore.getArtifactById(principleArt.artifactId);
|
|
1090
|
+
if (readBack?.sourcePrincipleId !== ledgerPrincipleId) {
|
|
1091
|
+
return {
|
|
1092
|
+
status: 'binding_readback_failed',
|
|
1093
|
+
expectedPrincipleId: ledgerPrincipleId,
|
|
1094
|
+
errorMessage: `read-back sourcePrincipleId=${readBack?.sourcePrincipleId ?? 'null'}`,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
} catch (readErr: unknown) {
|
|
1098
|
+
const msg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
1099
|
+
return { status: 'binding_readback_failed', expectedPrincipleId: ledgerPrincipleId, errorMessage: msg };
|
|
1100
|
+
}
|
|
1101
|
+
return { status: 'bound', principleId: ledgerPrincipleId };
|
|
1102
|
+
} catch (err: unknown) {
|
|
1103
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1104
|
+
return { status: 'binding_write_failed', errorMessage: msg };
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Phase A (SPEC §9.1.6): before the rule artifact enters the approvable set,
|
|
1110
|
+
* verify the chain's ledger identity is VERIFIED and the rule artifact carries
|
|
1111
|
+
* exactly that binding. A chain without a verified unique ledger target must
|
|
1112
|
+
* not publish, even if the artifact itself carries some id (e.g. a legacy
|
|
1113
|
+
* title-namespace value the evaluator would have echoed). Returns the
|
|
1114
|
+
* verified principle id, or null when publication must be refused.
|
|
1115
|
+
*/
|
|
1116
|
+
async function resolveRuleArtifactIdentity(
|
|
1117
|
+
artifactStore: PIArtifactStore,
|
|
1118
|
+
ruleArtifactId: string,
|
|
1119
|
+
identityBinding: ScribeIdentityBinding,
|
|
1120
|
+
): Promise<string | null> {
|
|
1121
|
+
if (!isIdentityBindingVerified(identityBinding)) return null;
|
|
1122
|
+
let artifactSourcePrincipleId: string | undefined;
|
|
1123
|
+
try {
|
|
1124
|
+
const ruleArt = await artifactStore.getArtifactById(ruleArtifactId);
|
|
1125
|
+
artifactSourcePrincipleId = ruleArt?.sourcePrincipleId;
|
|
1126
|
+
} catch {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
if (artifactSourcePrincipleId === undefined || artifactSourcePrincipleId !== identityBinding.principleId) return null;
|
|
1130
|
+
return artifactSourcePrincipleId;
|
|
849
1131
|
}
|
|
850
1132
|
|
|
851
1133
|
async function textPrincipleOnlyResult(
|
|
852
1134
|
params: TextPrincipleOnlyParams,
|
|
853
1135
|
): Promise<RuleHostPipelineResult> {
|
|
854
|
-
const { painId, stages, scribeTaskId, disabledReason, artifactStore } = params;
|
|
1136
|
+
const { painId, stages, scribeTaskId, disabledReason, artifactStore, approvalStore, now, identityBinding } = params;
|
|
855
1137
|
// Look up the principle artifact produced by the scribe stage.
|
|
856
1138
|
try {
|
|
857
1139
|
const arts = await artifactStore.listBySourceTaskId(scribeTaskId);
|
|
858
1140
|
const principleArt = arts.find((a) => a.artifactKind === 'principle');
|
|
1141
|
+
// Identity backfill already ran on the shared post-scribe step
|
|
1142
|
+
// (backfillScribeIdentity) for BOTH paths; nothing to do here.
|
|
1143
|
+
// On the text path the evaluator never runs, so nothing else flips the
|
|
1144
|
+
// validation status — mark it validated here (same store API the
|
|
1145
|
+
// evaluator uses; scribe task success already implies its validator
|
|
1146
|
+
// passed — peer-runner contract: output_invalid tasks never succeed).
|
|
1147
|
+
let identityNote = '';
|
|
1148
|
+
if (principleArt && principleArt.validationStatus !== 'validated') {
|
|
1149
|
+
try {
|
|
1150
|
+
await artifactStore.updateValidationStatus(principleArt.artifactId, 'validated');
|
|
1151
|
+
} catch (err: unknown) {
|
|
1152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1153
|
+
identityNote = `; validation_status_mark_failed: ${msg}`;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// PRI-804: a text principle is a prompt-channel intervention and follows the
|
|
1157
|
+
// same Owner governance as RuleCode (Owner decision 2026-09-15): enqueue it
|
|
1158
|
+
// into the EXISTING approval queue so the Owner can approve it in Console.
|
|
1159
|
+
// Deterministic approval id (apr_prompt_<artifactId>) + INSERT OR IGNORE make
|
|
1160
|
+
// replays idempotent. Enqueue failure degrades observably (rc-9), never
|
|
1161
|
+
// throwing away the artifact.
|
|
1162
|
+
//
|
|
1163
|
+
// Phase A governance publication boundary: an enqueue is only allowed when
|
|
1164
|
+
// the artifact's ledger identity binding is VERIFIED. Unverified bindings
|
|
1165
|
+
// keep the artifact (and its validation status) but must not create an
|
|
1166
|
+
// Owner approval subject; recovery is a replay after identity repair.
|
|
1167
|
+
let approvalId: string | null = null;
|
|
1168
|
+
let enqueueNote = '';
|
|
1169
|
+
if (!principleArt) {
|
|
1170
|
+
enqueueNote = '; approval_enqueue_skipped: no principle artifact from scribe';
|
|
1171
|
+
} else if (!isIdentityBindingVerified(identityBinding)) {
|
|
1172
|
+
const reasonCode = scribeIdentityReasonCode(identityBinding);
|
|
1173
|
+
enqueueNote = `; approval_enqueue_skipped: identity_binding_unverified (${reasonCode}). Principle artifact preserved; repair the ledger identity and re-run this pipeline to publish.`;
|
|
1174
|
+
} else if (identityBinding.principleId !== principleArt.sourcePrincipleId) {
|
|
1175
|
+
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.`;
|
|
1176
|
+
} else {
|
|
1177
|
+
try {
|
|
1178
|
+
const record = await approvalStore.enqueue({
|
|
1179
|
+
artifactId: principleArt.artifactId,
|
|
1180
|
+
channel: 'prompt',
|
|
1181
|
+
riskLevel: getChannelRiskLevel('prompt'),
|
|
1182
|
+
summary: `Text principle candidate for pain ${painId}`,
|
|
1183
|
+
triggerReason: `text_principle_only: pain=${painId}, principle=${principleArt.artifactId}; code-rule capability off (${disabledReason})`,
|
|
1184
|
+
}, now);
|
|
1185
|
+
const { approvalId: enqueuedApprovalId } = record;
|
|
1186
|
+
approvalId = enqueuedApprovalId;
|
|
1187
|
+
} catch (err: unknown) {
|
|
1188
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1189
|
+
enqueueNote = `; approval_enqueue_failed: ${msg}. Manual enqueue required: pd activation dispatch --artifact-id ${principleArt.artifactId} --channel prompt`;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
859
1192
|
return {
|
|
860
1193
|
decision: 'text_principle_only',
|
|
861
1194
|
painId,
|
|
@@ -863,8 +1196,8 @@ async function textPrincipleOnlyResult(
|
|
|
863
1196
|
scribeTaskId,
|
|
864
1197
|
ruleArtifactId: null,
|
|
865
1198
|
principleArtifactId: principleArt?.artifactId ?? null,
|
|
866
|
-
approvalId
|
|
867
|
-
degradationReason: `code_rule_capability_off: ${disabledReason}`,
|
|
1199
|
+
approvalId,
|
|
1200
|
+
degradationReason: `code_rule_capability_off: ${disabledReason}${identityNote}${enqueueNote}`,
|
|
868
1201
|
};
|
|
869
1202
|
} catch (error: unknown) {
|
|
870
1203
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -21,7 +21,6 @@ import { auditCandidateLedgerConsistency } from '@principles/core/runtime-v2';
|
|
|
21
21
|
import { OperatorHealthReadModel } from '@principles/core/runtime-v2';
|
|
22
22
|
import { createInternalizationQueueReadModel } from '@principles/core/runtime-v2';
|
|
23
23
|
import { createPITaskDiagnosticJson } from '@principles/core/runtime-v2';
|
|
24
|
-
import { contentHashFn } from './rulehost-pipeline-runner.js';
|
|
25
24
|
import {
|
|
26
25
|
computeOverallStatus,
|
|
27
26
|
boundedEvidence,
|
|
@@ -149,7 +148,6 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
|
|
|
149
148
|
artifactStore: stateManager.piArtifactStore,
|
|
150
149
|
validator: new DefaultDiagRootCauseValidator(),
|
|
151
150
|
contextAssembler,
|
|
152
|
-
contentHashFn,
|
|
153
151
|
},
|
|
154
152
|
{
|
|
155
153
|
owner: 'synthetic-baseline',
|
|
@@ -166,7 +164,6 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
|
|
|
166
164
|
eventEmitter,
|
|
167
165
|
artifactStore: stateManager.piArtifactStore,
|
|
168
166
|
validator: new DefaultDiagDistillerValidator(),
|
|
169
|
-
contentHashFn,
|
|
170
167
|
},
|
|
171
168
|
{
|
|
172
169
|
owner: 'synthetic-baseline',
|
|
@@ -183,7 +180,6 @@ export async function runSyntheticBaseline(opts: SyntheticBaselineRunnerOptions)
|
|
|
183
180
|
eventEmitter,
|
|
184
181
|
artifactStore: stateManager.piArtifactStore,
|
|
185
182
|
committer,
|
|
186
|
-
contentHashFn,
|
|
187
183
|
},
|
|
188
184
|
{
|
|
189
185
|
owner: 'synthetic-baseline',
|
|
@@ -19,10 +19,27 @@ import * as path from 'node:path';
|
|
|
19
19
|
|
|
20
20
|
export type VersionReportSource = 'official-installer' | 'official-legacy-overlay' | 'unknown';
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Where the component versions in a report were read from (SPEC §12, additive
|
|
24
|
+
* field). The installed canonical runtime is the live truth
|
|
25
|
+
* (`~/.pd/runtime/<component>/package.json`); the release cache under
|
|
26
|
+
* `~/.pd/releases/<id>/` remains the fallback for layouts without a runtime
|
|
27
|
+
* directory, and the legacy overlay reports its own bundle.
|
|
28
|
+
*/
|
|
29
|
+
export type VersionComponentsSource = 'runtime' | 'release-cached' | 'legacy-overlay';
|
|
30
|
+
|
|
22
31
|
export interface VersionReport {
|
|
23
32
|
readonly productVersion: string;
|
|
24
33
|
readonly releaseId: string;
|
|
25
34
|
readonly components: Readonly<Record<string, string>>;
|
|
35
|
+
/**
|
|
36
|
+
* Which layout the component versions were read from. Additive diagnostic
|
|
37
|
+
* ahead of the canonical SPEC §12 contract update (the authoritative copy
|
|
38
|
+
* lives in create-principles-disciple/src/update/product-identity.ts):
|
|
39
|
+
* optional so older consumers of this mirror stay compatible; the builder
|
|
40
|
+
* always emits it.
|
|
41
|
+
*/
|
|
42
|
+
readonly componentsSource?: VersionComponentsSource;
|
|
26
43
|
readonly bootstrapVersion: string;
|
|
27
44
|
readonly channel: 'stable' | 'candidate';
|
|
28
45
|
readonly source: VersionReportSource;
|
|
@@ -93,6 +110,7 @@ function buildLegacyOverlayReport(overlayDir: string, bootstrap: Record<string,
|
|
|
93
110
|
productVersion: version,
|
|
94
111
|
releaseId: '0'.repeat(64),
|
|
95
112
|
components: { plugin: version },
|
|
113
|
+
componentsSource: 'legacy-overlay',
|
|
96
114
|
bootstrapVersion: typeof bootstrapVersion === 'string' ? bootstrapVersion : 'unknown',
|
|
97
115
|
channel: 'stable',
|
|
98
116
|
source: 'official-legacy-overlay',
|
|
@@ -177,9 +195,40 @@ export function buildVersionReport(homeDir: string = os.homedir()): VersionRepor
|
|
|
177
195
|
? 'degraded'
|
|
178
196
|
: releaseMetadataMatchesActive ? 'healthy' : 'corrupt';
|
|
179
197
|
|
|
198
|
+
// The live canonical runtime is the primary component-version source; the
|
|
199
|
+
// release cache stays as the fallback for layouts without a runtime
|
|
200
|
+
// directory (and per component when the runtime copy is absent). Component
|
|
201
|
+
// versions are read, never judged: independently versioned components are
|
|
202
|
+
// not corruption, and health remains bound to the active-identity
|
|
203
|
+
// verification above (a runtime full of readable package versions alone
|
|
204
|
+
// never makes the report healthy). A malformed component manifest degrades
|
|
205
|
+
// to an omitted component — this command exists to diagnose broken
|
|
206
|
+
// installs, so one corrupt manifest must not kill the whole report.
|
|
207
|
+
const runtimeDir = path.join(pdHome, 'runtime');
|
|
208
|
+
const componentsBaseDir = fs.existsSync(runtimeDir) ? runtimeDir : releaseDir;
|
|
209
|
+
const componentsSource: VersionReport['componentsSource'] = componentsBaseDir === runtimeDir
|
|
210
|
+
? 'runtime'
|
|
211
|
+
: 'release-cached';
|
|
212
|
+
const readComponentManifest = (component: string): Record<string, unknown> | null => {
|
|
213
|
+
try {
|
|
214
|
+
return readJsonIfPresent(path.join(componentsBaseDir, component, 'package.json'));
|
|
215
|
+
} catch {
|
|
216
|
+
return null; // malformed live manifest: omit the component, keep the report
|
|
217
|
+
}
|
|
218
|
+
};
|
|
180
219
|
const components: Record<string, string> = {};
|
|
181
|
-
for (const component of ['plugin', 'console', 'core', 'pd-cli', 'host-runtime', 'install-layout']) {
|
|
182
|
-
|
|
220
|
+
for (const component of ['plugin', 'console', 'core', 'pd-cli', 'host-runtime', 'install-layout', 'codex-adapter', 'release-manager']) {
|
|
221
|
+
let manifest = readComponentManifest(component);
|
|
222
|
+
if (manifest === null && componentsBaseDir === runtimeDir) {
|
|
223
|
+
// Per-component fallback: a partially populated runtime dir (partial
|
|
224
|
+
// sync) must not silently drop components that the release cache can
|
|
225
|
+
// still report.
|
|
226
|
+
try {
|
|
227
|
+
manifest = readJsonIfPresent(path.join(releaseDir, component, 'package.json'));
|
|
228
|
+
} catch {
|
|
229
|
+
manifest = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
183
232
|
const version = manifest?.version;
|
|
184
233
|
if (typeof version === 'string') {
|
|
185
234
|
components[component] = version;
|
|
@@ -193,6 +242,7 @@ export function buildVersionReport(homeDir: string = os.homedir()): VersionRepor
|
|
|
193
242
|
productVersion,
|
|
194
243
|
releaseId,
|
|
195
244
|
components,
|
|
245
|
+
componentsSource,
|
|
196
246
|
bootstrapVersion: typeof bootstrapVersion === 'string' ? bootstrapVersion : 'unknown',
|
|
197
247
|
channel: channelValue === 'candidate' ? 'candidate' : 'stable',
|
|
198
248
|
source: 'official-installer',
|
|
@@ -61,7 +61,6 @@ function makeValidConfigYaml(): string {
|
|
|
61
61
|
evaluator: { enabled: false },
|
|
62
62
|
rolloutReviewer: { enabled: false },
|
|
63
63
|
correctionObserver: { enabled: false },
|
|
64
|
-
empathyObserver: { enabled: false },
|
|
65
64
|
},
|
|
66
65
|
},
|
|
67
66
|
ui: { diagnostics: { mode: 'simple' } },
|
|
@@ -127,7 +126,6 @@ describe('Internal agent runtime binding readiness', () => {
|
|
|
127
126
|
evaluator: { enabled: false },
|
|
128
127
|
rolloutReviewer: { enabled: false },
|
|
129
128
|
correctionObserver: { enabled: false },
|
|
130
|
-
empathyObserver: { enabled: false },
|
|
131
129
|
},
|
|
132
130
|
},
|
|
133
131
|
});
|
|
@@ -130,7 +130,7 @@ describe('parseTriggerDecisions', () => {
|
|
|
130
130
|
// ── Integration: Real Log File Fixture ─────────────────────────────────────
|
|
131
131
|
|
|
132
132
|
describe('real SYSTEM log fixture', () => {
|
|
133
|
-
const tmpDir = path.join(os.tmpdir(),
|
|
133
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-test-evidence-'));
|
|
134
134
|
const logDir = path.join(tmpDir, 'memory', 'logs');
|
|
135
135
|
|
|
136
136
|
beforeEach(() => {
|