mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 13:26:10 +00:00
Use one named input-schema projection for clients and catalog identity, preserve dialect declarations, and bound compact schema depth. Keep canonical SDK output validation when output advertisements are disabled. Release request-owned state on cancellation and carry upstream limitations through composed workflows. Preserve doctor full-output envelopes and validate navigation facts at their owner. Reviewed fixes: #2088, #2108, #2513, #2684, #2693, #2695, #2836, #2864, #2868, #2877, #2933, #2998, #3002, #3015. Validation: focused schema, SDK output-validation, context, and workflow tests passed; no schema dialect or canonical output validation was relaxed.
207 lines
6.7 KiB
TypeScript
207 lines
6.7 KiB
TypeScript
import type {
|
|
AnalysisExecution,
|
|
AnalysisOperation,
|
|
AnalysisOperationPort,
|
|
} from "./AnalysisProvider.js";
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import { AnalysisOutputError } from "../domain/analysisErrorCore.js";
|
|
import type { JsonValue } from "../domain/jsonValue.js";
|
|
import { analysisBookmarkSchema } from "../domain/hopperValues.js";
|
|
import { resolveAnalysisDocument } from "./AnalysisDocument.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
|
|
type Facet =
|
|
| { readonly state: "available"; readonly value: JsonValue }
|
|
| {
|
|
readonly state: "unavailable";
|
|
readonly reason: string;
|
|
readonly remediation: string;
|
|
};
|
|
|
|
const parameters = (
|
|
document: string | undefined,
|
|
address?: string,
|
|
): Readonly<Record<string, JsonValue>> => ({
|
|
...(document === undefined ? {} : { document }),
|
|
...(address === undefined ? {} : { address }),
|
|
});
|
|
|
|
const executionOptions = (
|
|
signal: AbortSignal | undefined,
|
|
): { readonly signal?: AbortSignal } =>
|
|
signal === undefined ? {} : { signal };
|
|
|
|
/** Compose the provider's volatile selection state into one coherent query. */
|
|
export const getNavigationContext = async (
|
|
analysis: AnalysisOperationPort,
|
|
input: { readonly document?: string | undefined },
|
|
signal?: AbortSignal,
|
|
): Promise<Result<JsonValue, AnalysisError>> => {
|
|
const document =
|
|
input.document ??
|
|
(await analysis.execute("current_document", {}, executionOptions(signal)));
|
|
if (typeof document !== "string" && !document.ok) return document;
|
|
const documentName =
|
|
typeof document === "string" ? document : document.value.result;
|
|
if (typeof documentName !== "string")
|
|
return err(
|
|
new AnalysisOutputError(
|
|
"current_document",
|
|
"expected a document identity string",
|
|
),
|
|
);
|
|
const documentArgs = parameters(documentName);
|
|
const address = await analysis.execute(
|
|
"current_address",
|
|
documentArgs,
|
|
executionOptions(signal),
|
|
);
|
|
if (!address.ok) return address;
|
|
if (typeof address.value.result !== "string")
|
|
return err(
|
|
new AnalysisOutputError("current_address", "expected an address string"),
|
|
);
|
|
const procedure = await analysis.execute(
|
|
"resolve_containing_procedure",
|
|
parameters(documentName, address.value.result),
|
|
executionOptions(signal),
|
|
);
|
|
if (!procedure.ok) {
|
|
if (!isMissingCurrentProcedure(procedure.error)) return procedure;
|
|
return ok({
|
|
document: documentName,
|
|
address: address.value.result,
|
|
procedure: null,
|
|
});
|
|
}
|
|
return ok({
|
|
document: documentName,
|
|
address: address.value.result,
|
|
procedure: containingProcedureName(procedure.value.result),
|
|
});
|
|
};
|
|
|
|
/** Inspect bounded provider-neutral facets at one explicit reproducible address. */
|
|
export const inspectAddressContext = async (
|
|
analysis: AnalysisOperationPort,
|
|
input: { readonly address: string; readonly document?: string | undefined },
|
|
signal?: AbortSignal,
|
|
): Promise<Result<JsonValue, AnalysisError>> => {
|
|
const document = await resolveAnalysisDocument(
|
|
analysis,
|
|
input.document,
|
|
signal,
|
|
);
|
|
if (!document.ok) return document;
|
|
const args = parameters(document.value, input.address);
|
|
const operations = [
|
|
"address_name",
|
|
"resolve_containing_procedure",
|
|
"comment",
|
|
"inline_comment",
|
|
"list_bookmarks",
|
|
] as const satisfies readonly AnalysisOperation[];
|
|
const results = await Promise.all(
|
|
operations.map((operation) =>
|
|
analysis.execute(
|
|
operation,
|
|
operation === "list_bookmarks" ? parameters(document.value) : args,
|
|
executionOptions(signal),
|
|
),
|
|
),
|
|
);
|
|
for (const result of results) {
|
|
if (!result.ok && isRequestLevelFailure(result.error)) return result;
|
|
}
|
|
const name = facet(results[0], operations[0]);
|
|
const procedure = facet(results[1], operations[1]);
|
|
const comment = facet(results[2], operations[2]);
|
|
const inlineComment = facet(results[3], operations[3]);
|
|
const bookmarks = facet(results[4], operations[4]);
|
|
return ok({
|
|
address: input.address,
|
|
document: document.value,
|
|
name,
|
|
procedure,
|
|
comment,
|
|
inline_comment: inlineComment,
|
|
bookmarks:
|
|
bookmarks.state === "available"
|
|
? matchingBookmarks(
|
|
bookmarks.value,
|
|
resolvedAddress(results[1], input.address),
|
|
)
|
|
: bookmarks,
|
|
});
|
|
};
|
|
|
|
const facet = (
|
|
result: Result<AnalysisExecution, AnalysisError> | undefined,
|
|
operation: AnalysisOperation,
|
|
): Facet =>
|
|
result?.ok === true
|
|
? { state: "available", value: result.value.result }
|
|
: {
|
|
state: "unavailable",
|
|
reason: result?.error.message ?? `${operation} returned no result`,
|
|
remediation: `Call ${operation} directly to inspect provider availability and diagnostics.`,
|
|
};
|
|
|
|
const isMissingCurrentProcedure = (error: AnalysisError): boolean =>
|
|
/(?:no procedure exists|not in a procedure)/iu.test(error.message);
|
|
|
|
const isRequestLevelFailure = (error: AnalysisError): boolean =>
|
|
error._tag === "AnalysisCancelledError" ||
|
|
error._tag === "AnalysisTimeoutError" ||
|
|
error._tag === "NoBinaryOpenError";
|
|
|
|
const containingProcedureName = (value: JsonValue): string | null => {
|
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
return null;
|
|
if (value.found !== true) return null;
|
|
const procedure = value.procedure;
|
|
if (
|
|
typeof procedure !== "object" ||
|
|
procedure === null ||
|
|
Array.isArray(procedure)
|
|
)
|
|
return null;
|
|
return typeof procedure.name === "string" ? procedure.name : null;
|
|
};
|
|
|
|
const resolvedAddress = (
|
|
result: Result<AnalysisExecution, AnalysisError> | undefined,
|
|
fallback: string,
|
|
): string => {
|
|
if (result?.ok !== true) return fallback;
|
|
const value = result.value.result;
|
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
return fallback;
|
|
return typeof value.query_address === "string"
|
|
? value.query_address
|
|
: fallback;
|
|
};
|
|
|
|
const matchingBookmarks = (value: JsonValue, address: string): Facet => {
|
|
if (!Array.isArray(value))
|
|
return {
|
|
state: "unavailable",
|
|
reason: "list_bookmarks returned malformed output; expected an array.",
|
|
remediation:
|
|
"Retry list_bookmarks directly and report the provider output shape if it remains malformed.",
|
|
};
|
|
const parsed = analysisBookmarkSchema.array().safeParse(value);
|
|
if (!parsed.success)
|
|
return {
|
|
state: "unavailable",
|
|
reason:
|
|
"list_bookmarks returned malformed entries; expected an address string and a string or null name.",
|
|
remediation:
|
|
"Retry list_bookmarks directly and report the provider output shape if it remains malformed.",
|
|
};
|
|
return {
|
|
state: "available",
|
|
value: parsed.data.filter((bookmark) => bookmark.address === address),
|
|
};
|
|
};
|