create-principles-disciple 1.146.8 → 1.147.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 (129) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +6 -0
  3. package/codex-adapter/dist/ingestion/explicit-owner-statement.js +6 -0
  4. package/codex-adapter/dist/pd-hook.js +5 -2
  5. package/codex-adapter/dist/worker/workspace-worker.d.ts +13 -1
  6. package/codex-adapter/dist/worker/workspace-worker.js +26 -3
  7. package/codex-adapter/package.json +1 -1
  8. package/console/dist/server/models/ExaminerEvidenceModel.d.ts +159 -0
  9. package/console/dist/server/models/ExaminerEvidenceModel.js +274 -0
  10. package/console/dist/server/models/ReceiptsConsoleModel.js +15 -1
  11. package/console/dist/server/routes/examiner-executor.d.ts +40 -4
  12. package/console/dist/server/routes/examiner-executor.js +54 -45
  13. package/console/dist/server/routes/examiner-runs.d.ts +196 -0
  14. package/console/dist/server/routes/examiner-runs.js +938 -0
  15. package/console/dist/server/routes/examiner-study-inputs.d.ts +147 -0
  16. package/console/dist/server/routes/examiner-study-inputs.js +263 -0
  17. package/console/dist/server/routes/examiner.d.ts +26 -17
  18. package/console/dist/server/routes/examiner.js +261 -12
  19. package/console/dist/types.d.ts +1 -0
  20. package/console/dist/web/assets/app.css +92 -0
  21. package/console/dist/web/assets/app.js +38 -32
  22. package/console/lab/lib/challenge-openclaw-host.mjs +17 -0
  23. package/console/lab/lib/challenge-runner.mjs +9 -0
  24. package/console/lab/lib/examiner-study.mjs +55 -0
  25. package/core/dist/runtime-v2/index.d.ts +2 -2
  26. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  27. package/core/dist/runtime-v2/index.js +1 -1
  28. package/core/dist/runtime-v2/index.js.map +1 -1
  29. package/core/dist/runtime-v2/pain-ingress-payload.d.ts +13 -1
  30. package/core/dist/runtime-v2/pain-ingress-payload.d.ts.map +1 -1
  31. package/core/dist/runtime-v2/pain-ingress-payload.js +21 -2
  32. package/core/dist/runtime-v2/pain-ingress-payload.js.map +1 -1
  33. package/core/dist/runtime-v2/pain-ingress.d.ts.map +1 -1
  34. package/core/dist/runtime-v2/pain-ingress.js +3 -0
  35. package/core/dist/runtime-v2/pain-ingress.js.map +1 -1
  36. package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts +35 -0
  37. package/core/dist/runtime-v2/signal-collector/confidence-gate.d.ts.map +1 -0
  38. package/core/dist/runtime-v2/signal-collector/confidence-gate.js +48 -0
  39. package/core/dist/runtime-v2/signal-collector/confidence-gate.js.map +1 -0
  40. package/core/dist/runtime-v2/signal-collector/index.d.ts +1 -0
  41. package/core/dist/runtime-v2/signal-collector/index.d.ts.map +1 -1
  42. package/core/dist/runtime-v2/signal-collector/index.js +1 -0
  43. package/core/dist/runtime-v2/signal-collector/index.js.map +1 -1
  44. package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts +4 -6
  45. package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -1
  46. package/core/dist/runtime-v2/signal-collector/keyword-stage.js +100 -9
  47. package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -1
  48. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.d.ts.map +1 -1
  49. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js +8 -4
  50. package/core/dist/runtime-v2/store/context/sqlite-context-assembler.js.map +1 -1
  51. package/core/package.json +1 -1
  52. package/dist/installer.d.ts.map +1 -1
  53. package/dist/installer.js +52 -1
  54. package/dist/installer.js.map +1 -1
  55. package/dist/installers/mcp-registration-conflict.d.ts +35 -0
  56. package/dist/installers/mcp-registration-conflict.d.ts.map +1 -0
  57. package/dist/installers/mcp-registration-conflict.js +559 -0
  58. package/dist/installers/mcp-registration-conflict.js.map +1 -0
  59. package/dist/installers/openclaw-host-installer.d.ts.map +1 -1
  60. package/dist/installers/openclaw-host-installer.js +20 -0
  61. package/dist/installers/openclaw-host-installer.js.map +1 -1
  62. package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
  63. package/dist/installers/workbuddy-host-installer.js +124 -2
  64. package/dist/installers/workbuddy-host-installer.js.map +1 -1
  65. package/dist/mcp-server-layout-compat.d.ts +18 -0
  66. package/dist/mcp-server-layout-compat.d.ts.map +1 -0
  67. package/dist/mcp-server-layout-compat.js +22 -0
  68. package/dist/mcp-server-layout-compat.js.map +1 -0
  69. package/host-runtime/dist/governance-signal-admission.d.ts +147 -4
  70. package/host-runtime/dist/governance-signal-admission.js +1065 -60
  71. package/host-runtime/dist/index.d.ts +2 -0
  72. package/host-runtime/dist/index.js +2 -0
  73. package/host-runtime/dist/signal-classifier.d.ts +46 -0
  74. package/host-runtime/dist/signal-classifier.js +167 -0
  75. package/host-runtime/dist/signal-stage2-cycle.d.ts +42 -0
  76. package/host-runtime/dist/signal-stage2-cycle.js +184 -0
  77. package/host-runtime/package.json +1 -1
  78. package/install-layout/dist/index.d.ts +15 -0
  79. package/install-layout/dist/index.js +10 -0
  80. package/install-layout/package.json +1 -1
  81. package/package.json +9 -7
  82. package/pd-cli/dist/commands/health-codex.d.ts.map +1 -1
  83. package/pd-cli/dist/commands/health-codex.js +12 -0
  84. package/pd-cli/dist/commands/health-codex.js.map +1 -1
  85. package/pd-cli/dist/commands/health-mcp.d.ts +74 -0
  86. package/pd-cli/dist/commands/health-mcp.d.ts.map +1 -0
  87. package/pd-cli/dist/commands/health-mcp.js +723 -0
  88. package/pd-cli/dist/commands/health-mcp.js.map +1 -0
  89. package/pd-cli/dist/commands/health.d.ts +2 -0
  90. package/pd-cli/dist/commands/health.d.ts.map +1 -1
  91. package/pd-cli/dist/commands/health.js +11 -6
  92. package/pd-cli/dist/commands/health.js.map +1 -1
  93. package/pd-mcp-server/dist/budget.d.ts +15 -0
  94. package/pd-mcp-server/dist/budget.js +19 -0
  95. package/pd-mcp-server/dist/deadline.d.ts +75 -0
  96. package/pd-mcp-server/dist/deadline.js +136 -0
  97. package/pd-mcp-server/dist/envelope.d.ts +52 -0
  98. package/pd-mcp-server/dist/envelope.js +63 -0
  99. package/pd-mcp-server/dist/index.d.ts +10 -0
  100. package/pd-mcp-server/dist/index.js +76 -0
  101. package/pd-mcp-server/dist/logging.d.ts +12 -0
  102. package/pd-mcp-server/dist/logging.js +42 -0
  103. package/pd-mcp-server/dist/server.d.ts +90 -0
  104. package/pd-mcp-server/dist/server.js +178 -0
  105. package/pd-mcp-server/dist/version.d.ts +13 -0
  106. package/pd-mcp-server/dist/version.js +13 -0
  107. package/pd-mcp-server/dist/workspace.d.ts +23 -0
  108. package/pd-mcp-server/dist/workspace.js +77 -0
  109. package/pd-mcp-server/package.json +49 -0
  110. package/plugin/dist/bundle.js +414 -412
  111. package/plugin/dist/core/mcp-binding.d.ts +41 -0
  112. package/plugin/dist/index.d.ts +33 -0
  113. package/plugin/dist/openclaw.plugin.json +10 -1
  114. package/plugin/dist/utils/workspace-resolver.d.ts +17 -0
  115. package/plugin/openclaw.plugin.json +10 -1
  116. package/plugin/package.json +2 -1
  117. package/release-manager/dist/installer.js +52 -1
  118. package/release-manager/dist/installers/mcp-registration-conflict.js +559 -0
  119. package/release-manager/dist/installers/openclaw-host-installer.js +20 -0
  120. package/release-manager/dist/installers/workbuddy-host-installer.js +124 -2
  121. package/release-manager/dist/mcp-server-layout-compat.js +22 -0
  122. package/release-manager/package.json +7 -5
  123. package/workbuddy-adapter/dist/pd-hook.js +28 -5
  124. package/workbuddy-adapter/dist/worker/workspace-worker.d.ts +6 -1
  125. package/workbuddy-adapter/dist/worker/workspace-worker.js +24 -3
  126. package/workbuddy-adapter/package.json +1 -1
  127. package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +2 -2
  128. package/workbuddy-adapter/plugin/.mcp.json +10 -0
  129. package/workbuddy-adapter/plugin/mcp/launcher.cjs +347 -0
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "productVersion": "2.8.0",
4
- "sourceCommit": "9ef8d607d51cea885249077634df3b4126919a0d"
4
+ "sourceCommit": "d3355465b090daf098581cdb4c174634fe1e5279"
5
5
  }
