mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 08:17:02 +00:00
fix(evidence): report partial workflow question publication (#8848)
* fix(evidence): report partial workflow question publication * fix: project nested recovery for caller lifetime
This commit is contained in:
1 parent
b97023809f
commit
6df2ac1d7e
9 files changed
+363
-27
No files matched your search
@@ -514,6 +514,7 @@ const runAnalysis = async (
|
||||
result: evidence.normalized_result,
|
||||
evidenceId: evidence.evidence_id,
|
||||
recordUnknown: (unknown) => session.recordUnknown(unknown),
|
||||
evidenceRetained: true,
|
||||
});
|
||||
if (!unknowns.ok)
|
||||
return err(unknowns.error.retainPartialObservation(evidence));
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
createAnalysisExecution,
|
||||
type AnalysisOperationPort,
|
||||
} from "./AnalysisProvider.js";
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisResourceConstraintError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { nativeApiBoundarySchema } from "../domain/native/nativeApiBoundary.js";
|
||||
import { projectNativeApiInspection } from "./native/NativeApiInspection.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
@@ -671,6 +675,71 @@ describe("workflow residual question retention", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a retained workflow and the committed prefix when Unknown publication fails", () => {
|
||||
const records = new InvestigationRecords();
|
||||
const result = { residual_unknowns: ["First?", "First?", "Second?"] };
|
||||
const evidence = createWorkflowEvidence({
|
||||
target: undefined,
|
||||
operation: "trace_feature",
|
||||
parameters: {},
|
||||
result,
|
||||
upstreamProfile: undefined,
|
||||
upstreamLimitations: [],
|
||||
});
|
||||
expect(records.recordEvidence(evidence).ok).toBe(true);
|
||||
const failure = new AnalysisResourceConstraintError(
|
||||
"record_unknown",
|
||||
"capacity",
|
||||
"full",
|
||||
null,
|
||||
);
|
||||
const outcome = recordWorkflowUnknowns({
|
||||
name: "trace_feature",
|
||||
result,
|
||||
evidenceId: evidence.evidence_id,
|
||||
evidenceRetained: true,
|
||||
recordUnknown: (input) =>
|
||||
input.question.startsWith("Second?")
|
||||
? err(failure)
|
||||
: records.recordUnknown(input),
|
||||
});
|
||||
expect(outcome.ok).toBe(false);
|
||||
if (outcome.ok) throw new Error("expected failure");
|
||||
const projected = projectAnalysisError(outcome.error);
|
||||
expect(projected.details?.workflow_unknown_publication).toEqual({
|
||||
evidence_id: evidence.evidence_id,
|
||||
evidence_retention: "retained",
|
||||
total_questions: 3,
|
||||
created_questions: 1,
|
||||
existing_questions: 1,
|
||||
next_question_index: 2,
|
||||
});
|
||||
expect(projected.remediation.action).toContain("get_evidence_bundle");
|
||||
const invocation = projectAnalysisError(
|
||||
outcome.error.retainPartialObservation(evidence),
|
||||
"invocation",
|
||||
);
|
||||
expect(invocation.details?.workflow_unknown_publication).toMatchObject({
|
||||
evidence_retention: "invocation-only",
|
||||
});
|
||||
expect(invocation.details?.partial_observation).toEqual(evidence);
|
||||
expect(invocation.remediation.action).not.toContain("get_evidence_bundle");
|
||||
expect(records.listUnknowns()).toHaveLength(1);
|
||||
expect(records.evidenceById(evidence.evidence_id)?.normalized_result).toEqual(
|
||||
result,
|
||||
);
|
||||
expect(
|
||||
recordWorkflowUnknowns({
|
||||
name: "trace_feature",
|
||||
result,
|
||||
evidenceId: evidence.evidence_id,
|
||||
evidenceRetained: true,
|
||||
recordUnknown: (input) => records.recordUnknown(input),
|
||||
}).ok,
|
||||
).toBe(true);
|
||||
expect(records.listUnknowns()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
" Which boundary? ",
|
||||
"\nWhich boundary?\t",
|
||||
|
||||
@@ -8,9 +8,10 @@ import type {
|
||||
AnalysisOperation,
|
||||
AnalysisOperationPort,
|
||||
} from "./AnalysisProvider.js";
|
||||
import { ok } from "../domain/result.js";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import type { EnhancedResult } from "./EnhancedToolTypes.js";
|
||||
import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { WorkflowSnapshotRecordInput } from "./binary/BinarySessionRecords.js";
|
||||
import {
|
||||
analysisProfileSchema,
|
||||
@@ -328,6 +329,8 @@ interface WorkflowUnknownInput {
|
||||
readonly result: JsonValue;
|
||||
readonly evidenceId: string;
|
||||
readonly recordUnknown: UnknownRegistryPort["recordUnknown"] | undefined;
|
||||
/** The caller confirmed the completed Evidence is in its investigation. */
|
||||
readonly evidenceRetained?: boolean;
|
||||
}
|
||||
|
||||
/** Retain residual workflow questions using the shared investigation policy. */
|
||||
@@ -336,6 +339,7 @@ export const recordWorkflowUnknowns = ({
|
||||
result,
|
||||
evidenceId,
|
||||
recordUnknown,
|
||||
evidenceRetained,
|
||||
}: WorkflowUnknownInput):
|
||||
| ReturnType<UnknownRegistryPort["recordUnknown"]>
|
||||
| { readonly ok: true; readonly value: null } => {
|
||||
@@ -350,7 +354,27 @@ export const recordWorkflowUnknowns = ({
|
||||
!Array.isArray(result.residual_unknowns)
|
||||
)
|
||||
return { ok: true, value: null };
|
||||
for (const question of result.residual_unknowns) {
|
||||
const questions = result.residual_unknowns;
|
||||
const total = questions.reduce<number>(
|
||||
(count, value) => count + (typeof value === "string" ? 1 : 0),
|
||||
0,
|
||||
);
|
||||
let created = 0;
|
||||
let existing = 0;
|
||||
const stopped = (error: AnalysisError, index: number) =>
|
||||
err(
|
||||
error.retainWorkflowUnknownPublication({
|
||||
evidence_id: evidenceId,
|
||||
evidence_retention:
|
||||
evidenceRetained === true ? "retained" : "unverified",
|
||||
total_questions: total,
|
||||
created_questions: created,
|
||||
existing_questions: existing,
|
||||
next_question_index: index,
|
||||
}),
|
||||
);
|
||||
for (let index = 0; index < questions.length; index += 1) {
|
||||
const question = questions[index];
|
||||
if (typeof question !== "string") continue;
|
||||
const input = workflowUnknownInput(evidenceId, {
|
||||
question,
|
||||
@@ -370,16 +394,15 @@ export const recordWorkflowUnknowns = ({
|
||||
],
|
||||
relationships: [],
|
||||
});
|
||||
if (!input.ok) return input;
|
||||
if (!input.ok) return stopped(input.error, index);
|
||||
const recorded = recordUnknown(input.value);
|
||||
if (
|
||||
!recorded.ok &&
|
||||
!(
|
||||
recorded.error instanceof UnknownRegistryError &&
|
||||
recorded.error.reason === "already-exists"
|
||||
)
|
||||
if (recorded.ok) created += 1;
|
||||
else if (
|
||||
recorded.error instanceof UnknownRegistryError &&
|
||||
recorded.error.reason === "already-exists"
|
||||
)
|
||||
return recorded;
|
||||
existing += 1;
|
||||
else return stopped(recorded.error, index);
|
||||
}
|
||||
return { ok: true, value: null };
|
||||
};
|
||||
Reference in new issue
Block a user