@principles/core 1.265.1 → 1.267.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 (71) hide show
  1. package/dist/runtime-v2/__tests__/owner-retry.test.d.ts +2 -0
  2. package/dist/runtime-v2/__tests__/owner-retry.test.d.ts.map +1 -0
  3. package/dist/runtime-v2/__tests__/owner-retry.test.js +144 -0
  4. package/dist/runtime-v2/__tests__/owner-retry.test.js.map +1 -0
  5. package/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.d.ts +2 -0
  6. package/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.d.ts.map +1 -0
  7. package/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.js +293 -0
  8. package/dist/runtime-v2/__tests__/pain-diagnosis-persistence.test.js.map +1 -0
  9. package/dist/runtime-v2/__tests__/recovery-actions-log.test.d.ts +2 -0
  10. package/dist/runtime-v2/__tests__/recovery-actions-log.test.d.ts.map +1 -0
  11. package/dist/runtime-v2/__tests__/recovery-actions-log.test.js +112 -0
  12. package/dist/runtime-v2/__tests__/recovery-actions-log.test.js.map +1 -0
  13. package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts +10 -0
  14. package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.d.ts.map +1 -1
  15. package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js +40 -4
  16. package/dist/runtime-v2/diagnostician/rootcause-prompt-builder.js.map +1 -1
  17. package/dist/runtime-v2/diagnostician-prompt-builder.d.ts +6 -0
  18. package/dist/runtime-v2/diagnostician-prompt-builder.d.ts.map +1 -1
  19. package/dist/runtime-v2/diagnostician-prompt-builder.js.map +1 -1
  20. package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +28 -0
  21. package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
  22. package/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.d.ts +2 -0
  23. package/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.d.ts.map +1 -0
  24. package/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.js +44 -0
  25. package/dist/runtime-v2/feature-flags/__tests__/pain-diagnosis-persistence-flag.test.js.map +1 -0
  26. package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  27. package/dist/runtime-v2/feature-flags/feature-flag-contract.js +18 -0
  28. package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  29. package/dist/runtime-v2/index.d.ts +5 -0
  30. package/dist/runtime-v2/index.d.ts.map +1 -1
  31. package/dist/runtime-v2/index.js +5 -0
  32. package/dist/runtime-v2/index.js.map +1 -1
  33. package/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -1
  34. package/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -0
  35. package/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
  36. package/dist/runtime-v2/internalization/owner-retry.d.ts +49 -0
  37. package/dist/runtime-v2/internalization/owner-retry.d.ts.map +1 -0
  38. package/dist/runtime-v2/internalization/owner-retry.js +51 -0
  39. package/dist/runtime-v2/internalization/owner-retry.js.map +1 -0
  40. package/dist/runtime-v2/pain-signal-bridge.d.ts +19 -0
  41. package/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
  42. package/dist/runtime-v2/pain-signal-bridge.js +61 -0
  43. package/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
  44. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts +12 -0
  45. package/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
  46. package/dist/runtime-v2/pain-signal-runtime-factory.js +48 -2
  47. package/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
  48. package/dist/runtime-v2/recovery-actions-log.d.ts +46 -0
  49. package/dist/runtime-v2/recovery-actions-log.d.ts.map +1 -0
  50. package/dist/runtime-v2/recovery-actions-log.js +116 -0
  51. package/dist/runtime-v2/recovery-actions-log.js.map +1 -0
  52. package/dist/runtime-v2/recovery-sweep-service.d.ts +8 -0
  53. package/dist/runtime-v2/recovery-sweep-service.d.ts.map +1 -1
  54. package/dist/runtime-v2/recovery-sweep-service.js +4 -0
  55. package/dist/runtime-v2/recovery-sweep-service.js.map +1 -1
  56. package/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts +77 -0
  57. package/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.d.ts.map +1 -0
  58. package/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.js +40 -0
  59. package/dist/runtime-v2/store/pain-diagnosis/pain-diagnosis-store.js.map +1 -0
  60. package/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts +9 -0
  61. package/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.d.ts.map +1 -0
  62. package/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js +118 -0
  63. package/dist/runtime-v2/store/pain-diagnosis/sqlite-pain-diagnosis-store.js.map +1 -0
  64. package/dist/runtime-v2/store/runtime-state-manager.d.ts +11 -0
  65. package/dist/runtime-v2/store/runtime-state-manager.d.ts.map +1 -1
  66. package/dist/runtime-v2/store/runtime-state-manager.js +18 -0
  67. package/dist/runtime-v2/store/runtime-state-manager.js.map +1 -1
  68. package/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
  69. package/dist/runtime-v2/store/sqlite-connection.js +23 -0
  70. package/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=owner-retry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-retry.test.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/owner-retry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * owner-retry.ts tests — Governance Recovery Actions v1.