@@ -11,6 +11,12 @@ export type RecordOwnerStatementResult = {
11
11
  * rollout to promote from). The receipt discloses it instead of failing.
12
12
  */
13
13
  promotionDegraded?: string;
14
+ /**
15
+ * PainIngress warning when the Owner statement legitimately degraded
16
+ * (e.g. ingestion OFF → no persisted observation to use as behavior
17
+ * evidence). The Pain + task still exist; the receipt discloses the basis.
18
+ */
19
+ ingressWarning?: string;
14
20
  } | {
15
21
  status: 'rejected';
16
22
  reason: 'statement_empty' | 'statement_too_long' | 'transcript_unavailable' | 'lineage_invalid' | 'rate_limited' | 'continuation_failed';
@@ -84,6 +84,11 @@ export async function recordExplicitOwnerStatement(args) {
84
84
  if (outcome.disposition === 'rate_limited') {
85
85
  return { status: 'rejected', reason: 'rate_limited', nextAction: 'The per-root-session correction quota (5/hour) is exhausted; resubmit later — the statement was NOT recorded.' };
86
86
  }
87
+ if (outcome.disposition !== 'admitted' && outcome.disposition !== 'already_admitted') {
88
+ // An explicit Owner statement bypasses keyword detection entirely, so it
89
+ // can never legitimately land in the Stage-2 review state machine.
90
+ return { status: 'rejected', reason: 'continuation_failed', nextAction: `unexpected admission disposition for an explicit statement: ${outcome.disposition}` };
91
+ }
87
92
  // Promotion runs BEFORE the task enqueue (deliberate): the durable task is
88
93
  // the receipt's hard guarantee, and promoting first closes the window in
89
94
  // which the background worker could start the first diagnosis before the
@@ -126,5 +131,6 @@ export async function recordExplicitOwnerStatement(args) {
126
131
  disposition: outcome.disposition === 'already_admitted' ? 'already_admitted' : 'admitted',
127
132
  logicalObservationKey: candidate.logicalObservationKey,
128
133
  ...(promotionDegraded !== undefined ? { promotionDegraded } : {}),
134
+ ...(ensured.ingressWarning !== undefined ? { ingressWarning: ensured.ingressWarning } : {}),
129
135
  };
130
136
  }
@@ -362,8 +362,11 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
362
362
  const promotionNotice = receipt.promotionDegraded === 'conversation_ingestion_disabled'
363
363
  ? '会话证据提升已跳过:会话摄取未启用(按隐私边界未读取会话内容,也未写入会话观察)'
364
364
  : `会话证据提升暂不可用:${receipt.promotionDegraded}`;
365
- explicitReceiptContext = `[PD] 已收到痛苦反馈(painId=${receipt.painId}, taskId=${receipt.taskId}${receipt.disposition === 'already_admitted' ? ', 重复提交已合并为同一信号' : ''})。后台将自动诊断;请在回复中如实转述本回执,不要声称仍在等待用户输入,也不要虚构诊断结论。${receipt.promotionDegraded !== undefined ? `(注意:${promotionNotice};诊断仍会基于你提交的陈述进行。)` : ''}`;
366
- explicitDiagnostics.push(diagnostic(`owner_statement_received:${receipt.painId}:${receipt.taskId}${receipt.promotionDegraded !== undefined ? `:promotion_degraded:${receipt.promotionDegraded}` : ''}`, 'Durable enqueue complete; the workspace worker continues diagnosis in the background.'));
365
+ const ingressNotice = receipt.ingressWarning !== undefined
366
+ ? `行为证据缺失,已按 Owner 陈述降级准入:${receipt.ingressWarning}`
367
+ : undefined;
368
+ explicitReceiptContext = `[PD] 已收到痛苦反馈(painId=${receipt.painId}, taskId=${receipt.taskId}${receipt.disposition === 'already_admitted' ? ', 重复提交已合并为同一信号' : ''})。后台将自动诊断;请在回复中如实转述本回执,不要声称仍在等待用户输入,也不要虚构诊断结论。${receipt.promotionDegraded !== undefined ? `(注意:${promotionNotice};诊断仍会基于你提交的陈述进行。)` : ''}${ingressNotice !== undefined ? `(注意:${ingressNotice};诊断仍会基于你提交的陈述进行。)` : ''}`;
369
+ explicitDiagnostics.push(diagnostic(`owner_statement_received:${receipt.painId}:${receipt.taskId}${receipt.promotionDegraded !== undefined ? `:promotion_degraded:${receipt.promotionDegraded}` : ''}${receipt.ingressWarning !== undefined ? `:ingress_degraded:${receipt.ingressWarning}` : ''}`, 'Durable enqueue complete; the workspace worker continues diagnosis in the background.'));
367
370
  }
