@principles/core 1.236.0 → 1.238.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.
- package/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js +586 -0
- package/dist/runtime-v2/__tests__/artificer-runner-vslice.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/feedback/create-report.test.js +51 -0
- package/dist/runtime-v2/__tests__/feedback/create-report.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/feedback/privacy-preview.test.js +1 -1
- package/dist/runtime-v2/__tests__/feedback/privacy-preview.test.js.map +1 -1
- package/dist/runtime-v2/__tests__/feedback/render-markdown.test.js +1 -1
- package/dist/runtime-v2/__tests__/feedback/render-markdown.test.js.map +1 -1
- package/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +109 -0
- package/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
- package/dist/runtime-v2/activation/activation-dispatcher.d.ts +11 -0
- package/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/dist/runtime-v2/activation/activation-dispatcher.js +60 -6
- package/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- package/dist/runtime-v2/activation/activation-types.d.ts +4 -0
- package/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js +34 -0
- package/dist/runtime-v2/feature-flags/__tests__/feature-flag-contract.test.js.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js +5 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/feedback/__tests__/create-report-agent-draft.test.d.ts +2 -0
- package/dist/runtime-v2/feedback/__tests__/create-report-agent-draft.test.d.ts.map +1 -0
- package/dist/runtime-v2/feedback/__tests__/create-report-agent-draft.test.js +221 -0
- package/dist/runtime-v2/feedback/__tests__/create-report-agent-draft.test.js.map +1 -0
- package/dist/runtime-v2/feedback/__tests__/mailto-url.test.d.ts +11 -0
- package/dist/runtime-v2/feedback/__tests__/mailto-url.test.d.ts.map +1 -0
- package/dist/runtime-v2/feedback/__tests__/mailto-url.test.js +160 -0
- package/dist/runtime-v2/feedback/__tests__/mailto-url.test.js.map +1 -0
- package/dist/runtime-v2/feedback/__tests__/pending-agent-draft-store.test.d.ts +2 -0
- package/dist/runtime-v2/feedback/__tests__/pending-agent-draft-store.test.d.ts.map +1 -0
- package/dist/runtime-v2/feedback/__tests__/pending-agent-draft-store.test.js +342 -0
- package/dist/runtime-v2/feedback/__tests__/pending-agent-draft-store.test.js.map +1 -0
- package/dist/runtime-v2/feedback/create-report.d.ts +7 -1
- package/dist/runtime-v2/feedback/create-report.d.ts.map +1 -1
- package/dist/runtime-v2/feedback/create-report.js +65 -7
- package/dist/runtime-v2/feedback/create-report.js.map +1 -1
- package/dist/runtime-v2/feedback/feedback-types.d.ts +18 -1
- package/dist/runtime-v2/feedback/feedback-types.d.ts.map +1 -1
- package/dist/runtime-v2/feedback/feedback-types.js +19 -12
- package/dist/runtime-v2/feedback/feedback-types.js.map +1 -1
- package/dist/runtime-v2/feedback/index.d.ts +3 -1
- package/dist/runtime-v2/feedback/index.d.ts.map +1 -1
- package/dist/runtime-v2/feedback/index.js +7 -1
- package/dist/runtime-v2/feedback/index.js.map +1 -1
- package/dist/runtime-v2/feedback/pending-agent-draft-store.d.ts +106 -0
- package/dist/runtime-v2/feedback/pending-agent-draft-store.d.ts.map +1 -0
- package/dist/runtime-v2/feedback/pending-agent-draft-store.js +220 -0
- package/dist/runtime-v2/feedback/pending-agent-draft-store.js.map +1 -0
- package/dist/runtime-v2/feedback/privacy-preview.d.ts +16 -0
- package/dist/runtime-v2/feedback/privacy-preview.d.ts.map +1 -1
- package/dist/runtime-v2/feedback/privacy-preview.js +54 -0
- package/dist/runtime-v2/feedback/privacy-preview.js.map +1 -1
- package/dist/runtime-v2/index.d.ts +5 -0
- package/dist/runtime-v2/index.d.ts.map +1 -1
- package/dist/runtime-v2/index.js +9 -0
- package/dist/runtime-v2/index.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js +74 -0
- package/dist/runtime-v2/internalization/__tests__/artificer-prompt-builder.test.js.map +1 -1
- package/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts +30 -1
- package/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-prompt-builder.js +9 -0
- package/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.d.ts +9 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +131 -0
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-agent-draft.test.d.ts +2 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-agent-draft.test.d.ts.map +1 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-agent-draft.test.js +459 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-agent-draft.test.js.map +1 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +27 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +138 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/peer-runner-types.d.ts +9 -0
- package/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -1
- package/dist/runtime-v2/store/pain/__tests__/sqlite-dead-letter-store.test.d.ts +2 -0
- package/dist/runtime-v2/store/pain/__tests__/sqlite-dead-letter-store.test.d.ts.map +1 -0
- package/dist/runtime-v2/store/pain/__tests__/sqlite-dead-letter-store.test.js +225 -0
- package/dist/runtime-v2/store/pain/__tests__/sqlite-dead-letter-store.test.js.map +1 -0
- package/dist/runtime-v2/store/pain/index.d.ts +3 -0
- package/dist/runtime-v2/store/pain/index.d.ts.map +1 -0
- package/dist/runtime-v2/store/pain/index.js +2 -0
- package/dist/runtime-v2/store/pain/index.js.map +1 -0
- package/dist/runtime-v2/store/pain/sqlite-dead-letter-store.d.ts +70 -0
- package/dist/runtime-v2/store/pain/sqlite-dead-letter-store.d.ts.map +1 -0
- package/dist/runtime-v2/store/pain/sqlite-dead-letter-store.js +154 -0
- package/dist/runtime-v2/store/pain/sqlite-dead-letter-store.js.map +1 -0
- package/dist/runtime-v2/store/sqlite-connection.d.ts.map +1 -1
- package/dist/runtime-v2/store/sqlite-connection.js +41 -0
- package/dist/runtime-v2/store/sqlite-connection.js.map +1 -1
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.d.ts +2 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.d.ts.map +1 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.js +442 -0
- package/dist/runtime-v2/store/task/__tests__/sqlite-task-store.failed-tasks.test.js.map +1 -0
- package/dist/runtime-v2/store/task/index.d.ts +1 -0
- package/dist/runtime-v2/store/task/index.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/index.js.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.d.ts +45 -0
- package/dist/runtime-v2/store/task/sqlite-task-store.d.ts.map +1 -1
- package/dist/runtime-v2/store/task/sqlite-task-store.js +205 -0
- package/dist/runtime-v2/store/task/sqlite-task-store.js.map +1 -1
- package/dist/runtime-v2/store/task/task-types.d.ts +60 -0
- package/dist/runtime-v2/store/task/task-types.d.ts.map +1 -0
- package/dist/runtime-v2/store/task/task-types.js +2 -0
- package/dist/runtime-v2/store/task/task-types.js.map +1 -0
- package/package.json +3 -2
|
@@ -662,4 +662,590 @@ describe('ArtificerRunner integration: test-double captures sourceScribeArtifact
|
|
|
662
662
|
expect(storedOutput.sourceTrace.scribeArtifactId).toBe(SCRIBE_ART_ID);
|
|
663
663
|
});
|
|
664
664
|
});
|
|
665
|
+
describe('PRI-508: ArtificerRunner.buildContext reads dreamer artifact via scribe.sourceTrace.dreamerArtifactId', () => {
|
|
666
|
+
// Vertical slice 4: end-to-end — dreamer artifact in store → prompt contains dreamerContext 5维字段
|
|
667
|
+
const SCRIBE_ART_ID = 'pi-art-scribe-pri508';
|
|
668
|
+
const DREAMER_ART_ID = 'pi-art-dreamer-pri508';
|
|
669
|
+
const SCRIBE_TASK_ID_PRI508 = 'scribe-pri508';
|
|
670
|
+
const ARTIFICER_TASK_ID_PRI508 = 'artificer-pri508';
|
|
671
|
+
function makeScribeArtifactWithDreamer() {
|
|
672
|
+
return {
|
|
673
|
+
artifactId: SCRIBE_ART_ID,
|
|
674
|
+
artifactKind: 'principle',
|
|
675
|
+
sourceTaskId: SCRIBE_TASK_ID_PRI508,
|
|
676
|
+
lineageArtifactIds: [DREAMER_ART_ID],
|
|
677
|
+
validationStatus: 'pending',
|
|
678
|
+
contentJson: JSON.stringify({
|
|
679
|
+
taskId: SCRIBE_TASK_ID_PRI508,
|
|
680
|
+
sourcePhilosopherArtifactId: 'pi-art-philosopher-pri508',
|
|
681
|
+
principleDraft: {
|
|
682
|
+
title: 'Path Traversal Guard',
|
|
683
|
+
statement: 'Validate parent path before write_file',
|
|
684
|
+
rationale: 'Unchecked parent path enables path traversal',
|
|
685
|
+
applicability: ['write_file'],
|
|
686
|
+
antiPatterns: ['Trusting raw user input path'],
|
|
687
|
+
confidence: 0.9,
|
|
688
|
+
},
|
|
689
|
+
sourceTrace: {
|
|
690
|
+
philosopherArtifactId: 'pi-art-philosopher-pri508',
|
|
691
|
+
dreamerArtifactId: DREAMER_ART_ID,
|
|
692
|
+
},
|
|
693
|
+
risks: [],
|
|
694
|
+
generatedAt: new Date().toISOString(),
|
|
695
|
+
}),
|
|
696
|
+
createdAt: new Date().toISOString(),
|
|
697
|
+
updatedAt: new Date().toISOString(),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function makeDreamerArtifact() {
|
|
701
|
+
return {
|
|
702
|
+
artifactId: DREAMER_ART_ID,
|
|
703
|
+
artifactKind: 'principle',
|
|
704
|
+
sourceTaskId: 'dreamer-task-pri508',
|
|
705
|
+
lineageArtifactIds: [],
|
|
706
|
+
validationStatus: 'pending',
|
|
707
|
+
contentJson: JSON.stringify({
|
|
708
|
+
valid: true,
|
|
709
|
+
taskId: 'dreamer-task-pri508',
|
|
710
|
+
candidates: [
|
|
711
|
+
{
|
|
712
|
+
candidateIndex: 0,
|
|
713
|
+
badDecision: 'agent called write_file without resolving parent path',
|
|
714
|
+
betterDecision: 'agent must resolve and validate parent path before write_file',
|
|
715
|
+
rationale: 'parent path resolution prevents path traversal exploits',
|
|
716
|
+
confidence: 0.85,
|
|
717
|
+
riskLevel: 'medium',
|
|
718
|
+
strategicPerspective: 'proactive validation beats reactive cleanup',
|
|
719
|
+
},
|
|
720
|
+
],
|
|
721
|
+
contextRefs: [],
|
|
722
|
+
generatedAt: new Date().toISOString(),
|
|
723
|
+
}),
|
|
724
|
+
createdAt: new Date().toISOString(),
|
|
725
|
+
updatedAt: new Date().toISOString(),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function makeScribeTaskPri508() {
|
|
729
|
+
return {
|
|
730
|
+
taskId: SCRIBE_TASK_ID_PRI508,
|
|
731
|
+
taskKind: 'scribe',
|
|
732
|
+
status: 'succeeded',
|
|
733
|
+
attemptCount: 1,
|
|
734
|
+
maxAttempts: 3,
|
|
735
|
+
resultRef: 'scribe://run-pri508',
|
|
736
|
+
createdAt: new Date().toISOString(),
|
|
737
|
+
updatedAt: new Date().toISOString(),
|
|
738
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
739
|
+
dependencyTaskIds: [],
|
|
740
|
+
channel: 'prompt',
|
|
741
|
+
timeoutMs: 300_000,
|
|
742
|
+
inputArtifactRefs: [],
|
|
743
|
+
outputArtifactRefs: [{ artifactType: 'principle', ref: SCRIBE_ART_ID }],
|
|
744
|
+
}),
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
function makeArtificerTaskPri508() {
|
|
748
|
+
return {
|
|
749
|
+
taskId: ARTIFICER_TASK_ID_PRI508,
|
|
750
|
+
taskKind: 'artificer',
|
|
751
|
+
status: 'pending',
|
|
752
|
+
attemptCount: 0,
|
|
753
|
+
maxAttempts: 3,
|
|
754
|
+
createdAt: new Date().toISOString(),
|
|
755
|
+
updatedAt: new Date().toISOString(),
|
|
756
|
+
diagnosticJson: createPITaskDiagnosticJson({
|
|
757
|
+
dependencyTaskIds: [SCRIBE_TASK_ID_PRI508],
|
|
758
|
+
channel: 'prompt',
|
|
759
|
+
timeoutMs: 300_000,
|
|
760
|
+
inputArtifactRefs: [{ artifactType: 'principle', ref: SCRIBE_ART_ID }],
|
|
761
|
+
outputArtifactRefs: [],
|
|
762
|
+
}),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function makeArtificerOutputPri508() {
|
|
766
|
+
return {
|
|
767
|
+
taskId: ARTIFICER_TASK_ID_PRI508,
|
|
768
|
+
sourceScribeArtifactId: SCRIBE_ART_ID,
|
|
769
|
+
implementationCode: 'function evaluate(input, helpers) { return { decision: "allow", matched: false, reason: "ok" }; }',
|
|
770
|
+
goldenTraceCases: [
|
|
771
|
+
{ caseId: 'positive-1', kind: 'positive', toolName: 'write_file', params: { path: '/workspace/file' }, expectedDecision: 'allow' },
|
|
772
|
+
{ caseId: 'negative-1', kind: 'negative', toolName: 'write_file', params: { path: '/etc/passwd' }, expectedDecision: 'block' },
|
|
773
|
+
],
|
|
774
|
+
affectedTools: ['write_file'],
|
|
775
|
+
implementationSummary: 'Validate parent path before write',
|
|
776
|
+
sourceTrace: {
|
|
777
|
+
scribeArtifactId: SCRIBE_ART_ID,
|
|
778
|
+
},
|
|
779
|
+
risks: [],
|
|
780
|
+
generatedAt: new Date().toISOString(),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
it('dreamerContext 5维字段透传到 artificer prompt (end-to-end)', async () => {
|
|
784
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
785
|
+
await artifactStore.upsertArtifact(makeScribeArtifactWithDreamer());
|
|
786
|
+
await artifactStore.upsertArtifact(makeDreamerArtifact());
|
|
787
|
+
const artificerTask = makeArtificerTaskPri508();
|
|
788
|
+
const scribeTask = makeScribeTaskPri508();
|
|
789
|
+
const stateManager = {
|
|
790
|
+
acquireLease: vi.fn().mockResolvedValue(artificerTask),
|
|
791
|
+
getTask: vi.fn().mockImplementation((id) => {
|
|
792
|
+
if (id === ARTIFICER_TASK_ID_PRI508)
|
|
793
|
+
return Promise.resolve(artificerTask);
|
|
794
|
+
if (id === SCRIBE_TASK_ID_PRI508)
|
|
795
|
+
return Promise.resolve(scribeTask);
|
|
796
|
+
return Promise.resolve(null);
|
|
797
|
+
}),
|
|
798
|
+
getRunsByTask: vi.fn().mockResolvedValue([{
|
|
799
|
+
runId: 'run-pri508',
|
|
800
|
+
taskId: ARTIFICER_TASK_ID_PRI508,
|
|
801
|
+
runtimeKind: 'artificer',
|
|
802
|
+
startedAt: new Date().toISOString(),
|
|
803
|
+
}]),
|
|
804
|
+
getValidRunsByTaskTolerant: vi.fn().mockResolvedValue({
|
|
805
|
+
runs: [{ runId: 'run-pri508', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }],
|
|
806
|
+
degradedRuns: [],
|
|
807
|
+
}),
|
|
808
|
+
updateRunOutput: vi.fn().mockResolvedValue(undefined),
|
|
809
|
+
markTaskSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
810
|
+
markTaskFailed: vi.fn().mockResolvedValue(undefined),
|
|
811
|
+
markTaskRetryWait: vi.fn().mockResolvedValue(undefined),
|
|
812
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: () => false }),
|
|
813
|
+
};
|
|
814
|
+
let capturedPrompt;
|
|
815
|
+
const runtimeAdapter = {
|
|
816
|
+
startRun: vi.fn().mockImplementation((input) => {
|
|
817
|
+
capturedPrompt = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
|
|
818
|
+
return Promise.resolve({ runId: 'run-pri508', runtimeKind: 'test-double', startedAt: new Date().toISOString() });
|
|
819
|
+
}),
|
|
820
|
+
pollRun: vi.fn().mockResolvedValue({ status: 'succeeded', runId: 'run-pri508' }),
|
|
821
|
+
fetchOutput: vi.fn().mockResolvedValue({ payload: makeArtificerOutputPri508() }),
|
|
822
|
+
cancelRun: vi.fn().mockResolvedValue(undefined),
|
|
823
|
+
};
|
|
824
|
+
const eventEmitter = { emitTelemetry: vi.fn() };
|
|
825
|
+
const validator = new DefaultArtificerValidator();
|
|
826
|
+
const deps = {
|
|
827
|
+
stateManager,
|
|
828
|
+
runtimeAdapter,
|
|
829
|
+
eventEmitter,
|
|
830
|
+
artifactStore,
|
|
831
|
+
validator,
|
|
832
|
+
};
|
|
833
|
+
const runner = new ArtificerRunner(deps, {
|
|
834
|
+
owner: 'test',
|
|
835
|
+
runtimeKind: 'artificer',
|
|
836
|
+
pollIntervalMs: 10,
|
|
837
|
+
timeoutMs: 1000,
|
|
838
|
+
});
|
|
839
|
+
const result = await runner.run(ARTIFICER_TASK_ID_PRI508);
|
|
840
|
+
expect(result.status).toBe('succeeded');
|
|
841
|
+
// Assert the prompt captured at startRun contains dreamerContext 5维字段
|
|
842
|
+
expect(capturedPrompt).toBeDefined();
|
|
843
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
844
|
+
expect(parsed.dreamerContext).toBeDefined();
|
|
845
|
+
expect(parsed.dreamerContext.badDecision).toBe('agent called write_file without resolving parent path');
|
|
846
|
+
expect(parsed.dreamerContext.betterDecision).toBe('agent must resolve and validate parent path before write_file');
|
|
847
|
+
expect(parsed.dreamerContext.rationale).toBe('parent path resolution prevents path traversal exploits');
|
|
848
|
+
expect(parsed.dreamerContext.riskLevel).toBe('medium');
|
|
849
|
+
expect(parsed.dreamerContext.strategicPerspective).toBe('proactive validation beats reactive cleanup');
|
|
850
|
+
});
|
|
851
|
+
it('dreamerArtifactId 缺失时 dreamerContext undefined (向后兼容)', async () => {
|
|
852
|
+
// scribe artifact 不带 sourceTrace.dreamerArtifactId → dreamerContext 不出现在 prompt
|
|
853
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
854
|
+
const scribeArtifactNoDreamer = {
|
|
855
|
+
artifactId: SCRIBE_ART_ID,
|
|
856
|
+
artifactKind: 'principle',
|
|
857
|
+
sourceTaskId: SCRIBE_TASK_ID_PRI508,
|
|
858
|
+
lineageArtifactIds: [],
|
|
859
|
+
validationStatus: 'pending',
|
|
860
|
+
contentJson: JSON.stringify({
|
|
861
|
+
taskId: SCRIBE_TASK_ID_PRI508,
|
|
862
|
+
sourcePhilosopherArtifactId: 'pi-art-philosopher-pri508',
|
|
863
|
+
principleDraft: {
|
|
864
|
+
title: 'Path Traversal Guard',
|
|
865
|
+
statement: 'Validate parent path before write_file',
|
|
866
|
+
rationale: 'Unchecked parent path enables path traversal',
|
|
867
|
+
applicability: ['write_file'],
|
|
868
|
+
antiPatterns: ['Trusting raw user input path'],
|
|
869
|
+
confidence: 0.9,
|
|
870
|
+
},
|
|
871
|
+
sourceTrace: {
|
|
872
|
+
philosopherArtifactId: 'pi-art-philosopher-pri508',
|
|
873
|
+
// dreamerArtifactId 刻意缺失
|
|
874
|
+
},
|
|
875
|
+
risks: [],
|
|
876
|
+
generatedAt: new Date().toISOString(),
|
|
877
|
+
}),
|
|
878
|
+
createdAt: new Date().toISOString(),
|
|
879
|
+
updatedAt: new Date().toISOString(),
|
|
880
|
+
};
|
|
881
|
+
await artifactStore.upsertArtifact(scribeArtifactNoDreamer);
|
|
882
|
+
const artificerTask = makeArtificerTaskPri508();
|
|
883
|
+
const scribeTask = makeScribeTaskPri508();
|
|
884
|
+
const stateManager = {
|
|
885
|
+
acquireLease: vi.fn().mockResolvedValue(artificerTask),
|
|
886
|
+
getTask: vi.fn().mockImplementation((id) => {
|
|
887
|
+
if (id === ARTIFICER_TASK_ID_PRI508)
|
|
888
|
+
return Promise.resolve(artificerTask);
|
|
889
|
+
if (id === SCRIBE_TASK_ID_PRI508)
|
|
890
|
+
return Promise.resolve(scribeTask);
|
|
891
|
+
return Promise.resolve(null);
|
|
892
|
+
}),
|
|
893
|
+
getRunsByTask: vi.fn().mockResolvedValue([{ runId: 'run-pri508-nodreamer', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }]),
|
|
894
|
+
getValidRunsByTaskTolerant: vi.fn().mockResolvedValue({ runs: [{ runId: 'run-pri508-nodreamer', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }], degradedRuns: [] }),
|
|
895
|
+
updateRunOutput: vi.fn().mockResolvedValue(undefined),
|
|
896
|
+
markTaskSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
897
|
+
markTaskFailed: vi.fn().mockResolvedValue(undefined),
|
|
898
|
+
markTaskRetryWait: vi.fn().mockResolvedValue(undefined),
|
|
899
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: () => false }),
|
|
900
|
+
};
|
|
901
|
+
let capturedPrompt;
|
|
902
|
+
const runtimeAdapter = {
|
|
903
|
+
startRun: vi.fn().mockImplementation((input) => {
|
|
904
|
+
capturedPrompt = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
|
|
905
|
+
return Promise.resolve({ runId: 'run-pri508-nodreamer', runtimeKind: 'test-double', startedAt: new Date().toISOString() });
|
|
906
|
+
}),
|
|
907
|
+
pollRun: vi.fn().mockResolvedValue({ status: 'succeeded', runId: 'run-pri508-nodreamer' }),
|
|
908
|
+
fetchOutput: vi.fn().mockResolvedValue({ payload: makeArtificerOutputPri508() }),
|
|
909
|
+
cancelRun: vi.fn().mockResolvedValue(undefined),
|
|
910
|
+
};
|
|
911
|
+
const eventEmitter = { emitTelemetry: vi.fn() };
|
|
912
|
+
const validator = new DefaultArtificerValidator();
|
|
913
|
+
const deps = {
|
|
914
|
+
stateManager,
|
|
915
|
+
runtimeAdapter,
|
|
916
|
+
eventEmitter,
|
|
917
|
+
artifactStore,
|
|
918
|
+
validator,
|
|
919
|
+
};
|
|
920
|
+
const runner = new ArtificerRunner(deps, {
|
|
921
|
+
owner: 'test',
|
|
922
|
+
runtimeKind: 'artificer',
|
|
923
|
+
pollIntervalMs: 10,
|
|
924
|
+
timeoutMs: 1000,
|
|
925
|
+
});
|
|
926
|
+
const result = await runner.run(ARTIFICER_TASK_ID_PRI508);
|
|
927
|
+
expect(result.status).toBe('succeeded');
|
|
928
|
+
expect(capturedPrompt).toBeDefined();
|
|
929
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
930
|
+
expect(parsed.dreamerContext).toBeUndefined();
|
|
931
|
+
});
|
|
932
|
+
it('dreamerArtifactId 存在但 artifact 查不到时 emit event + dreamerContext undefined (rc-9)', async () => {
|
|
933
|
+
// scribe 带 dreamerArtifactId 但 store 里没有 dreamer artifact → 不能静默成功,要有可观察事件
|
|
934
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
935
|
+
await artifactStore.upsertArtifact(makeScribeArtifactWithDreamer());
|
|
936
|
+
// 故意不 upsert dreamer artifact
|
|
937
|
+
const artificerTask = makeArtificerTaskPri508();
|
|
938
|
+
const scribeTask = makeScribeTaskPri508();
|
|
939
|
+
const stateManager = {
|
|
940
|
+
acquireLease: vi.fn().mockResolvedValue(artificerTask),
|
|
941
|
+
getTask: vi.fn().mockImplementation((id) => {
|
|
942
|
+
if (id === ARTIFICER_TASK_ID_PRI508)
|
|
943
|
+
return Promise.resolve(artificerTask);
|
|
944
|
+
if (id === SCRIBE_TASK_ID_PRI508)
|
|
945
|
+
return Promise.resolve(scribeTask);
|
|
946
|
+
return Promise.resolve(null);
|
|
947
|
+
}),
|
|
948
|
+
getRunsByTask: vi.fn().mockResolvedValue([{ runId: 'run-pri508-missing', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }]),
|
|
949
|
+
getValidRunsByTaskTolerant: vi.fn().mockResolvedValue({ runs: [{ runId: 'run-pri508-missing', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }], degradedRuns: [] }),
|
|
950
|
+
updateRunOutput: vi.fn().mockResolvedValue(undefined),
|
|
951
|
+
markTaskSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
952
|
+
markTaskFailed: vi.fn().mockResolvedValue(undefined),
|
|
953
|
+
markTaskRetryWait: vi.fn().mockResolvedValue(undefined),
|
|
954
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: () => false }),
|
|
955
|
+
};
|
|
956
|
+
let capturedPrompt;
|
|
957
|
+
const runtimeAdapter = {
|
|
958
|
+
startRun: vi.fn().mockImplementation((input) => {
|
|
959
|
+
capturedPrompt = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
|
|
960
|
+
return Promise.resolve({ runId: 'run-pri508-missing', runtimeKind: 'test-double', startedAt: new Date().toISOString() });
|
|
961
|
+
}),
|
|
962
|
+
pollRun: vi.fn().mockResolvedValue({ status: 'succeeded', runId: 'run-pri508-missing' }),
|
|
963
|
+
fetchOutput: vi.fn().mockResolvedValue({ payload: makeArtificerOutputPri508() }),
|
|
964
|
+
cancelRun: vi.fn().mockResolvedValue(undefined),
|
|
965
|
+
};
|
|
966
|
+
const emitTelemetry = vi.fn();
|
|
967
|
+
const eventEmitter = { emitTelemetry };
|
|
968
|
+
const validator = new DefaultArtificerValidator();
|
|
969
|
+
const deps = {
|
|
970
|
+
stateManager,
|
|
971
|
+
runtimeAdapter,
|
|
972
|
+
eventEmitter,
|
|
973
|
+
artifactStore,
|
|
974
|
+
validator,
|
|
975
|
+
};
|
|
976
|
+
const runner = new ArtificerRunner(deps, {
|
|
977
|
+
owner: 'test',
|
|
978
|
+
runtimeKind: 'artificer',
|
|
979
|
+
pollIntervalMs: 10,
|
|
980
|
+
timeoutMs: 1000,
|
|
981
|
+
});
|
|
982
|
+
const result = await runner.run(ARTIFICER_TASK_ID_PRI508);
|
|
983
|
+
// 仍应成功(dreamerContext 是 best-effort,不阻塞主流程)
|
|
984
|
+
expect(result.status).toBe('succeeded');
|
|
985
|
+
// rc-9: 不能静默 — 必须有可观察事件
|
|
986
|
+
// emitTelemetry 接收单个结构化对象 (eventType + traceId + payload + ...)
|
|
987
|
+
expect(emitTelemetry).toHaveBeenCalledWith(expect.objectContaining({
|
|
988
|
+
eventType: expect.stringContaining('dreamer'),
|
|
989
|
+
traceId: ARTIFICER_TASK_ID_PRI508,
|
|
990
|
+
}));
|
|
991
|
+
expect(capturedPrompt).toBeDefined();
|
|
992
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
993
|
+
expect(parsed.dreamerContext).toBeUndefined();
|
|
994
|
+
});
|
|
995
|
+
// ── PRI-508 edge cases: cover resolveDreamerContext() failure branches (rc-9) ──
|
|
996
|
+
// Each case asserts: (a) runner still succeeds (best-effort, non-blocking),
|
|
997
|
+
// (b) dreamerContext undefined in prompt, (c) emitTelemetry called with the
|
|
998
|
+
// expected observable event name so the gap is not silent.
|
|
999
|
+
/**
|
|
1000
|
+
* Shared harness for PRI-508 edge-case tests. Accepts a custom scribe artifact
|
|
1001
|
+
* and optional dreamer artifact, runs the artificer end-to-end, and returns
|
|
1002
|
+
* the captured prompt + telemetry spy so each case can assert behavior.
|
|
1003
|
+
*/
|
|
1004
|
+
async function runPri508EdgeCase(params) {
|
|
1005
|
+
const artifactStore = new MemoryPIArtifactStore();
|
|
1006
|
+
await artifactStore.upsertArtifact(params.scribeArtifact);
|
|
1007
|
+
if (params.dreamerArtifact) {
|
|
1008
|
+
await artifactStore.upsertArtifact(params.dreamerArtifact);
|
|
1009
|
+
}
|
|
1010
|
+
const artificerTask = makeArtificerTaskPri508();
|
|
1011
|
+
const scribeTask = makeScribeTaskPri508();
|
|
1012
|
+
const stateManager = {
|
|
1013
|
+
acquireLease: vi.fn().mockResolvedValue(artificerTask),
|
|
1014
|
+
getTask: vi.fn().mockImplementation((id) => {
|
|
1015
|
+
if (id === ARTIFICER_TASK_ID_PRI508)
|
|
1016
|
+
return Promise.resolve(artificerTask);
|
|
1017
|
+
if (id === SCRIBE_TASK_ID_PRI508)
|
|
1018
|
+
return Promise.resolve(scribeTask);
|
|
1019
|
+
return Promise.resolve(null);
|
|
1020
|
+
}),
|
|
1021
|
+
getRunsByTask: vi.fn().mockResolvedValue([{ runId: 'run-pri508-edge', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }]),
|
|
1022
|
+
getValidRunsByTaskTolerant: vi.fn().mockResolvedValue({ runs: [{ runId: 'run-pri508-edge', taskId: ARTIFICER_TASK_ID_PRI508, runtimeKind: 'artificer', startedAt: new Date().toISOString() }], degradedRuns: [] }),
|
|
1023
|
+
updateRunOutput: vi.fn().mockResolvedValue(undefined),
|
|
1024
|
+
markTaskSucceeded: vi.fn().mockResolvedValue(undefined),
|
|
1025
|
+
markTaskFailed: vi.fn().mockResolvedValue(undefined),
|
|
1026
|
+
markTaskRetryWait: vi.fn().mockResolvedValue(undefined),
|
|
1027
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: () => false }),
|
|
1028
|
+
};
|
|
1029
|
+
let capturedPrompt;
|
|
1030
|
+
const runtimeAdapter = {
|
|
1031
|
+
startRun: vi.fn().mockImplementation((input) => {
|
|
1032
|
+
capturedPrompt = typeof input.inputPayload === 'string' ? input.inputPayload : JSON.stringify(input.inputPayload);
|
|
1033
|
+
return Promise.resolve({ runId: 'run-pri508-edge', runtimeKind: 'test-double', startedAt: new Date().toISOString() });
|
|
1034
|
+
}),
|
|
1035
|
+
pollRun: vi.fn().mockResolvedValue({ status: 'succeeded', runId: 'run-pri508-edge' }),
|
|
1036
|
+
fetchOutput: vi.fn().mockResolvedValue({ payload: makeArtificerOutputPri508() }),
|
|
1037
|
+
cancelRun: vi.fn().mockResolvedValue(undefined),
|
|
1038
|
+
};
|
|
1039
|
+
const emitTelemetry = vi.fn();
|
|
1040
|
+
const eventEmitter = { emitTelemetry };
|
|
1041
|
+
const validator = new DefaultArtificerValidator();
|
|
1042
|
+
const deps = {
|
|
1043
|
+
stateManager,
|
|
1044
|
+
runtimeAdapter,
|
|
1045
|
+
eventEmitter,
|
|
1046
|
+
artifactStore,
|
|
1047
|
+
validator,
|
|
1048
|
+
};
|
|
1049
|
+
const runner = new ArtificerRunner(deps, {
|
|
1050
|
+
owner: 'test',
|
|
1051
|
+
runtimeKind: 'artificer',
|
|
1052
|
+
pollIntervalMs: 10,
|
|
1053
|
+
timeoutMs: 1000,
|
|
1054
|
+
});
|
|
1055
|
+
const result = await runner.run(ARTIFICER_TASK_ID_PRI508);
|
|
1056
|
+
return { result, capturedPrompt, emitTelemetry };
|
|
1057
|
+
}
|
|
1058
|
+
/** Build a scribe artifact with a custom contentJson string. */
|
|
1059
|
+
function makeScribeWithContentJson(contentJson) {
|
|
1060
|
+
return {
|
|
1061
|
+
artifactId: SCRIBE_ART_ID,
|
|
1062
|
+
artifactKind: 'principle',
|
|
1063
|
+
sourceTaskId: SCRIBE_TASK_ID_PRI508,
|
|
1064
|
+
lineageArtifactIds: [],
|
|
1065
|
+
validationStatus: 'pending',
|
|
1066
|
+
contentJson,
|
|
1067
|
+
createdAt: new Date().toISOString(),
|
|
1068
|
+
updatedAt: new Date().toISOString(),
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
/** Build a scribe artifact whose sourceTrace.dreamerArtifactId points to DREAMER_ART_ID. */
|
|
1072
|
+
function makeScribeWithDreamerRef() {
|
|
1073
|
+
return makeScribeWithContentJson(JSON.stringify({
|
|
1074
|
+
taskId: SCRIBE_TASK_ID_PRI508,
|
|
1075
|
+
sourcePhilosopherArtifactId: 'pi-art-philosopher-pri508',
|
|
1076
|
+
principleDraft: { title: 'T', statement: 'S', rationale: 'R', applicability: [], antiPatterns: [], confidence: 0.5 },
|
|
1077
|
+
sourceTrace: { philosopherArtifactId: 'pi-art-philosopher-pri508', dreamerArtifactId: DREAMER_ART_ID },
|
|
1078
|
+
risks: [],
|
|
1079
|
+
generatedAt: new Date().toISOString(),
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
/** Build a dreamer artifact with a custom contentJson string. */
|
|
1083
|
+
function makeDreamerWithContentJson(contentJson) {
|
|
1084
|
+
return {
|
|
1085
|
+
artifactId: DREAMER_ART_ID,
|
|
1086
|
+
artifactKind: 'principle',
|
|
1087
|
+
sourceTaskId: 'dreamer-task-pri508',
|
|
1088
|
+
lineageArtifactIds: [],
|
|
1089
|
+
validationStatus: 'pending',
|
|
1090
|
+
contentJson,
|
|
1091
|
+
createdAt: new Date().toISOString(),
|
|
1092
|
+
updatedAt: new Date().toISOString(),
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
it.each([
|
|
1096
|
+
{
|
|
1097
|
+
name: 'scribe contentJson unparseable → emit dreamer_context_skipped',
|
|
1098
|
+
scribeContentJson: '{not valid json',
|
|
1099
|
+
expectedEvent: 'dreamer_context_skipped',
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: 'scribe contentJson not a record (array) → dreamerContext undefined, no event',
|
|
1103
|
+
scribeContentJson: JSON.stringify(['not', 'a', 'record']),
|
|
1104
|
+
expectedEvent: null,
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
name: 'scribe.sourceTrace missing → dreamerContext undefined, no event (backward compat)',
|
|
1108
|
+
scribeContentJson: JSON.stringify({ taskId: SCRIBE_TASK_ID_PRI508, principleDraft: { statement: 'S' } }),
|
|
1109
|
+
expectedEvent: null,
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
name: 'scribe.sourceTrace not a record (string) → dreamerContext undefined, no event',
|
|
1113
|
+
scribeContentJson: JSON.stringify({ sourceTrace: 'not-a-record' }),
|
|
1114
|
+
expectedEvent: null,
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
name: 'sourceTrace.dreamerArtifactId missing → dreamerContext undefined, no event (backward compat)',
|
|
1118
|
+
scribeContentJson: JSON.stringify({ sourceTrace: { philosopherArtifactId: 'pi-art-philosopher-pri508' } }),
|
|
1119
|
+
expectedEvent: null,
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
name: 'sourceTrace.dreamerArtifactId is undefined → dreamerContext undefined, no event',
|
|
1123
|
+
scribeContentJson: JSON.stringify({ sourceTrace: { dreamerArtifactId: undefined } }),
|
|
1124
|
+
expectedEvent: null,
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
name: 'sourceTrace.dreamerArtifactId not a string (object) → emit dreamer_context_skipped',
|
|
1128
|
+
scribeContentJson: JSON.stringify({ sourceTrace: { dreamerArtifactId: { evil: true } } }),
|
|
1129
|
+
expectedEvent: 'dreamer_context_skipped',
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
name: 'sourceTrace.dreamerArtifactId empty string → emit dreamer_context_skipped',
|
|
1133
|
+
scribeContentJson: JSON.stringify({ sourceTrace: { dreamerArtifactId: '' } }),
|
|
1134
|
+
expectedEvent: 'dreamer_context_skipped',
|
|
1135
|
+
},
|
|
1136
|
+
])('edge: $name', async ({ scribeContentJson, expectedEvent }) => {
|
|
1137
|
+
const { result, capturedPrompt, emitTelemetry } = await runPri508EdgeCase({
|
|
1138
|
+
scribeArtifact: makeScribeWithContentJson(scribeContentJson),
|
|
1139
|
+
});
|
|
1140
|
+
// Best-effort: runner still succeeds even when dreamer context cannot be resolved.
|
|
1141
|
+
expect(result.status).toBe('succeeded');
|
|
1142
|
+
expect(capturedPrompt).toBeDefined();
|
|
1143
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
1144
|
+
expect(parsed.dreamerContext).toBeUndefined();
|
|
1145
|
+
if (expectedEvent !== null) {
|
|
1146
|
+
// rc-9: failure must be observable, not silent.
|
|
1147
|
+
expect(emitTelemetry).toHaveBeenCalledWith(expect.objectContaining({
|
|
1148
|
+
eventType: expect.stringContaining(expectedEvent),
|
|
1149
|
+
traceId: ARTIFICER_TASK_ID_PRI508,
|
|
1150
|
+
}));
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
it.each([
|
|
1154
|
+
{
|
|
1155
|
+
name: 'dreamer contentJson unparseable → emit dreamer_context_invalid',
|
|
1156
|
+
dreamerContentJson: '{broken json',
|
|
1157
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
name: 'dreamer contentJson not a record (array) → emit dreamer_context_invalid',
|
|
1161
|
+
dreamerContentJson: JSON.stringify(['not', 'a', 'record']),
|
|
1162
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
name: 'dreamer candidates missing → emit dreamer_context_invalid',
|
|
1166
|
+
dreamerContentJson: JSON.stringify({ valid: true, taskId: 'dreamer-task-pri508' }),
|
|
1167
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: 'dreamer candidates not an array (string) → emit dreamer_context_invalid',
|
|
1171
|
+
dreamerContentJson: JSON.stringify({ candidates: 'not-an-array' }),
|
|
1172
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
name: 'dreamer candidates empty array → emit dreamer_context_invalid',
|
|
1176
|
+
dreamerContentJson: JSON.stringify({ candidates: [] }),
|
|
1177
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: 'dreamer candidates[0] not a record (string) → emit dreamer_context_invalid',
|
|
1181
|
+
dreamerContentJson: JSON.stringify({ candidates: ['not-a-record'] }),
|
|
1182
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
name: 'dreamer candidates[0] missing badDecision → emit dreamer_context_invalid',
|
|
1186
|
+
dreamerContentJson: JSON.stringify({ candidates: [{ betterDecision: 'b', rationale: 'r' }] }),
|
|
1187
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
name: 'dreamer candidates[0] missing betterDecision → emit dreamer_context_invalid',
|
|
1191
|
+
dreamerContentJson: JSON.stringify({ candidates: [{ badDecision: 'a', rationale: 'r' }] }),
|
|
1192
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
name: 'dreamer candidates[0] missing rationale → emit dreamer_context_invalid',
|
|
1196
|
+
dreamerContentJson: JSON.stringify({ candidates: [{ badDecision: 'a', betterDecision: 'b' }] }),
|
|
1197
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: 'dreamer candidates[0] badDecision empty string → emit dreamer_context_invalid',
|
|
1201
|
+
dreamerContentJson: JSON.stringify({ candidates: [{ badDecision: ' ', betterDecision: 'b', rationale: 'r' }] }),
|
|
1202
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
name: 'dreamer candidates[0] badDecision not a string (number) → emit dreamer_context_invalid',
|
|
1206
|
+
dreamerContentJson: JSON.stringify({ candidates: [{ badDecision: 42, betterDecision: 'b', rationale: 'r' }] }),
|
|
1207
|
+
expectedEvent: 'dreamer_context_invalid',
|
|
1208
|
+
},
|
|
1209
|
+
])('edge: $name', async ({ dreamerContentJson, expectedEvent }) => {
|
|
1210
|
+
const { result, capturedPrompt, emitTelemetry } = await runPri508EdgeCase({
|
|
1211
|
+
scribeArtifact: makeScribeWithDreamerRef(),
|
|
1212
|
+
dreamerArtifact: makeDreamerWithContentJson(dreamerContentJson),
|
|
1213
|
+
});
|
|
1214
|
+
// Best-effort: runner still succeeds even when dreamer context validation fails.
|
|
1215
|
+
expect(result.status).toBe('succeeded');
|
|
1216
|
+
expect(capturedPrompt).toBeDefined();
|
|
1217
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
1218
|
+
expect(parsed.dreamerContext).toBeUndefined();
|
|
1219
|
+
// rc-9: failure must be observable, not silent.
|
|
1220
|
+
expect(emitTelemetry).toHaveBeenCalledWith(expect.objectContaining({
|
|
1221
|
+
eventType: expect.stringContaining(expectedEvent),
|
|
1222
|
+
traceId: ARTIFICER_TASK_ID_PRI508,
|
|
1223
|
+
}));
|
|
1224
|
+
});
|
|
1225
|
+
it('edge: dreamer candidates[0] with only required 3 fields + no optional → dreamerContext resolved (no riskLevel/strategicPerspective)', async () => {
|
|
1226
|
+
// Validates the optional-field spread path: riskLevel/strategicPerspective absent
|
|
1227
|
+
// should still yield a valid dreamerContext with only the 3 required fields.
|
|
1228
|
+
const dreamerArtifact = makeDreamerWithContentJson(JSON.stringify({
|
|
1229
|
+
candidates: [{
|
|
1230
|
+
badDecision: 'bad',
|
|
1231
|
+
betterDecision: 'better',
|
|
1232
|
+
rationale: 'why',
|
|
1233
|
+
// riskLevel and strategicPerspective intentionally absent
|
|
1234
|
+
}],
|
|
1235
|
+
}));
|
|
1236
|
+
const { result, capturedPrompt } = await runPri508EdgeCase({
|
|
1237
|
+
scribeArtifact: makeScribeWithDreamerRef(),
|
|
1238
|
+
dreamerArtifact,
|
|
1239
|
+
});
|
|
1240
|
+
expect(result.status).toBe('succeeded');
|
|
1241
|
+
expect(capturedPrompt).toBeDefined();
|
|
1242
|
+
const parsed = JSON.parse(capturedPrompt);
|
|
1243
|
+
expect(parsed.dreamerContext).toBeDefined();
|
|
1244
|
+
expect(parsed.dreamerContext.badDecision).toBe('bad');
|
|
1245
|
+
expect(parsed.dreamerContext.betterDecision).toBe('better');
|
|
1246
|
+
expect(parsed.dreamerContext.rationale).toBe('why');
|
|
1247
|
+
expect(parsed.dreamerContext.riskLevel).toBeUndefined();
|
|
1248
|
+
expect(parsed.dreamerContext.strategicPerspective).toBeUndefined();
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
665
1251
|
//# sourceMappingURL=artificer-runner-vslice.test.js.map
|