3
+ *
4
+ * The extracted owner authority reset (needs_human_review → pending, clearing
5
+ * runnerDecision + completionIntent atomically). CLI behavior is guarded by
6
+ * pd-cli's runtime-internalization-retry-owner-authority tests against the
7
+ * SAME shared function; these tests exercise the function directly and via
8
+ * the RecoverySweepService production path (EP-02: real RuntimeStateManager +
9
+ * real SQLite temp workspace — no store mocks).
10
+ */
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import * as os from 'node:os';
15
+ import { RuntimeStateManager } from '../store/runtime-state-manager.js';
16
+ import { hydratePITaskRecord, createPITaskDiagnosticJson, } from '../internalization/pitask-metadata.js';
17
+ import { ownerRetryNeedsHumanReviewTask } from '../internalization/owner-retry.js';
18
+ import { createRecoverySweepService } from '../recovery-sweep-service.js';
19
+ const TASK_ID = 'rollout_reviewer-owner-retry-core';
20
+ let workspaceDir;
21
+ let stateManager;
22
+ function intent(status) {
23
+ return {
24
+ decision: 'needs_revision',
25
+ sourceRunId: 'run-core-1',
26
+ revisionEpoch: 1,
27
+ status,
28
+ effect: 'needs_human_review',
29
+ };
30
+ }
31
+ async function seedNeedsHumanReview(opts = {}) {
32
+ await stateManager.createTask({
33
+ taskId: opts.taskId ?? TASK_ID,
34
+ taskKind: 'rollout_reviewer',
35
+ status: 'needs_human_review',
36
+ attemptCount: 2,
37
+ maxAttempts: 3,
38
+ diagnosticJson: opts.diagnosticJson ??
39
+ createPITaskDiagnosticJson({
40
+ dependencyTaskIds: ['evaluator-1'],
41
+ channel: 'prompt',
42
+ timeoutMs: 300_000,
43
+ inputArtifactRefs: [],
44
+ outputArtifactRefs: [],
45
+ correlationId: 'owner-retry-core',
46
+ ...opts.meta,
47
+ }),
48
+ });
49
+ }
50
+ async function readTask(taskId = TASK_ID) {
51
+ return stateManager.getTask(taskId);
52
+ }
53
+ beforeEach(async () => {
54
+ workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-owner-retry-core-'));
55
+ stateManager = new RuntimeStateManager({ workspaceDir });
56
+ await stateManager.initialize();
57
+ });
58
+ afterEach(async () => {
59
+ await stateManager.close();
60
+ try {
61
+ fs.rmSync(workspaceDir, { recursive: true, force: true });
62
+ }
63
+ catch {
64
+ /* temp */
65
+ }
66
+ });
67
+ describe('ownerRetryNeedsHumanReviewTask (direct function)', () => {
68
+ it('resets status/attemptCount and clears runnerDecision + completionIntent', async () => {
69
+ await seedNeedsHumanReview({
70
+ meta: { runnerDecision: 'needs_revision', completionIntent: intent('applied') },
71
+ });
72
+ const outcome = await ownerRetryNeedsHumanReviewTask(stateManager, TASK_ID);
73
+ expect(outcome).toEqual({
74
+ status: 'requeued',
75
+ taskKind: 'rollout_reviewer',
76
+ previousStatus: 'needs_human_review',
77
+ });
78
+ const task = await readTask();
79
+ expect(task?.status).toBe('pending');
80
+ expect(task?.attemptCount).toBe(0);
81
+ const meta = task ? hydratePITaskRecord(task) : null;
82
+ expect(meta).not.toBeNull();
83
+ expect(meta?.runnerDecision).toBeUndefined();
84
+ expect(meta?.completionIntent).toBeUndefined();
85
+ // lineage preserved
86
+ expect(meta?.dependencyTaskIds).toEqual(['evaluator-1']);
87
+ expect(meta?.channel).toBe('prompt');
88
+ });
89
+ it('returns not_found for an unknown task id (no mutation)', async () => {
90
+ const outcome = await ownerRetryNeedsHumanReviewTask(stateManager, 'does-not-exist');
91
+ expect(outcome).toEqual({ status: 'not_found' });
92
+ });
93
+ it('returns skipped (no mutation) for a task that is not needs_human_review', async () => {
94
+ await seedNeedsHumanReview({ taskId: 'failed-task-1' });
95
+ await stateManager.updateTask('failed-task-1', { status: 'failed' });
96
+ const outcome = await ownerRetryNeedsHumanReviewTask(stateManager, 'failed-task-1');
97
+ expect(outcome.status).toBe('skipped');
98
+ if (outcome.status === 'skipped') {
99
+ expect(outcome.previousStatus).toBe('failed');
100
+ expect(outcome.taskKind).toBe('rollout_reviewer');
101
+ }
102
+ // row untouched
103
+ expect((await readTask('failed-task-1'))?.status).toBe('failed');
104
+ });
105
+ it('fail-closed: metadata_invalid leaves the row completely untouched', async () => {
106
+ await seedNeedsHumanReview({ diagnosticJson: JSON.stringify({ note: 'not pi metadata' }) });
107
+ const outcome = await ownerRetryNeedsHumanReviewTask(stateManager, TASK_ID);
108
+ expect(outcome).toEqual({ status: 'metadata_invalid', taskKind: 'rollout_reviewer' });
109
+ const task = await readTask();
110
+ expect(task?.status).toBe('needs_human_review');
111
+ expect(task?.attemptCount).toBe(2);
112
+ });
113
+ });
114
+ describe('RecoverySweepService.recoverNeedsHumanReviewTask (production path)', () => {
115
+ it('delegates to the same sequence via the sweep service handle', async () => {
116
+ await seedNeedsHumanReview({
117
+ meta: { runnerDecision: 'needs_revision', completionIntent: intent('pending') },
118
+ });
119
+ const handle = await createRecoverySweepService({ workspaceDir });
120
+ try {
121
+ const outcome = await handle.service.recoverNeedsHumanReviewTask(TASK_ID);
122
+ expect(outcome.status).toBe('requeued');
123
+ const task = await readTask();
124
+ expect(task?.status).toBe('pending');
125
+ const meta = task ? hydratePITaskRecord(task) : null;
126
+ expect(meta?.runnerDecision).toBeUndefined();
127
+ expect(meta?.completionIntent).toBeUndefined();
128
+ }
129
+ finally {
130
+ await handle.close();
131
+ }
132
+ });
133
+ it('reports not_found through the service for an unknown id', async () => {
134
+ const handle = await createRecoverySweepService({ workspaceDir });
135
+ try {
136
+ const outcome = await handle.service.recoverNeedsHumanReviewTask('nope');
137
+ expect(outcome.status).toBe('not_found');
138
+ }
139
+ finally {
140
+ await handle.close();
141
+ }
142
+ });
143
+ });
144
+ //# sourceMappingURL=owner-retry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-retry.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/owner-retry.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,uCAAuC,CAAC;AAE/C,OAAO,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,MAAM,OAAO,GAAG,mCAAmC,CAAC;AAEpD,IAAI,YAAoB,CAAC;AACzB,IAAI,YAAiC,CAAC;AAKtC,SAAS,MAAM,CAAC,MAA6B;IAC3C,OAAO;QACL,QAAQ,EAAE,gBAAgB;QAC1B,WAAW,EAAE,YAAY;QACzB,aAAa,EAAE,CAAC;QAChB,MAAM;QACN,MAAM,EAAE,oBAAoB;KAC7B,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,IAAI,GAAiF,EAAE;IAEvF,MAAM,YAAY,CAAC,UAAU,CAAC;QAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,OAAO;QAC9B,QAAQ,EAAE,kBAAkB;QAC5B,MAAM,EAAE,oBAAoB;QAC5B,YAAY,EAAE,CAAC;QACf,WAAW,EAAE,CAAC;QACd,cAAc,EACZ,IAAI,CAAC,cAAc;YACnB,0BAA0B,CAAC;gBACzB,iBAAiB,EAAE,CAAC,aAAa,CAAC;gBAClC,OAAO,EAAE,QAAQ;gBACjB,SAAS,EAAE,OAAO;gBAClB,iBAAiB,EAAE,EAAE;gBACrB,kBAAkB,EAAE,EAAE;gBACtB,aAAa,EAAE,kBAAkB;gBACjC,GAAG,IAAI,CAAC,IAAI;aACb,CAAC;KACL,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,MAAM,GAAW,OAAO;IAC9C,OAAO,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,YAAY,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAC9E,YAAY,GAAG,IAAI,mBAAmB,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IACzD,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC;AAClC,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,UAAU;IACZ,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kDAAkD,EAAE,GAAG,EAAE;IAChE,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,oBAAoB,CAAC;YACzB,IAAI,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE;SAChF,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5E,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC;YACtB,MAAM,EAAE,UAAU;YAClB,QAAQ,EAAE,kBAAkB;YAC5B,cAAc,EAAE,oBAAoB;SACrC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,aAAa,EAAE,CAAC;QAC/C,oBAAoB;QACpB,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;QACzD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;QACrF,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,oBAAoB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;QACxD,MAAM,YAAY,CAAC,UAAU,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAErE,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACpF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACpD,CAAC;QACD,gBAAgB;QAChB,MAAM,CAAC,CAAC,MAAM,QAAQ,CAAC,eAAe,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,oBAAoB,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC,CAAC;QAE5F,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC5E,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAEtF,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oEAAoE,EAAE,GAAG,EAAE;IAClF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,oBAAoB,CAAC;YACzB,IAAI,EAAE,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE;SAChF,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;YAC1E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAExC,MAAM,IAAI,GAAG,MAAM,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACrD,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,aAAa,EAAE,CAAC;YAC7C,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,aAAa,EAAE,CAAC;QACjD,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,MAAM,GAAG,MAAM,0BAA0B,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,2BAA2B,CAAC,MAAM,CAAC,CAAC;YACzE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3C,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pain-diagnosis-persistence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pain-diagnosis-persistence.test.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/pain-diagnosis-persistence.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Pain Diagnosis Persistence regression tests (Pain Diagnosis Persistence
3
+ * SPEC v1.1 §12).
4
+ *
5
+ * Coverage required by the SPEC:
6
+ * 1. Agent behavior failure → persisted diagnosis category People.
7
+ * 2. Environment failure → persisted diagnosis category Tooling.
8
+ * 3. Mixed failure → multiple diagnosis rows sharing one pain_id (no Mixed enum).
9
+ * 4. Principle pollution → a software-bug (Design/Tooling) diagnosis keeps its
10
+ * true category and cannot masquerade as an agent-behavior (People) narrative;
11
+ * the Stage A prompt only allows People with agent-behavior evidence.
12
+ *
13
+ * Plus flag-contract behavior: default off writes nothing; replay is idempotent;
14
+ * unparseable rootCause prefixes and store failures degrade observably (rc-9).
15
+ *
16
+ * Persistence goes through a REAL RuntimeStateManager on a temp workspace, so
17
+ * the schema migration (pain_diagnoses) and the sqlite round-trip are exercised.
18
+ */
19
+ import fs from 'node:fs';
20
+ import os from 'node:os';
21
+ import path from 'node:path';
22
+ import { afterAll, describe, expect, it } from 'vitest';
23
+ import { PainSignalBridge } from '../pain-signal-bridge.js';
24
+ import { RuntimeStateManager as RuntimeStateManagerClass } from '../store/runtime-state-manager.js';
25
+ import { SqliteConnection } from '../store/sqlite-connection.js';
26
+ import { buildRootCauseProtocolInstruction } from '../diagnostician/rootcause-prompt-builder.js';
27
+ import { mapBridgeTelemetryToStoreEvent } from '../pain-signal-runtime-factory.js';
28
+ // ── Fixtures ─────────────────────────────────────────────────────────────────
29
+ const workspaces = [];
30
+ const managers = [];
31
+ async function makeStateManager() {
32
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pain-diag-'));
33
+ workspaces.push(dir);
34
+ const mgr = new RuntimeStateManagerClass({ workspaceDir: dir });
35
+ await mgr.initialize();
36
+ managers.push(mgr);
37
+ return mgr;
38
+ }
39
+ afterAll(async () => {
40
+ for (const mgr of managers.splice(0)) {
41
+ try {
42
+ await mgr.close();
43
+ }
44
+ catch { /* best-effort cleanup */ }
45
+ }
46
+ for (const dir of workspaces.splice(0)) {
47
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 20 });
48
+ }
49
+ });
50
+ function makeOutput(rootCause, over = {}) {
51
+ return {
52
+ valid: true,
53
+ diagnosisId: 'diag-001',
54
+ summary: 'test diagnosis summary',
55
+ rootCause,
56
+ violatedPrinciples: [],
57
+ evidence: [{ sourceRef: 'tool_calls:1', note: 'agent action evidence' }],
58
+ recommendations: [{ kind: 'defer', description: 'no actionable principle' }],
59
+ confidence: 0.7,
60
+ ...over,
61
+ };
62
+ }
63
+ function makeBridge(stateManager, opts) {
64
+ const ledgerAdapter = {
65
+ existsForCandidate: () => null,
66
+ writeProbationEntry: undefined,
67
+ };
68
+ return new PainSignalBridge({
69
+ stateManager,
70
+ runner: {
71
+ run: async (taskId) => ({ status: 'succeeded', taskId, attemptCount: 1 }),
72
+ },
73
+ intakeService: undefined,
74
+ ledgerAdapter,
75
+ autoIntakeEnabled: false,
76
+ diagnosisPersistenceEnabled: opts.persistenceEnabled,
77
+ eventEmitter: {
78
+ emitTelemetry: (event) => { opts.events.push({ eventType: event.eventType, payload: event.payload }); },
79
+ },
80
+ });
81
+ }
82
+ async function completeDiagnosis(bridge, opts) {
83
+ return bridge.onDiagnosisComplete({
84
+ taskId: opts.taskId,
85
+ diagnosticianOutput: opts.output,
86
+ painId: opts.painId,
87
+ provenance: 'automatic_hook',
88
+ inputEvidenceCount: 1,
89
+ });
90
+ }
91
+ // ── SPEC §12: Agent Failure → People ────────────────────────────────────────
92
+ describe('pain diagnosis persistence (SPEC v1.1 §12 regression)', () => {
93
+ it('agent behavior failure (changed code without testing) persists category People', async () => {
94
+ const mgr = await makeStateManager();
95
+ const events = [];
96
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events });
97
+ await completeDiagnosis(bridge, {
98
+ painId: 'pain-agent-001',
99
+ taskId: 'diagnosis_pain-agent-001',
100
+ output: makeOutput('People: Agent modified code without running the tests first'),
101
+ });
102
+ const rows = await mgr.getDiagnosesByPainId('pain-agent-001');
103
+ expect(rows).toHaveLength(1);
104
+ expect(rows[0]?.category).toBe('People');
105
+ expect(rows[0]?.rootCause).toBe('People: Agent modified code without running the tests first');
106
+ expect(rows[0]?.diagnosisId).toBe('diag-001');
107
+ expect(rows[0]?.evidence).toEqual([{ sourceRef: 'tool_calls:1', note: 'agent action evidence' }]);
108
+ expect(rows[0]?.confidence).toBe(0.7);
109
+ expect(rows[0]?.artifactId).toBeNull();
110
+ expect(typeof rows[0]?.createdAt).toBe('string');
111
+ });
112
+ // ── SPEC §12: Environment Failure → Tooling ──────────────────────────────
113
+ it('environment failure (third-party API outage) persists category Tooling', async () => {
114
+ const mgr = await makeStateManager();
115
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events: [] });
116
+ await completeDiagnosis(bridge, {
117
+ painId: 'pain-env-001',
118
+ taskId: 'diagnosis_pain-env-001',
119
+ output: makeOutput('Tooling: third-party API returned 503 for every retry'),
120
+ });
121
+ const rows = await mgr.getDiagnosesByPainId('pain-env-001');
122
+ expect(rows).toHaveLength(1);
123
+ expect(rows[0]?.category).toBe('Tooling');
124
+ });
125
+ // ── SPEC §12: Mixed Failure → multiple rows per pain_id ──────────────────
126
+ it('mixed failure persists multiple diagnosis rows under one pain_id (no Mixed enum)', async () => {
127
+ const mgr = await makeStateManager();
128
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events: [] });
129
+ const painId = 'pain-mixed-001';
130
+ await completeDiagnosis(bridge, {
131
+ painId,
132
+ taskId: 'diagnosis_pain-mixed-001',
133
+ output: makeOutput('Tooling: unstable third-party API', { diagnosisId: 'diag-tooling' }),
134
+ });
135
+ await completeDiagnosis(bridge, {
136
+ painId,
137
+ taskId: 'diagnosis_pain-mixed-001-r2',
138
+ output: makeOutput('People: Agent added no fallback despite prior API instability', { diagnosisId: 'diag-people' }),
139
+ });
140
+ const rows = await mgr.getDiagnosesByPainId(painId);
141
+ expect(rows).toHaveLength(2);
142
+ // Same-millisecond writes make created_at tie-break on id — assert the
143
+ // category SET, order is not part of the contract.
144
+ expect([...rows.map((r) => r.category)].sort()).toEqual(['People', 'Tooling']);
145
+ expect(new Set(rows.map((r) => r.painId))).toEqual(new Set([painId]));
146
+ });
147
+ it('replaying the same diagnosis completion is idempotent', async () => {
148
+ const mgr = await makeStateManager();
149
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events: [] });
150
+ const call = {
151
+ painId: 'pain-replay-001',
152
+ taskId: 'diagnosis_pain-replay-001',
153
+ output: makeOutput('Design: missing validation gate'),
154
+ };
155
+ await completeDiagnosis(bridge, call);
156
+ await completeDiagnosis(bridge, call);
157
+ const rows = await mgr.getDiagnosesByPainId('pain-replay-001');
158
+ expect(rows).toHaveLength(1);
159
+ });
160
+ // ── SPEC §12: Principle Pollution ────────────────────────────────────────
161
+ it('software-bug diagnosis keeps its Design category — attribution cannot drift to People', async () => {
162
+ const mgr = await makeStateManager();
163
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events: [] });
164
+ // A bug in PD itself, which the agent legitimately worked around: the
165
+ // persisted attribution must remain Design so a future agent Principle
166
+ // cannot be justified by re-reading this pain's history as "agent error".
167
+ await completeDiagnosis(bridge, {
168
+ painId: 'pain-pollution-001',
169
+ taskId: 'diagnosis_pain-pollution-001',
170
+ output: makeOutput('Design: PD failed to close the DB handle before renaming the workspace', { confidence: 0.9 }),
171
+ });
172
+ const rows = await mgr.getDiagnosesByPainId('pain-pollution-001');
173
+ expect(rows).toHaveLength(1);
174
+ expect(rows[0]?.category).toBe('Design');
175
+ expect(rows[0]?.rootCause).not.toMatch(/^People:/);
176
+ });
177
+ it('prompt requires agent-behavior evidence for People — failure is not automatically an agent error', () => {
178
+ const prompt = buildRootCauseProtocolInstruction({ evidenceFirstAttribution: true });
179
+ expect(prompt).toContain('Evidence First Attribution');
180
+ expect(prompt).toContain('A failure event is NOT automatically an agent error');
181
+ expect(prompt).toContain('Never classify as People without evidence of an avoidable agent action');
182
+ // SPEC §6: the five People-leaning behaviors are stated explicitly.
183
+ expect(prompt).toContain('without investigating');
184
+ expect(prompt).toContain('skipped verification or testing');
185
+ expect(prompt).toContain('ignored an already-active principle');
186
+ expect(prompt).toContain('high-risk action without checking');
187
+ expect(prompt).toContain('repeated the same class of error');
188
+ });
189
+ it('prompt is byte-identical when the flag is off (default) — no attribution semantics ship by default', () => {
190
+ const off = buildRootCauseProtocolInstruction();
191
+ expect(off).toBe(buildRootCauseProtocolInstruction({ evidenceFirstAttribution: false }));
192
+ expect(off).not.toContain('Evidence First Attribution');
193
+ expect(off).not.toContain('automatically an agent error');
194
+ });
195
+ // ── Flag default / degradation contract ───────────────────────────────────
196
+ it('flag off (default) writes no diagnosis rows', async () => {
197
+ const mgr = await makeStateManager();
198
+ const bridge = makeBridge(mgr, { persistenceEnabled: false, events: [] });
199
+ await completeDiagnosis(bridge, {
200
+ painId: 'pain-off-001',
201
+ taskId: 'diagnosis_pain-off-001',
202
+ output: makeOutput('People: Agent modified code without running the tests first'),
203
+ });
204
+ expect(await mgr.getDiagnosesByPainId('pain-off-001')).toHaveLength(0);
205
+ });
206
+ it('unparseable rootCause prefix skips the write observably (rc-9)', async () => {
207
+ const mgr = await makeStateManager();
208
+ const events = [];
209
+ const bridge = makeBridge(mgr, { persistenceEnabled: true, events });
210
+ await completeDiagnosis(bridge, {
211
+ painId: 'pain-noprefix-001',
212
+ taskId: 'diagnosis_pain-noprefix-001',
213
+ output: makeOutput('Something went wrong with no category prefix'),
214
+ });
215
+ expect(await mgr.getDiagnosesByPainId('pain-noprefix-001')).toHaveLength(0);
216
+ const skipped = events.filter((e) => e.eventType === 'pain_diagnosis_persist_skipped');
217
+ expect(skipped).toHaveLength(1);
218
+ expect(skipped[0]?.payload.reason).toBe('unparseable_root_cause_prefix');
219
+ expect(typeof skipped[0]?.payload.nextAction).toBe('string');
220
+ });
221
+ it('store failure degrades observably and does not break the admission flow', async () => {
222
+ const mgr = await makeStateManager();
223
+ const events = [];
224
+ const failing = Object.assign(Object.create(Object.getPrototypeOf(mgr)), mgr, {
225
+ recordPainDiagnosis: async () => { throw new Error('injected store failure'); },
226
+ });
227
+ const bridge = makeBridge(failing, { persistenceEnabled: true, events });
228
+ await expect(completeDiagnosis(bridge, {
229
+ painId: 'pain-storefail-001',
230
+ taskId: 'diagnosis_pain-storefail-001',
231
+ output: makeOutput('Tooling: store unavailable'),
232
+ })).resolves.toBeDefined();
233
+ const failed = events.filter((e) => e.eventType === 'pain_diagnosis_persist_failed');
234
+ expect(failed).toHaveLength(1);
235
+ expect(String(failed[0]?.payload.reason)).toContain('injected store failure');
236
+ expect(typeof failed[0]?.payload.nextAction).toBe('string');
237
+ });
238
+ // ── Schema migration ──────────────────────────────────────────────────────
239
+ it('existing state.db gains the pain_diagnoses table on next open', async () => {
240
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-pain-diag-mig-'));
241
+ workspaces.push(dir);
242
+ const first = new SqliteConnection(dir);
243
+ const db = first.getDb();
244
+ expect(db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='pain_diagnoses'").get()).toBeDefined();
245
+ // Simulate a pre-feature database by dropping the table, then reopen.
246
+ db.prepare('DROP TABLE pain_diagnoses').run();
247
+ first.close();
248
+ const second = new SqliteConnection(dir);
249
+ const db2 = second.getDb();
250
+ expect(db2.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='pain_diagnoses'").get()).toBeDefined();
251
+ second.close();
252
+ });
253
+ });
254
+ // ── Factory telemetry wiring ─────────────────────────────────────────────────
255
+ describe('pain diagnosis persistence — factory telemetry forwarding', () => {
256
+ // The production factory (createPainSignalBridge) is the ONLY place the
257
+ // bridge gets an eventEmitter. Before this feature, no emitter was wired,
258
+ // so the bridge's pre-existing event sites (candidate_admission_decision,
259
+ // candidate_dreamer_task_seeded, …) were dormant. Wiring the emitter must
260
+ // not wake them up as degradation_triggered — routine admission decisions
261
+ // are not degradations, and flag-off behavior must stay identical to main.
262
+ it('maps persistence degradation events onto degradation_triggered with originalEventType preserved', () => {
263
+ const mapped = mapBridgeTelemetryToStoreEvent({
264
+ eventType: 'pain_diagnosis_persist_failed',
265
+ traceId: 'pain-1',
266
+ timestamp: '2026-08-24T00:00:00.000Z',
267
+ payload: { reason: 'boom', nextAction: 'inspect' },
268
+ });
269
+ expect(mapped).toEqual({
270
+ eventType: 'degradation_triggered',
271
+ traceId: 'pain-1',
272
+ timestamp: '2026-08-24T00:00:00.000Z',
273
+ sessionId: '',
274
+ payload: { component: 'PainSignalBridge', originalEventType: 'pain_diagnosis_persist_failed', reason: 'boom', nextAction: 'inspect' },
275
+ });
276
+ });
277
+ it('keeps pre-existing bridge events dormant — admission decisions are NOT degradation_triggered (negative control)', () => {
278
+ for (const eventType of [
279
+ 'candidate_admission_decision',
280
+ 'candidate_dreamer_task_seeded',
281
+ 'candidate_dreamer_task_seed_failed',
282
+ 'candidate_not_internalizable',
283
+ ]) {
284
+ expect(mapBridgeTelemetryToStoreEvent({
285
+ eventType,
286
+ traceId: 'cand-1',
287
+ timestamp: '2026-08-24T00:00:00.000Z',
288
+ payload: { decision: 'accepted' },
289
+ })).toBeNull();
290
+ }
291
+ });
292
+ });
293
+ //# sourceMappingURL=pain-diagnosis-persistence.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pain-diagnosis-persistence.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/pain-diagnosis-persistence.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=recovery-actions-log.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-actions-log.test.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/recovery-actions-log.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * RecoveryActionsLog unit tests — Governance Recovery Actions v1 (SPEC §10).
3
+ *
4
+ * Tests the append-only JSONL audit log for owner-triggered recovery actions.
5
+ */
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+ import { appendRecoveryAction, listRecoveryActions } from '../recovery-actions-log.js';
11
+ describe('RecoveryActionsLog', () => {
12
+ let tmpDir = '';
13
+ beforeEach(() => {
14
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'recovery-actions-log-test-'));
15
+ });
16
+ afterEach(() => {
17
+ fs.rmSync(tmpDir, { recursive: true, force: true });
18
+ });
19
+ it('append creates .state/recovery_actions.jsonl', () => {
20
+ appendRecoveryAction(tmpDir, {
21
+ taskId: 'dreamer-chain-1',
22
+ previousStatus: 'failed',
23
+ result: 'recovered',
24
+ operator: 'console',
25
+ reason: 'Owner approved retry after reviewing failure',
26
+ });
27
+ const logPath = path.join(tmpDir, '.state', 'recovery_actions.jsonl');
28
+ expect(fs.existsSync(logPath)).toBe(true);
29
+ });
30
+ it('append returns record with SPEC §10 minimal fields', () => {
31
+ const record = appendRecoveryAction(tmpDir, {
32
+ taskId: 'dreamer-chain-2',
33
+ previousStatus: 'needs_human_review',
34
+ result: 'requeued',
35
+ operator: 'console',
36
+ reason: null,
37
+ });
38
+ expect(typeof record.actionId).toBe('string');
39
+ expect(record.actionId.length).toBeGreaterThan(0);
40
+ expect(record.taskId).toBe('dreamer-chain-2');
41
+ expect(record.action).toBe('recover');
42
+ expect(record.previousStatus).toBe('needs_human_review');
43
+ expect(record.operator).toBe('console');
44
+ expect(record.reason).toBeNull();
45
+ expect(record.createdAt).toBeTruthy();
46
+ expect(record.result).toBe('requeued');
47
+ });
48
+ it('defaults operator to null when omitted', () => {
49
+ const record = appendRecoveryAction(tmpDir, {
50
+ taskId: 't-1',
51
+ previousStatus: 'failed',
52
+ result: 'recovered',
53
+ });
54
+ expect(record.operator).toBeNull();
55
+ });
56
+ it('bounds reason to 2000 characters (rc-8 bounded serialization)', () => {
57
+ const longReason = 'x'.repeat(3000);
58
+ const record = appendRecoveryAction(tmpDir, {
59
+ taskId: 't-2',
60
+ previousStatus: 'failed',
61
+ result: 'recovered',
62
+ reason: longReason,
63
+ });
64
+ expect(record.reason).not.toBeNull();
65
+ expect(record.reason.length).toBe(2000);
66
+ });
67
+ it('rejects invalid result values (fail loud, rc-3)', () => {
68
+ expect(() => appendRecoveryAction(tmpDir, {
69
+ taskId: 't-3',
70
+ previousStatus: 'failed',
71
+ // @ts-expect-error — deliberately invalid union member
72
+ result: 'exploded',
73
+ })).toThrow(/Invalid recovery result/);
74
+ });
75
+ it('list returns records oldest-first and round-trips fields', () => {
76
+ appendRecoveryAction(tmpDir, { taskId: 't-a', previousStatus: 'failed', result: 'recovered', operator: 'console', reason: 'first' });
77
+ appendRecoveryAction(tmpDir, { taskId: 't-b', previousStatus: 'needs_human_review', result: 'requeued' });
78
+ const records = listRecoveryActions(tmpDir);
79
+ expect(records.length).toBe(2);
80
+ expect(records[0]?.taskId).toBe('t-a');
81
+ expect(records[0]?.result).toBe('recovered');
82
+ expect(records[0]?.reason).toBe('first');
83
+ expect(records[1]?.taskId).toBe('t-b');
84
+ expect(records[1]?.operator).toBeNull();
85
+ });
86
+ it('list filters by taskId', () => {
87
+ appendRecoveryAction(tmpDir, { taskId: 't-a', previousStatus: 'failed', result: 'recovered' });
88
+ appendRecoveryAction(tmpDir, { taskId: 't-b', previousStatus: 'failed', result: 'recovered' });
89
+ const records = listRecoveryActions(tmpDir, { taskId: 't-b' });
90
+ expect(records.length).toBe(1);
91
+ expect(records[0]?.taskId).toBe('t-b');
92
+ });
93
+ it('list returns [] when log file does not exist', () => {
94
+ expect(listRecoveryActions(tmpDir)).toEqual([]);
95
+ });
96
+ it('list skips corrupt and shape-invalid lines (rc-1/rc-2)', () => {
97
+ const stateDir = path.join(tmpDir, '.state');
98
+ fs.mkdirSync(stateDir, { recursive: true });
99
+ const logPath = path.join(stateDir, 'recovery_actions.jsonl');
100
+ const good = appendRecoveryAction(tmpDir, { taskId: 't-good', previousStatus: 'failed', result: 'recovered' });
101
+ fs.appendFileSync(logPath, [
102
+ 'not json at all\n',
103
+ JSON.stringify({ taskId: 'missing every other field' }) + '\n',
104
+ JSON.stringify({ actionId: 'x', taskId: 't-wrong-result', action: 'recover', previousStatus: 'failed', operator: null, reason: null, createdAt: 'now', result: 'not-a-real-result' }) + '\n',
105
+ ].join(''), 'utf-8');
106
+ const records = listRecoveryActions(tmpDir);
107
+ expect(records.length).toBe(1);
108
+ expect(records[0]?.taskId).toBe('t-good');
109
+ expect(records[0]?.actionId).toBe(good.actionId);
110
+ });
111
+ });
112
+ //# sourceMappingURL=recovery-actions-log.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-actions-log.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/recovery-actions-log.test.ts"],"names":[],"mappings":"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"}
@@ -47,6 +47,16 @@ export interface RootCausePromptBuilderOptions {
47
47
  * byte-identical to the pre-PRI-468 prompt (EP-03: no silent fallback).
48
48
  */
49
49
  intentGrounding?: boolean;
50
+ /**
51
+ * Evidence First Attribution (Pain Diagnosis Persistence SPEC §2.2/§6,
52
+ * gated by the `pain_diagnosis_persistence` flag; default: false).
53
+ *
54
+ * When true, inserts attribution rules that constrain PHASE 3 classification
55
+ * to be evidence-driven (People only on agent-avoidable-behavior evidence,
56
+ * never as a default). When false/undefined, the prompt is byte-identical
57
+ * to the pre-feature prompt (EP-03: no silent fallback).
58
+ */
59
+ evidenceFirstAttribution?: boolean;
50
60
  }
51
61
  /**
52
62
  * Build the Stage A diagnostic protocol instruction (PHASE 1–3 + optional PHASE 3.5).