368
371
  else {
369
372
  explicitDiagnostics.push(diagnostic(`owner_statement_${receipt.reason}`, receipt.nextAction));
@@ -1,4 +1,4 @@
1
- import { type InternalizationConsumerCycleOutcome, type ConsumerCycleLogger, type ReconcileGovernanceContinuationResult } from '@principles/host-runtime';
1
+ import { type InternalizationConsumerCycleOutcome, type ConsumerCycleLogger, type ReconcileGovernanceContinuationResult, type SignalLlmClassifier, type Stage2CorrectionCycleReport } from '@principles/host-runtime';
2
2
  import { type CodexCatchUpResult } from '../ingestion/catch-up.js';
3
3
  import type { TranscriptPort } from '../ingestion/transcript-decoder.js';
4
4
  export type CodexWorkerMode = 'ready' | 'manual_action_required' | 'paused' | 'degraded';
@@ -34,6 +34,11 @@ export interface CodexWorkerCycleStepReport {
34
34
  readonly reuseReviewRequiredCandidateIds?: readonly string[];
35
35
  } | null;
36
36
  readonly downstream: InternalizationConsumerCycleOutcome | null;
37
+ /**
38
+ * Phase B Stage-2 review step. Always the LAST step so it can never starve
39
+ * the Diagnostician (bounded to ≤2 candidates / ≤60s aggregate).
40
+ */
41
+ readonly stage2?: Stage2CorrectionCycleReport;
37
42
  }
