Files
reverse-engineering-anything/src/application/environment/environmentState.ts
T
Edgar ArakelyanandClaude Opus 5.5 6676582675 feat(environment)!: serve the environment state by default
environment-serve derives the environment state at startup by running the
environment-state view in-process on the capture with the manifest's
template rules, so an environment comes with its state and a user supplies
nothing extra. The derived Evidence is the same one inspect-analysis-view
produces by hand. --state is removed; --replay-only serves exactly the
stateless replay of main. When the state cannot be derived, or its catalog
does not fit the discovery budget, the server starts in replay mode, logs
the reason and reports it in the stop summary's `state`. Exported bundles
serve the state by default too: export derives it the same way, so the
verifier's tool count and checked answer match what is served, and the
README describes the served mode.

BREAKING CHANGE: environment-serve answers read tools from the derived
state and does not serve write tools unless --replay-only is given; the
stop summary reports `state` instead of `state_evidence_id`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 23:29:29 -07:00

249 lines
8.3 KiB
TypeScript

import type { AnalysisViewResult } from "../../domain/analysisView/analysisView.js";
import {
changesState,
type StateDocument,
type StateEntity,
type StateType,
type StateView,
} from "../../domain/analysisView/environmentStateRules.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type {
AnalysisInputIssue,
AnalysisResourceConstraintError,
} from "../../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
import { err, ok, type Result } from "../../domain/result.js";
import { inspectAnalysisViewValidated } from "../analysisView/AnalysisViewService.js";
import {
ENVIRONMENT_SERVE_OPERATION,
inputError,
parseEvidenceFile,
parseViewEvidence,
type EnvironmentManifestTool,
type EnvironmentSource,
} from "./environmentSource.js";
import {
environmentToolDefinitions,
type EnvironmentDataPlane,
type EnvironmentToolDefinition,
} from "./environmentTools.js";
type EnvironmentStateView = Extract<
AnalysisViewResult,
{ kind: "environment-state" }
>;
/** The initial state an environment-state view extracted for the served manifest, authenticated and cross-checked. */
export interface EnvironmentState {
readonly evidenceId: string;
readonly types: readonly StateType[];
/** Render plans keyed by tool name. */
readonly views: ReadonlyMap<string, StateView>;
readonly entities: readonly StateEntity[];
readonly documents: readonly StateDocument[];
}
/** Serving from the state is read-only: tools whose requests may change state are not served. */
const servedFromState = (tool: EnvironmentManifestTool): boolean =>
!changesState(tool.method, tool.path_template);
/** The tools an environment serves and the data plane its card declares. */
export interface ServedCatalog {
readonly tools: readonly EnvironmentToolDefinition[];
readonly dataPlane: EnvironmentDataPlane;
}
/** The catalog an environment serves: every manifest tool for replay, the read tools and the data plane from a state. */
export const servedCatalog = (
source: EnvironmentSource,
fromState: boolean,
name: string,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<ServedCatalog, AnalysisResourceConstraintError> => {
const dataPlane: EnvironmentDataPlane = fromState ? "state" : "none";
const tools = environmentToolDefinitions(
fromState
? {
...source.manifest,
tools: source.manifest.tools.filter(servedFromState),
}
: source.manifest,
source.evidenceId,
name,
{ operation, dataPlane },
);
return tools.ok ? ok({ tools: tools.value, dataPlane }) : tools;
};
const refusal = (
operation: string,
issue: AnalysisInputIssue,
userMessage: string,
): AnalysisError =>
inputError(
operation,
[{ ...issue, path: ["state", "normalized_result", ...issue.path] }],
userMessage,
);
/** Why the state does not belong to the served manifest and capture, if it does not. */
const stateMismatch = (
operation: string,
view: EnvironmentStateView,
source: EnvironmentSource,
): AnalysisError | undefined => {
if (view.parent_evidence_id !== source.evidenceId)
return refusal(
operation,
{
path: ["parent_evidence_id"],
reason: "invalid_value",
expected: source.evidenceId,
message: `The state cites parent Evidence ${view.parent_evidence_id}; the evidence file is ${source.evidenceId}.`,
},
"The state was extracted from a different capture than the supplied evidence file.",
);
const rules = source.manifest.view.template_rules;
if (
view.view.template_rules.length !== rules.length ||
view.view.template_rules.some((rule, index) => rule !== rules[index])
)
return refusal(
operation,
{
path: ["view", "template_rules"],
reason: "invalid_value",
expected: [...rules],
message:
"The state must be extracted with the manifest's template_rules, in the same order, so its tool names are the manifest's.",
},
"The state was extracted with different template rules than the manifest.",
);
const tools = new Set(source.manifest.tools.map((tool) => tool.name));
const index = view.views.findIndex((entry) => !tools.has(entry.tool));
return index < 0
? undefined
: refusal(
operation,
{
path: ["views", index, "tool"],
reason: "invalid_value",
message: "Every state view must name a tool of the served manifest.",
},
"The state names a tool the manifest does not have.",
);
};
/**
* Load the environment-state Evidence for an already loaded manifest and
* capture, refusing a state extracted from another capture or with other
* template rules; refusals name the command that loaded it.
*/
export const loadEnvironmentState = (
stateValue: unknown,
source: EnvironmentSource,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<EnvironmentState, AnalysisError> => {
const parsed = parseViewEvidence(
operation,
"state",
stateValue,
"environment-state",
);
if (!parsed.ok) return parsed;
const { view, evidenceId } = parsed.value;
const mismatch = stateMismatch(operation, view, source);
if (mismatch !== undefined) return err(mismatch);
return ok({
evidenceId,
types: view.types,
views: new Map(view.views.map((entry) => [entry.tool, entry])),
entities: view.entities,
documents: view.documents,
});
};
/** Derive the state the capture observed with the manifest's rules, projected as retained Evidence, and check it as a supplied state. */
export const deriveEnvironmentState = (
source: EnvironmentSource,
captureValue: unknown,
operation: string = ENVIRONMENT_SERVE_OPERATION,
): Result<EnvironmentState, AnalysisError> => {
const capture = parseEvidenceFile(operation, "evidence", captureValue);
if (!capture.ok) return capture;
const captureId = capture.value.evidence_id;
const projected = inspectAnalysisViewValidated(
{
source: { kind: "retained-evidence", evidence_id: captureId },
view: {
kind: "environment-state",
template_rules: [...source.manifest.view.template_rules],
},
},
(evidenceId) => (evidenceId === captureId ? capture.value : undefined),
);
return projected.ok
? loadEnvironmentState(projected.value, source, operation)
: projected;
};
/** What a command reports about the state: the state Evidence it serves, or why it serves replay. */
export type StateServingReport =
| { readonly served: true; readonly evidence_id: string }
| { readonly served: false; readonly reason: "replay-only" }
| {
readonly served: false;
readonly reason: "unavailable";
readonly code: string;
readonly message: string;
};
/** The state an environment serves, if any, and how its command reports it. */
export interface ServedState {
readonly state: EnvironmentState | undefined;
readonly report: StateServingReport;
}
/** What preparing the state yields: served or unavailable, never replay-only, which only the serve command chooses. */
export interface PreparedState extends ServedState {
readonly report: Exclude<
StateServingReport,
{ readonly reason: "replay-only" }
>;
}
const unavailable = (error: AnalysisError): PreparedState => {
const { code, message } = projectAnalysisError(error);
return {
state: undefined,
report: { served: false, reason: "unavailable", code, message },
};
};
/** Serve the derived state when its catalog fits under the served name; otherwise serve replay and report why. */
export const prepareServedState = (
source: EnvironmentSource,
captureValue: unknown,
options: {
readonly name?: string | undefined;
readonly operation?: string;
} = {},
): PreparedState => {
const operation = options.operation ?? ENVIRONMENT_SERVE_OPERATION;
// The state catalog depends only on the manifest and name, so checking it first spares deriving a state that cannot be served.
const catalog = servedCatalog(
source,
true,
options.name ?? source.manifest.name,
operation,
);
if (!catalog.ok) return unavailable(catalog.error);
const derived = deriveEnvironmentState(source, captureValue, operation);
return derived.ok
? {
state: derived.value,
report: { served: true, evidence_id: derived.value.evidenceId },
}
: unavailable(derived.error);
};