38
43
  export interface CodexWorkerCycleResult {
39
44
  readonly workspaceDir: string;
@@ -53,6 +58,13 @@ export interface CodexWorkerCycleOptions {
53
58
  readonly port?: TranscriptPort;
54
59
  /** Cap on diagnostician candidates inspected per cycle (default 5). */
55
60
  readonly diagnosticianCandidateLimit?: number;
61
+ /**
62
+ * Stage-2 classifier injection (tests). `undefined` = resolve the
63
+ * signalCollector profile from the workspace config; `null` = explicitly
64
+ * unavailable (candidates with a durable review result still settle).
65
+ */
66
+ readonly stage2Classifier?: SignalLlmClassifier | null;
67
+ readonly stage2AggregateBudgetMs?: number;
56
68
  }
57
69
  export declare function readCodexWorkerLiveness(workspaceDir: string): Record<string, unknown> | null;
58
70
  /**
@@ -28,7 +28,7 @@
28
28
  import fs from 'node:fs';
29
29
  import path from 'node:path';
30
30
  import { computeFeatureFlagsFromConfig, createRuntimeStateHandle, createPainSignalBridge, isRetryWaitBackoffElapsed, PrincipleTreeLedgerAdapter, resolveDiagnosticianCapability, } from '@principles/core/runtime-v2';
31
- import { createWorkspaceBehaviorEvidencePort, createCodexArtificerTrustedFactsPort, loadPdConfigForPlugin, loadFeatureFlagFromConfig, reconcileGovernanceContinuation, runInternalizationConsumerCycle, saveHostToolDeclaration, } from '@principles/host-runtime';
31
+ import { createWorkspaceBehaviorEvidencePort, createCodexArtificerTrustedFactsPort, loadPdConfigForPlugin, loadFeatureFlagFromConfig, reconcileGovernanceContinuation, runInternalizationConsumerCycle, runStage2CorrectionCycle, createSignalClassifierForWorkspace, saveHostToolDeclaration, } from '@principles/host-runtime';
32
32
  import { catchUpCodexIngestion } from '../ingestion/catch-up.js';
33
33
  import { CODEX_TOOL_SEMANTIC_MAPPINGS, CODEX_TOOL_SEMANTICS } from '../tool-semantics.js';
34
34
  const WORKER_OWNER = 'companion-worker';
@@ -419,6 +419,28 @@ export async function runCodexWorkspaceWorkerCycle(options) {
419
419
  // No hostToolCatalog: PD has not declared a Codex tool catalog; a wrong
420
420
  // (OpenClaw) catalog would be worse than none (PRI-630 follow-up).
421
421
  });
422
+ // Step 8 (SPEC v0.3.1 §2) — bounded Stage-2 correction review, ALWAYS last:
423
+ // the Diagnostician has already run, so this step cannot starve it. ≤2
424
+ // candidates, ≤60s aggregate, single 60s classification timeout.
425
+ const stage2Resolution = options.stage2Classifier === undefined
426
+ ? createSignalClassifierForWorkspace(workspaceDir)
427
+ : null;
428
+ const stage2Classifier = options.stage2Classifier !== undefined
429
+ ? options.stage2Classifier
430
+ : (stage2Resolution?.ok === true ? stage2Resolution.classifier : null);
431
+ const stage2ClassifierState = options.stage2Classifier === null
432
+ ? 'disabled'
433
+ : stage2Classifier === null
434
+ ? (stage2Resolution !== null && !stage2Resolution.ok && stage2Resolution.readiness === 'disabled' ? 'disabled' : 'unavailable')
435
+ : 'used';
436
+ const stage2 = await runStage2CorrectionCycle({
437
+ workspaceDir,
438
+ classifier: stage2Classifier,
439
+ classifierState: stage2ClassifierState,
440
+ ...(options.stage2AggregateBudgetMs !== undefined ? { aggregateBudgetMs: options.stage2AggregateBudgetMs } : {}),
441
+ ...(options.now !== undefined ? { now: options.now } : {}),
442
+ onEvent: emitEvent,
443
+ });
422
444
  // Aggregated mode: degraded overrides all (review P1). Any component
423
445
  // degrading — catch-up, reconciliation, diagnostician or downstream —
424
446
  // surfaces the workspace as degraded, with the highest-priority reason.
@@ -429,7 +451,8 @@ export async function runCodexWorkspaceWorkerCycle(options) {
429
451
  : diagnostician?.status === 'failed' && diagnostician.errorCategory !== 'lease_conflict' ? `diagnostician_failed:${diagnostician.message ?? 'max_attempts_exceeded'}`
430
452
  : diagnostician?.status === 'degraded' ? `diagnostician_degraded:${diagnostician.message ?? 'unknown'}`
431
453
  : downstream.skipReason === 'runtime_config_error' || downstream.skipReason === 'cycle_error' || downstream.skipReason === 'config_malformed' ? `downstream:${downstream.skipReason}`
432
- : undefined;
454
+ : !stage2.ok ? `stage2:${stage2.reason ?? 'failed'}`
455
+ : undefined;
433
456
  writeLiveness(workspaceDir, {
434
457
  runningStage: null,
435
458
  stageDeadline: null,
@@ -442,7 +465,7 @@ export async function runCodexWorkspaceWorkerCycle(options) {
442
465
  ...base,
443
466
  mode: degradedReason !== undefined ? 'degraded' : 'ready',
444
467
  ...(degradedReason !== undefined ? { reason: degradedReason, nextAction: 'Inspect the Workspace .pd/config.yaml runtime profile and the per-step report above; the worker retries automatically on the next cycle.' } : {}),
445
- report: { catchUp, reconcile, postProcessing, diagnostician, downstream },
468
+ report: { catchUp, reconcile, postProcessing, diagnostician, downstream, stage2 },
446
469
  };
447
470
  }
448
471
  // Slice D (PRI-625): the mode authority moved to host-runtime so the CLI and
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/codex-adapter",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,159 @@
1
+ import type { ReceiptEvidenceCoverage } from '@principles/core/runtime-v2';
2
+ import type { PromptInjectionRouteSummaryWire } from '../../shared/prompt-injection-contract.js';
3
+ import type { PrincipleReceipts } from './ReceiptsConsoleModel.js';
4
+ export interface ExaminerEvidenceDeliverySample {
5
+ evidenceId: string;
6
+ recordedAt: string;
7
+ sourceKind: string;
8
+ confirmation: string;
9
+ outcome: string;
10
+ targetKind: string;
11
+ }
12
+ export interface ExaminerEvidenceApplicationSample {
13
+ evidenceId: string;
14
+ recordedAt: string;
15
+ sourceKind: string;
16
+ proofMethod: string;
17
+ action: string;
18
+ }
19
+ export interface ExaminerEvidenceRecordSample {
20
+ evidenceId: string;
21
+ recordedAt: string;
22
+ sourceKind: string;
23
+ summary: string;
24
+ }
25
+ /** P1-3: per-kind pagination truth, passed through verbatim from the audit. */
26
+ export interface ExaminerEvidenceKindPage {
27
+ hasMore: boolean;
28
+ nextCursor: {
29
+ recordedAt: string;
30
+ evidenceId: string;
31
+ } | null;
32
+ }
33
+ export interface ExaminerEvidenceAuditProjection {
34
+ status: 'ok' | 'degraded';
35
+ reason?: string;
36
+ nextAction?: string;
37
+ /** P1-3: LOADED counts (the audit page window) — never presented as totals. */
38
+ counts: {
39
+ deliveries: number;
40
+ applications: number;
41
+ episodes: number;
42
+ effects: number;
43
+ outcomes: number;
44
+ };
45
+ /** P1-3: pagination truth per kind — hasMore=true means MORE records exist. */
46
+ pages: Record<'delivery' | 'application' | 'behavior_episode' | 'effect' | 'outcome', ExaminerEvidenceKindPage>;
47
+ /** True when ANY kind reports hasMore — the UI shows a "more exist" band. */
48
+ anyHasMore: boolean;
49
+ /** Delivery confirmations observed in the audit window (never promoted). */
50
+ deliveryConfirmations: {
51
+ prepared: number;
52
+ submitted: number;
53
+ hostAccepted: number;
54
+ contextPresent: number;
55
+ runtimeLoaded: number;
56
+ };
57
+ /** Application proof split — agent claims are NOT runtime verification. */
58
+ applicationProof: {
59
+ agentClaimed: number;
60
+ runtimeVerified: number;
61
+ };
62
+ unresolvedReferences: number;
63
+ capabilityDeclarations: {
64
+ hostKind: string;
65
+ capability: string;
66
+ status: string;
67
+ maxConfirmation?: string;
68
+ note?: string;
69
+ }[];
70
+ samples: {
71
+ deliveries: ExaminerEvidenceDeliverySample[];
72
+ applications: ExaminerEvidenceApplicationSample[];
73
+ episodes: ExaminerEvidenceRecordSample[];
74
+ effects: ExaminerEvidenceRecordSample[];
75
+ outcomes: ExaminerEvidenceRecordSample[];
76
+ };
77
+ asOf: string | null;
78
+ coverage: ReceiptEvidenceCoverage | null;
79
+ }
80
+ export interface ExaminerPromptActivationRow {
81
+ activationId: string;
82
+ action: string;
83
+ status: string;
84
+ activatedAt: string | null;
85
+ deactivatedAt: string | null;
86
+ enforcement?: string;
87
+ warning?: string;
88
+ }
89
+ export interface ExaminerRuleCodeRow {
90
+ activationId: string;
91
+ action: string;
92
+ mode?: string;
93
+ status: string;
94
+ enforcement?: string;
95
+ targetRef: string;
96
+ warning?: string;
97
+ }
98
+ export interface ExaminerActivationProjection {
99
+ status: 'ok' | 'degraded';
100
+ reason?: string;
101
+ nextAction?: string;
102
+ prompt: ExaminerPromptActivationRow[];
103
+ ruleCode: ExaminerRuleCodeRow[];
104
+ promptInjectionRoute?: PromptInjectionRouteSummaryWire;
105
+ }
106
+ export interface ExaminerInjectionProjection {
107
+ status: 'available' | 'degraded';
108
+ /** Present when no status could be computed at all (fail-loud, rc-9). */
109
+ globalReason?: string;
110
+ statuses: {
111
+ activationId: string;
112
+ status: string;
113
+ inCurrentWindow?: boolean;
114
+ costChars?: number;
115
+ budget?: number;
116
+ route?: string;
117
+ reason?: string;
118
+ }[];
119
+ budgetProvenance?: {
120
+ capacitySource?: string;
121
+ capacityStatus?: string;
122
+ contextWindowTokens?: number;
123
+ policyVersion?: string;
124
+ fallbackReason?: string;
125
+ };
126
+ warnings: string[];
127
+ }
128
+ export interface ExaminerPrincipleEvidence {
129
+ asOf: string;
130
+ principleId: string;
131
+ principle: {
132
+ id: string;
133
+ text: string;
134
+ status: string;
135
+ ruleCount: number;
136
+ derivedFromPainIds: string[];
137
+ evaluability: string;
138
+ painPreventedCount: number;
139
+ adherenceRate: number;
140
+ valueScore: number;
141
+ runtimeDeactivated?: boolean;
142
+ /** Asset status ≠ runtime activation — pinned for the UI (SPEC §4.1). */
143
+ ledgerStatusNote: string;
144
+ };
145
+ activation: ExaminerActivationProjection;
146
+ injection: ExaminerInjectionProjection;
147
+ receipts: PrincipleReceipts;
148
+ evidenceAudit: ExaminerEvidenceAuditProjection;
149
+ }
150
+ export declare class ExaminerEvidenceModel {
151
+ private readonly workspaceDir;
152
+ constructor(workspaceDir: string);
153
+ /**
154
+ * One principle's full production-evidence aggregate. The caller has
155
+ * already validated the principle id charset; a principle that is not in
156
+ * the ledger returns null (route answers 404 — never a fabricated entry).
157
+ */
158
+ getPrincipleEvidence(principleId: string): Promise<ExaminerPrincipleEvidence | null>;
159
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * SPEC-PD-EXAMINER-001 v0.10.1 §4/§5 — Examiner production-evidence read model.
3
+ *
4
+ * A pure DERIVED projection: composes the existing console models and the
5
+ * host-runtime production projection into ONE per-principle evidence
6
+ * aggregate for the Examiner Principle Evaluation page. It owns no storage,
7
+ * writes nothing, and creates no second evidence truth (P4) — every fact is
8
+ * read through the authority that already owns it:
9
+ *
10
+ * ledger PrinciplesConsoleModel (principle_training_state.json)
11
+ * activations ActivationsConsoleModel (state.db activations + artifacts)
12
+ * prompt inject. readPromptActivationInjectionStatuses (host-runtime,
13
+ * built on the SAME production projection the runtime uses)
14
+ * receipts ReceiptsConsoleModel (principle_applications + coverage)
15
+ * evidence audit ReceiptsConsoleModel.getEvidenceAudit
16
+ * (SqliteInterventionEvidenceStore.readAuditRelations)
17
+ *
18
+ * Honesty contract (SPEC §4.3/§5.5):
19
+ * - Projection / Submitted / Presence / Context Present / Runtime Loaded
20
+ * stay DISTINCT — nothing is promoted.
21
+ * - `agent_claimed` never becomes `runtime_verified`.
22
+ * - Unavailable evidence is reported as DEGRADED/UNAVAILABLE with reason
23
+ * and nextAction — empty data is never presented as "zero events".
24
+ * - RuleHost evidence is surfaced as its own lane; it is never counted as
25
+ * prompt-consumption evidence.
26
+ */
27
+ import { readPromptActivationInjectionStatuses } from '@principles/host-runtime';
28
+ import { ReceiptsConsoleModel } from './ReceiptsConsoleModel.js';
29
+ import { PrinciplesConsoleModel, CorePrincipleNotGovernedError } from './PrinciplesConsoleModel.js';
30
+ import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
31
+ /** Bounded per-kind sample shown on the evaluation page (audit stays paged). */
32
+ const AUDIT_SAMPLE_LIMIT = 5;
33
+ function isRecord(value) {
34
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
35
+ }
36
+ function stringOrUndefined(value) {
37
+ return typeof value === 'string' ? value : undefined;
38
+ }
39
+ /**
40
+ * Bounded episode/effect/outcome sample summary from the payload (untrusted).
41
+ * Shortened by CODE POINTS — a UTF-16 slice can split a surrogate pair and
42
+ * render U+FFFD in the Owner-facing panel (EP-11).
43
+ */
44
+ function recordSummary(payload) {
45
+ if (!isRecord(payload))
46
+ return '';
47
+ const candidate = payload.observationSummary ?? payload.actionSummary ?? payload.feedbackText ?? payload.resultSummary;
48
+ const text = typeof candidate === 'string' ? candidate : '';
49
+ const codePoints = Array.from(text);
50
+ return codePoints.length > 160 ? `${codePoints.slice(0, 157).join('')}…` : text;
51
+ }
52
+ export class ExaminerEvidenceModel {
53
+ workspaceDir;
54
+ constructor(workspaceDir) {
55
+ this.workspaceDir = workspaceDir;
56
+ }
57
+ /**
58
+ * One principle's full production-evidence aggregate. The caller has
59
+ * already validated the principle id charset; a principle that is not in
60
+ * the ledger returns null (route answers 404 — never a fabricated entry).
61
+ */
62
+ async getPrincipleEvidence(principleId) {
63
+ const asOf = new Date().toISOString();
64
+ // 1. Ledger facts (the authoritative principle asset source).
65
+ const principlesModel = new PrinciplesConsoleModel(this.workspaceDir);
66
+ let detailOutput;
67
+ try {
68
+ detailOutput = await principlesModel.getPrincipleDetail(principleId);
69
+ }
70
+ catch (error) {
71
+ // Core principles are not workspace-governed objects: they have no
72
+ // production-evidence aggregate here, so the route answers the same
73
+ // 404 as any other principle absent from the workspace ledger.
74
+ if (error instanceof CorePrincipleNotGovernedError)
75
+ return null;
76
+ throw error;
77
+ }
78
+ if (!detailOutput)
79
+ return null;
80
+ const detail = detailOutput.principle;
81
+ // 2. Activation lifecycle rows (prompt + RuleCode lanes) — full reuse of
82
+ // the activations read model, filtered to this principle. A read error
83
+ // degrades the ACTIVATION lane only: the ledger facts below still
84
+ // answer, and the lane carries reason + nextAction (never "zero").
85
+ const activationsModel = new ActivationsConsoleModel(this.workspaceDir);
86
+ let activations;
87
+ try {
88
+ activations = await activationsModel.getActivations();
89
+ }
90
+ catch (error) {
91
+ activations = {
92
+ activations: [],
93
+ status: 'degraded',
94
+ reason: `activation read failed: ${error instanceof Error ? error.message : String(error)}`,
95
+ nextAction: 'check workspace .pd/state.db readability and the activation schema',
96
+ };
97
+ }
98
+ const rows = activations.activations.filter((row) => row.principleId === principleId);
99
+ const activation = {
100
+ status: activations.status,
101
+ reason: activations.reason,
102
+ nextAction: activations.nextAction,
103
+ promptInjectionRoute: activations.promptInjectionRoute,
104
+ prompt: rows
105
+ .filter((row) => row.channel === 'prompt')
106
+ .map((row) => ({
107
+ activationId: row.activationId,
108
+ action: row.action,
109
+ status: row.status,
110
+ activatedAt: row.activatedAt,
111
+ deactivatedAt: row.deactivatedAt,
112
+ enforcement: row.enforcement,
113
+ warning: row.warning,
114
+ })),
115
+ ruleCode: rows
116
+ .filter((row) => row.channel === 'code_tool_hook')
117
+ .map((row) => ({
118
+ activationId: row.activationId,
119
+ action: row.action,
120
+ mode: row.mode,
121
+ status: row.status,
122
+ enforcement: row.enforcement,
123
+ targetRef: row.targetRef,
124
+ warning: row.warning,
125
+ })),
126
+ };
127
+ // 3. Injection statuses from the SAME production projection the runtime
128
+ // uses (route + budget provenance + per-activation single-item facts).
129
+ const injectionReport = await readPromptActivationInjectionStatuses({ workspaceDir: this.workspaceDir });
130
+ const targetStatuses = injectionReport.statuses.filter((row) => row.principleId === principleId);
131
+ const injection = {
132
+ status: injectionReport.globalReason ? 'degraded' : 'available',
133
+ globalReason: injectionReport.globalReason,
134
+ statuses: targetStatuses.map((row) => ({
135
+ activationId: row.activationId,
136
+ status: row.status,
137
+ inCurrentWindow: row.inCurrentWindow,
138
+ costChars: row.costChars,
139
+ budget: row.budget,
140
+ route: row.route,
141
+ reason: row.reason,
142
+ })),
143
+ budgetProvenance: injectionReport.budgetProvenance
144
+ ? {
145
+ capacitySource: injectionReport.budgetProvenance.capacitySource,
146
+ capacityStatus: injectionReport.budgetProvenance.capacityStatus,
147
+ contextWindowTokens: injectionReport.budgetProvenance.contextWindowTokens,
148
+ policyVersion: injectionReport.budgetProvenance.policyVersion,
149
+ fallbackReason: injectionReport.budgetProvenance.fallbackReason,
150
+ }
151
+ : undefined,
152
+ warnings: injectionReport.warnings.slice(0, 20),
153
+ };
154
+ // 4. Receipts + 5. evidence audit through the existing receipts model.
155
+ const receiptsModel = new ReceiptsConsoleModel(this.workspaceDir);
156
+ const receipts = await receiptsModel.getPrincipleReceipts(principleId);
157
+ const audit = await receiptsModel.getEvidenceAudit({ type: 'principle', principleId });
158
+ const deliveryConfirmations = { prepared: 0, submitted: 0, hostAccepted: 0, contextPresent: 0, runtimeLoaded: 0 };
159
+ for (const record of audit.deliveries) {
160
+ const confirmation = stringOrUndefined(isRecord(record.payload) ? record.payload.confirmation : undefined);
161
+ if (confirmation === 'prepared')
162
+ deliveryConfirmations.prepared += 1;
163
+ else if (confirmation === 'submitted')
164
+ deliveryConfirmations.submitted += 1;
165
+ else if (confirmation === 'host_accepted')
166
+ deliveryConfirmations.hostAccepted += 1;
167
+ else if (confirmation === 'context_present')
168
+ deliveryConfirmations.contextPresent += 1;
169
+ else if (confirmation === 'runtime_loaded')
170
+ deliveryConfirmations.runtimeLoaded += 1;
171
+ }
172
+ const applicationProof = { agentClaimed: 0, runtimeVerified: 0 };
173
+ for (const record of audit.applications) {
174
+ const proof = stringOrUndefined(isRecord(record.payload) ? record.payload.proofMethod : undefined);
175
+ if (proof === 'runtime_verified')
176
+ applicationProof.runtimeVerified += 1;
177
+ else if (proof === 'agent_claimed')
178
+ applicationProof.agentClaimed += 1;
179
+ }
180
+ // P1-3: pagination truth rides through verbatim — the counts above are
181
+ // LOADED-window sizes, and hasMore per kind tells the Owner more records
182
+ // exist beyond this read.
183
+ const pages = {
184
+ delivery: { hasMore: audit.pages.delivery.hasMore, nextCursor: audit.pages.delivery.nextCursor },
185
+ application: { hasMore: audit.pages.application.hasMore, nextCursor: audit.pages.application.nextCursor },
186
+ behavior_episode: { hasMore: audit.pages.behavior_episode.hasMore, nextCursor: audit.pages.behavior_episode.nextCursor },
187
+ effect: { hasMore: audit.pages.effect.hasMore, nextCursor: audit.pages.effect.nextCursor },
188
+ outcome: { hasMore: audit.pages.outcome.hasMore, nextCursor: audit.pages.outcome.nextCursor },
189
+ };
190
+ const anyHasMore = Object.values(pages).some((page) => page.hasMore);
191
+ const evidenceAudit = {
192
+ status: audit.status,
193
+ reason: audit.reason,
194
+ nextAction: audit.nextAction,
195
+ counts: {
196
+ deliveries: audit.deliveries.length,
197
+ applications: audit.applications.length,
198
+ episodes: audit.episodes.length,
199
+ effects: audit.effects.length,
200
+ outcomes: audit.outcomes.length,
201
+ },
202
+ pages,
203
+ anyHasMore,
204
+ deliveryConfirmations,
205
+ applicationProof,
206
+ unresolvedReferences: audit.unresolvedReferences.length,
207
+ capabilityDeclarations: audit.capabilityDeclarations.slice(0, 12).map((declaration) => ({
208
+ hostKind: String(declaration.hostKind),
209
+ capability: String(declaration.capability),
210
+ status: String(declaration.status),
211
+ maxConfirmation: stringOrUndefined(declaration.maxConfirmation),
212
+ note: stringOrUndefined(declaration.note),
213
+ })),
214
+ samples: {
215
+ deliveries: audit.deliveries.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
216
+ evidenceId: record.evidenceId,
217
+ recordedAt: record.recordedAt,
218
+ sourceKind: String(record.sourceKind),
219
+ confirmation: stringOrUndefined(isRecord(record.payload) ? record.payload.confirmation : undefined) ?? 'unknown',
220
+ outcome: stringOrUndefined(isRecord(record.payload) ? record.payload.outcome : undefined) ?? 'unknown',
221
+ targetKind: stringOrUndefined(isRecord(record.payload) ? record.payload.targetKind : undefined) ?? 'unknown',
222
+ })),
223
+ applications: audit.applications.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
224
+ evidenceId: record.evidenceId,
225
+ recordedAt: record.recordedAt,
226
+ sourceKind: String(record.sourceKind),
227
+ proofMethod: stringOrUndefined(isRecord(record.payload) ? record.payload.proofMethod : undefined) ?? 'unknown',
228
+ action: stringOrUndefined(isRecord(record.payload) ? record.payload.action : undefined) ?? 'unknown',
229
+ })),
230
+ episodes: audit.episodes.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
231
+ evidenceId: record.evidenceId,
232
+ recordedAt: record.recordedAt,
233
+ sourceKind: String(record.sourceKind),
234
+ summary: recordSummary(record.payload),
235
+ })),
236
+ effects: audit.effects.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
237
+ evidenceId: record.evidenceId,
238
+ recordedAt: record.recordedAt,
239
+ sourceKind: String(record.sourceKind),
240
+ summary: recordSummary(record.payload),
241
+ })),
242
+ outcomes: audit.outcomes.slice(0, AUDIT_SAMPLE_LIMIT).map((record) => ({
243
+ evidenceId: record.evidenceId,
244
+ recordedAt: record.recordedAt,
245
+ sourceKind: String(record.sourceKind),
246
+ summary: recordSummary(record.payload),
247
+ })),
248
+ },
249
+ asOf: audit.asOf,
250
+ coverage: audit.coverage ?? null,
251
+ };
252
+ return {
253
+ asOf,
254
+ principleId,
255
+ principle: {
256
+ id: detail.id,
257
+ text: detail.text,
258
+ status: detail.status,
259
+ ruleCount: detail.ruleCount,
260
+ derivedFromPainIds: detail.derivedFromPainIds,
261
+ evaluability: detail.evaluability,
262
+ painPreventedCount: detail.painPreventedCount,
263
+ adherenceRate: detail.adherenceRate,
264
+ valueScore: detail.valueScore,
265
+ runtimeDeactivated: detail.runtimeDeactivated,
266
+ ledgerStatusNote: 'ledger status is the ASSET lifecycle — it is not runtime activation; the runtime rows below are the activation truth',
267
+ },
268
+ activation,
269
+ injection,
270
+ receipts,
271
+ evidenceAudit,
272
+ };
273
+ }
274
+ }
@@ -171,7 +171,21 @@ export class ReceiptsConsoleModel {
171
171
  coverage: unreadCoverage('unavailable', 'ledger_table_missing', 'update_plugin'),
172
172
  };
173
173
  }
174
- throw err;
174
+ // An unreadable state.db (permissions, corruption) must degrade, not
175
+ // throw: the documented read-path contract is degraded-over-500, so the
176
+ // evidence route keeps its ledger facts and shows WHY receipts are absent.
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ return {
179
+ status: 'degraded',
180
+ reason: `receipt ledger read failed: ${message}`,
181
+ nextAction: 'Run pd runtime diagnostics to check workspace .pd/state.db readability',
182
+ principleId,
183
+ effectCount: 0,
184
+ presenceCount: 0,
185
+ lastEffectAt: null,
186
+ events: [],
187
+ coverage: unreadCoverage('unavailable', 'receipt_read_failed', 'run_runtime_diagnostics'),
188
+ };
175
189
  }
176
190
  finally {
177
191
  conn.close();