662 lines
21 KiB
TypeScript
662 lines
21 KiB
TypeScript
import { z } from "zod";
|
|
import { jsonValueSchema } from "../jsonValue.js";
|
|
|
|
import { normalizationSchema } from "./processScenario.js";
|
|
import { collectProcessCaptureIssues } from "./processCaptureValidation.js";
|
|
|
|
export * from "./processScenario.js";
|
|
|
|
/** Normalized raw PTY chunk, preserving transport-level output differences. */
|
|
export interface TerminalFrame {
|
|
readonly sequence: number;
|
|
readonly at_ms: number;
|
|
readonly data: string;
|
|
}
|
|
|
|
/** Serialized terminal state after interpreting control and resize sequences. */
|
|
export interface RenderedTerminalFrame {
|
|
readonly sequence: number;
|
|
readonly at_ms: number;
|
|
readonly columns: number;
|
|
readonly rows: number;
|
|
readonly cursor_x: number;
|
|
readonly cursor_y: number;
|
|
readonly active_buffer: "normal" | "alternate";
|
|
readonly lines: readonly string[];
|
|
readonly serialized_state: string;
|
|
}
|
|
|
|
/** Scheduled terminal interaction with its observed dispatch outcome. */
|
|
export interface InteractionEvent {
|
|
readonly sequence: number;
|
|
readonly scheduled_at_ms: number;
|
|
readonly dispatched_at_ms: number;
|
|
readonly type: "input" | "resize" | "signal";
|
|
readonly data: string;
|
|
readonly outcome: "dispatched" | "target_exited" | "failed";
|
|
}
|
|
|
|
/** One filesystem state used for before/after comparison. */
|
|
interface FileStateIdentity {
|
|
readonly path: string;
|
|
readonly mode: number;
|
|
readonly size: number;
|
|
}
|
|
|
|
export type FileState = FileStateIdentity &
|
|
(
|
|
| {
|
|
readonly type: "file";
|
|
readonly sha256: string | null;
|
|
readonly symlink_target: null;
|
|
}
|
|
| {
|
|
readonly type: "symlink";
|
|
readonly sha256: null;
|
|
readonly symlink_target: string;
|
|
}
|
|
| {
|
|
readonly type: "directory" | "other";
|
|
readonly sha256: null;
|
|
readonly symlink_target: null;
|
|
}
|
|
);
|
|
|
|
interface FileEffectIdentity {
|
|
readonly path: string;
|
|
}
|
|
|
|
type FileEffect = FileEffectIdentity &
|
|
(
|
|
| {
|
|
readonly status: "created";
|
|
readonly before: null;
|
|
readonly after: FileState;
|
|
}
|
|
| {
|
|
readonly status: "deleted";
|
|
readonly before: FileState;
|
|
readonly after: null;
|
|
}
|
|
| {
|
|
readonly status: "modified" | "unchanged";
|
|
readonly before: FileState;
|
|
readonly after: FileState;
|
|
}
|
|
);
|
|
|
|
/** A sampled owned-process observation; sampling cannot prove syscall completeness. */
|
|
export interface ProcessSample {
|
|
readonly at_ms: number;
|
|
readonly pid: number;
|
|
readonly parent_pid: number;
|
|
readonly command: string;
|
|
readonly process_group_id: number | null;
|
|
readonly session_id: number | null;
|
|
}
|
|
|
|
/** Initial or final filesystem snapshot selected for observation. */
|
|
export interface FilesystemCheckpoint {
|
|
readonly name: string;
|
|
readonly at_ms: number;
|
|
readonly files: readonly FileState[];
|
|
readonly effects: readonly FileEffect[];
|
|
readonly truncated: boolean;
|
|
}
|
|
|
|
/** Capture collection whose members participate in global observation order. */
|
|
export const PROCESS_CAPTURE_EVENT_COLLECTIONS = [
|
|
"frames",
|
|
"rendered_frames",
|
|
"interaction_events",
|
|
"lifecycle",
|
|
"process_samples",
|
|
"filesystem_checkpoints",
|
|
] as const;
|
|
|
|
export type ProcessCaptureEventCollection =
|
|
(typeof PROCESS_CAPTURE_EVENT_COLLECTIONS)[number];
|
|
|
|
/** One reference from global observation order into a capture collection. */
|
|
export interface ProcessCaptureEventJournalEntry {
|
|
readonly capture_order: number;
|
|
readonly collection: ProcessCaptureEventCollection;
|
|
readonly index: number;
|
|
}
|
|
|
|
/** Shared observation callback used by every process-capture producer. */
|
|
export type RecordProcessCaptureEvent = (
|
|
collection: ProcessCaptureEventCollection,
|
|
index: number,
|
|
) => void;
|
|
|
|
/** Observed process-tree settlement and the cleanup required by that state. */
|
|
export type ProcessSettlement =
|
|
| VerifiedProcessSettlement
|
|
| {
|
|
readonly state: "quiesced";
|
|
readonly elapsed_ms: number;
|
|
readonly cleanup_outcome: "failed";
|
|
};
|
|
|
|
export type VerifiedProcessSettlement =
|
|
| {
|
|
readonly state: "quiesced";
|
|
readonly elapsed_ms: number;
|
|
readonly cleanup_outcome: "not_required";
|
|
}
|
|
| {
|
|
readonly state: "alive_at_deadline" | "unverifiable";
|
|
readonly elapsed_ms: number;
|
|
readonly cleanup_outcome: "cleaned" | "failed";
|
|
};
|
|
|
|
/**
|
|
* Process capture observation set.
|
|
*
|
|
* `truncated` and `residual_unknowns` are semantic evidence: consumers must not
|
|
* infer equivalence from matching bounded observations when either is present.
|
|
*/
|
|
export interface UnverifiedProcessCapture {
|
|
readonly manifest: {
|
|
readonly rea_version: string;
|
|
readonly provider_version: string;
|
|
readonly platform: string;
|
|
readonly architecture: string;
|
|
readonly pty_backend: "node-pty";
|
|
readonly started_at: string;
|
|
readonly completed_at: string;
|
|
readonly scenario: Readonly<Record<string, unknown>>;
|
|
readonly comparison_contract: Readonly<Record<string, unknown>>;
|
|
readonly full_scenario_sha256: string;
|
|
readonly comparison_contract_sha256: string;
|
|
/** Digest of the selected file sampled before spawn, when readable. */
|
|
readonly selected_executable_sha256: string | null;
|
|
/** Digest associated with the launch only when path metadata stayed stable across spawn. */
|
|
readonly executable_sha256: string | null;
|
|
/**
|
|
* Digest recorded by the original v3 format before selected and launch
|
|
* executable digests were distinguished. Kept only on migrated captures.
|
|
*/
|
|
readonly legacy_executable_sha256?: string | undefined;
|
|
readonly executable_identity: {
|
|
readonly state: "path_metadata_unchanged" | "unknown";
|
|
readonly reason: string | null;
|
|
};
|
|
readonly normalization_sha256: string;
|
|
};
|
|
readonly normalization: z.infer<typeof normalizationSchema>;
|
|
readonly frames: readonly TerminalFrame[];
|
|
readonly rendered_frames: readonly RenderedTerminalFrame[];
|
|
readonly interaction_events: readonly InteractionEvent[];
|
|
readonly exit: {
|
|
readonly code: number | null;
|
|
readonly signal: number | null;
|
|
readonly reason: "exited" | "timeout" | "idle_timeout";
|
|
};
|
|
readonly settlement: VerifiedProcessSettlement;
|
|
readonly process_samples: readonly ProcessSample[];
|
|
readonly filesystem_checkpoints: readonly FilesystemCheckpoint[];
|
|
/**
|
|
* Global observation order across independently recorded collections.
|
|
*
|
|
* Absence is accepted for captures written before this journal existed.
|
|
*/
|
|
readonly event_journal?: readonly ProcessCaptureEventJournalEntry[];
|
|
readonly files_before: readonly FileState[];
|
|
readonly files_after: readonly FileState[];
|
|
readonly filesystem_effects: readonly FileEffect[];
|
|
readonly truncated: boolean;
|
|
readonly limitations: readonly string[];
|
|
readonly residual_unknowns: readonly {
|
|
readonly scope:
|
|
| "terminal"
|
|
| "interaction"
|
|
| "exit"
|
|
| "process"
|
|
| "filesystem"
|
|
| "cleanup"
|
|
| "network"
|
|
| "environment";
|
|
readonly reason: string;
|
|
}[];
|
|
readonly cleanup: {
|
|
readonly owned_process_group: "verified";
|
|
readonly temporary_root: "removed";
|
|
};
|
|
}
|
|
|
|
const fileStateShape = {
|
|
path: z.string(),
|
|
mode: z.number().int().nonnegative(),
|
|
size: z.number().int().nonnegative(),
|
|
};
|
|
|
|
/** A resource cleanup result retained when observations cannot be verified. */
|
|
export interface ProcessCaptureResourceCleanup {
|
|
readonly state: "cleaned" | "failed" | "unverified" | "not_required";
|
|
readonly reason: string | null;
|
|
}
|
|
|
|
export interface ProcessCaptureCleanupReport {
|
|
readonly owned_process_group: ProcessCaptureResourceCleanup;
|
|
readonly terminal_renderer: ProcessCaptureResourceCleanup;
|
|
readonly temporary_root: ProcessCaptureResourceCleanup;
|
|
}
|
|
|
|
/** One observation field that may be unavailable when capture completion fails. */
|
|
export type PartialProcessObservationField<T> =
|
|
| { readonly state: "available"; readonly value: T }
|
|
| { readonly state: "unavailable"; readonly reason: string };
|
|
|
|
/** Snapshot facts available before filesystem comparison can be completed. */
|
|
/** Snapshot facts available before filesystem comparison can be completed. */
|
|
export interface PartialFilesystemSnapshot {
|
|
readonly files: readonly FileState[];
|
|
readonly truncated: boolean;
|
|
}
|
|
|
|
/** Available and unavailable fields when a run never becomes a full capture. */
|
|
/** Available and unavailable fields when a run never becomes a full capture. */
|
|
export interface IncompleteProcessCaptureObservations {
|
|
readonly target_pid: PartialProcessObservationField<number>;
|
|
readonly frames: PartialProcessObservationField<readonly TerminalFrame[]>;
|
|
readonly rendered_frames: PartialProcessObservationField<
|
|
readonly RenderedTerminalFrame[]
|
|
>;
|
|
readonly interaction_events: PartialProcessObservationField<
|
|
readonly InteractionEvent[]
|
|
>;
|
|
readonly exit: PartialProcessObservationField<{
|
|
readonly code: number | null;
|
|
readonly signal: number | null;
|
|
readonly reason: "exited" | "timeout" | "idle_timeout" | "cancelled";
|
|
}>;
|
|
readonly settlement: PartialProcessObservationField<{
|
|
readonly state: "quiesced" | "alive_at_deadline" | "unverifiable";
|
|
readonly elapsed_ms: number;
|
|
}>;
|
|
readonly process_samples: PartialProcessObservationField<
|
|
readonly ProcessSample[]
|
|
>;
|
|
readonly filesystem_snapshots: {
|
|
readonly before: PartialProcessObservationField<PartialFilesystemSnapshot>;
|
|
readonly after: PartialProcessObservationField<PartialFilesystemSnapshot>;
|
|
};
|
|
readonly event_journal: PartialProcessObservationField<
|
|
readonly ProcessCaptureEventJournalEntry[]
|
|
>;
|
|
readonly manifest: PartialProcessObservationField<
|
|
UnverifiedProcessCapture["manifest"]
|
|
>;
|
|
}
|
|
|
|
const fileStateSchema = z.discriminatedUnion("type", [
|
|
z.object({
|
|
...fileStateShape,
|
|
type: z.literal("file"),
|
|
sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.nullable(),
|
|
symlink_target: z.null(),
|
|
}),
|
|
z.object({
|
|
...fileStateShape,
|
|
type: z.literal("symlink"),
|
|
sha256: z.null(),
|
|
symlink_target: z.string(),
|
|
}),
|
|
z.object({
|
|
...fileStateShape,
|
|
type: z.enum(["directory", "other"]),
|
|
sha256: z.null(),
|
|
symlink_target: z.null(),
|
|
}),
|
|
]);
|
|
const fileEffectSchema = z.discriminatedUnion("status", [
|
|
z.object({
|
|
path: z.string(),
|
|
status: z.literal("created"),
|
|
before: z.null(),
|
|
after: fileStateSchema,
|
|
}),
|
|
z.object({
|
|
path: z.string(),
|
|
status: z.literal("deleted"),
|
|
before: fileStateSchema,
|
|
after: z.null(),
|
|
}),
|
|
z.object({
|
|
path: z.string(),
|
|
status: z.enum(["modified", "unchanged"]),
|
|
before: fileStateSchema,
|
|
after: fileStateSchema,
|
|
}),
|
|
]);
|
|
/** Exact serialized shape of a process capture. */
|
|
const processSettlementSchema = z.discriminatedUnion("state", [
|
|
z.object({
|
|
state: z.literal("quiesced"),
|
|
elapsed_ms: z.number().int().nonnegative(),
|
|
cleanup_outcome: z.enum(["not_required", "failed"]),
|
|
}),
|
|
z.object({
|
|
state: z.enum(["alive_at_deadline", "unverifiable"]),
|
|
elapsed_ms: z.number().int().nonnegative(),
|
|
cleanup_outcome: z.enum(["cleaned", "failed"]),
|
|
}),
|
|
]);
|
|
|
|
const processCaptureShapeSchema = z.strictObject({
|
|
manifest: z.strictObject({
|
|
rea_version: z.string().min(1),
|
|
provider_version: z.string().min(1),
|
|
platform: z.string().min(1),
|
|
architecture: z.string().min(1),
|
|
pty_backend: z.literal("node-pty"),
|
|
started_at: z.iso.datetime(),
|
|
completed_at: z.iso.datetime(),
|
|
scenario: z.record(z.string(), jsonValueSchema),
|
|
comparison_contract: z.record(z.string(), jsonValueSchema),
|
|
full_scenario_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
comparison_contract_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
selected_executable_sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.nullable(),
|
|
executable_sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.nullable(),
|
|
legacy_executable_sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.optional(),
|
|
executable_identity: z.strictObject({
|
|
state: z.enum(["path_metadata_unchanged", "unknown"]),
|
|
reason: z.string().nullable(),
|
|
}),
|
|
normalization_sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
}),
|
|
normalization: normalizationSchema,
|
|
frames: z.array(
|
|
z.object({
|
|
sequence: z.number().int().nonnegative(),
|
|
at_ms: z.number().int().nonnegative(),
|
|
data: z.string(),
|
|
}),
|
|
),
|
|
rendered_frames: z.array(
|
|
z.object({
|
|
sequence: z.number().int().nonnegative(),
|
|
at_ms: z.number().int().nonnegative(),
|
|
columns: z.number().int().positive(),
|
|
rows: z.number().int().positive(),
|
|
cursor_x: z.number().int().nonnegative(),
|
|
cursor_y: z.number().int().nonnegative(),
|
|
active_buffer: z.enum(["normal", "alternate"]),
|
|
lines: z.array(z.string()),
|
|
serialized_state: z.string(),
|
|
}),
|
|
),
|
|
interaction_events: z.array(
|
|
z.object({
|
|
sequence: z.number().int().nonnegative(),
|
|
scheduled_at_ms: z.number().int().nonnegative(),
|
|
dispatched_at_ms: z.number().int().nonnegative(),
|
|
type: z.enum(["input", "resize", "signal"]),
|
|
data: z.string(),
|
|
outcome: z.enum(["dispatched", "target_exited", "failed"]),
|
|
}),
|
|
),
|
|
exit: z.object({
|
|
code: z.number().int().nullable(),
|
|
signal: z.number().int().nullable(),
|
|
reason: z.enum(["exited", "timeout", "idle_timeout"]),
|
|
}),
|
|
settlement: z.discriminatedUnion("state", [
|
|
z.object({
|
|
state: z.literal("quiesced"),
|
|
elapsed_ms: z.number().int().nonnegative(),
|
|
cleanup_outcome: z.literal("not_required"),
|
|
}),
|
|
z.object({
|
|
state: z.enum(["alive_at_deadline", "unverifiable"]),
|
|
elapsed_ms: z.number().int().nonnegative(),
|
|
cleanup_outcome: z.enum(["cleaned", "failed"]),
|
|
}),
|
|
]),
|
|
process_samples: z.array(
|
|
z.object({
|
|
at_ms: z.number().int().nonnegative(),
|
|
pid: z.number().int().positive(),
|
|
parent_pid: z.number().int().nonnegative(),
|
|
command: z.string(),
|
|
process_group_id: z.number().int().positive().nullable(),
|
|
session_id: z.number().int().nonnegative().nullable(),
|
|
}),
|
|
),
|
|
filesystem_checkpoints: z.array(
|
|
z.object({
|
|
name: z.enum(["before", "after_settlement"]),
|
|
at_ms: z.number().int().nonnegative(),
|
|
files: z.array(fileStateSchema),
|
|
effects: z.array(fileEffectSchema),
|
|
truncated: z.boolean(),
|
|
}),
|
|
),
|
|
event_journal: z
|
|
.array(
|
|
z.object({
|
|
capture_order: z.number().int().nonnegative(),
|
|
collection: z.enum(PROCESS_CAPTURE_EVENT_COLLECTIONS),
|
|
index: z.number().int().nonnegative(),
|
|
}),
|
|
)
|
|
.default([]),
|
|
files_before: z.array(fileStateSchema),
|
|
files_after: z.array(fileStateSchema),
|
|
filesystem_effects: z.array(fileEffectSchema),
|
|
truncated: z.boolean(),
|
|
limitations: z.array(z.string()),
|
|
residual_unknowns: z.array(
|
|
z.object({
|
|
scope: z.enum([
|
|
"terminal",
|
|
"interaction",
|
|
"exit",
|
|
"process",
|
|
"filesystem",
|
|
"cleanup",
|
|
"network",
|
|
"environment",
|
|
]),
|
|
reason: z.string(),
|
|
}),
|
|
),
|
|
cleanup: z.object({
|
|
owned_process_group: z.literal("verified"),
|
|
temporary_root: z.literal("removed"),
|
|
}),
|
|
});
|
|
|
|
const processCleanupResourceSchema = z.strictObject({
|
|
state: z.enum(["cleaned", "failed", "unverified", "not_required"]),
|
|
reason: z.string().nullable(),
|
|
});
|
|
|
|
const partialObservationFieldSchema = <Schema extends z.ZodType>(
|
|
value: Schema,
|
|
) =>
|
|
z.discriminatedUnion("state", [
|
|
z.strictObject({ state: z.literal("available"), value }),
|
|
z.strictObject({ state: z.literal("unavailable"), reason: z.string() }),
|
|
]);
|
|
|
|
const incompleteProcessCaptureObservationsSchema = z.strictObject({
|
|
target_pid: partialObservationFieldSchema(z.number().int().positive()),
|
|
frames: partialObservationFieldSchema(processCaptureShapeSchema.shape.frames),
|
|
rendered_frames: partialObservationFieldSchema(
|
|
processCaptureShapeSchema.shape.rendered_frames,
|
|
),
|
|
interaction_events: partialObservationFieldSchema(
|
|
processCaptureShapeSchema.shape.interaction_events,
|
|
),
|
|
exit: partialObservationFieldSchema(
|
|
z.strictObject({
|
|
code: z.number().int().nullable(),
|
|
signal: z.number().int().nullable(),
|
|
reason: z.enum(["exited", "timeout", "idle_timeout", "cancelled"]),
|
|
}),
|
|
),
|
|
settlement: partialObservationFieldSchema(
|
|
z.strictObject({
|
|
state: z.enum(["quiesced", "alive_at_deadline", "unverifiable"]),
|
|
elapsed_ms: z.number().int().nonnegative(),
|
|
}),
|
|
),
|
|
process_samples: partialObservationFieldSchema(
|
|
processCaptureShapeSchema.shape.process_samples,
|
|
),
|
|
filesystem_snapshots: z.strictObject({
|
|
before: partialObservationFieldSchema(
|
|
z.strictObject({
|
|
files: z.array(fileStateSchema),
|
|
truncated: z.boolean(),
|
|
}),
|
|
),
|
|
after: partialObservationFieldSchema(
|
|
z.strictObject({
|
|
files: z.array(fileStateSchema),
|
|
truncated: z.boolean(),
|
|
}),
|
|
),
|
|
}),
|
|
event_journal: partialObservationFieldSchema(
|
|
processCaptureShapeSchema.shape.event_journal,
|
|
),
|
|
manifest: partialObservationFieldSchema(
|
|
processCaptureShapeSchema.shape.manifest,
|
|
),
|
|
});
|
|
|
|
const partialCleanupReportSchema = z.strictObject({
|
|
owned_process_group: processCleanupResourceSchema,
|
|
terminal_renderer: processCleanupResourceSchema,
|
|
temporary_root: processCleanupResourceSchema,
|
|
});
|
|
const partialObservationMetadataSchema = z.strictObject({
|
|
cleanup: partialCleanupReportSchema,
|
|
execution_failure: z.string().nullable(),
|
|
});
|
|
const completedCaptureObservationSchema = z.strictObject({
|
|
capture: processCaptureShapeSchema
|
|
.omit({ cleanup: true })
|
|
.extend({ settlement: processSettlementSchema }),
|
|
});
|
|
const incompleteCaptureObservationSchema = z.strictObject({
|
|
observations: incompleteProcessCaptureObservationsSchema,
|
|
});
|
|
|
|
/** Validated, non-comparable process observations attached to cleanup errors. */
|
|
export const partialProcessCaptureObservationSchema = z
|
|
.union([
|
|
completedCaptureObservationSchema.extend(
|
|
partialObservationMetadataSchema.shape,
|
|
),
|
|
incompleteCaptureObservationSchema.extend(
|
|
partialObservationMetadataSchema.shape,
|
|
),
|
|
])
|
|
.superRefine((partial, context) => {
|
|
if (
|
|
!Object.values(partial.cleanup).some(
|
|
({ state }) => state === "failed" || state === "unverified",
|
|
)
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["cleanup"],
|
|
message: "partial observations require incomplete resource cleanup",
|
|
});
|
|
|
|
if ("observations" in partial) {
|
|
const { observations } = partial;
|
|
if (
|
|
![
|
|
observations.frames,
|
|
observations.interaction_events,
|
|
observations.process_samples,
|
|
observations.filesystem_snapshots.before,
|
|
observations.event_journal,
|
|
].some(({ state }) => state === "available")
|
|
)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["observations"],
|
|
message: "incomplete observations contain no collected evidence",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { capture } = partial;
|
|
const settlement =
|
|
capture.settlement.state === "quiesced"
|
|
? {
|
|
...capture.settlement,
|
|
cleanup_outcome: "not_required" as const,
|
|
}
|
|
: { ...capture.settlement, cleanup_outcome: "cleaned" as const };
|
|
const validationCapture: UnverifiedProcessCapture = {
|
|
...capture,
|
|
settlement,
|
|
cleanup: {
|
|
owned_process_group: "verified",
|
|
temporary_root: "removed",
|
|
},
|
|
};
|
|
for (const issue of collectProcessCaptureIssues(validationCapture))
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["capture", ...issue.path.split(".")],
|
|
message: issue.message,
|
|
});
|
|
});
|
|
|
|
/** One collected payload: completed capture data or explicit unavailable fields. */
|
|
export type PartialProcessCaptureObservation = z.infer<
|
|
typeof partialProcessCaptureObservationSchema
|
|
>;
|
|
|
|
/** Exact serialized shape plus all process-capture semantic invariants. */
|
|
export const processCaptureSchema = processCaptureShapeSchema
|
|
.superRefine((capture, context) => {
|
|
if (capture.settlement.cleanup_outcome === "failed")
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["settlement", "cleanup_outcome"],
|
|
message: "verified captures cannot contain failed cleanup",
|
|
});
|
|
for (const issue of collectProcessCaptureIssues(capture))
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: issue.path.split("."),
|
|
message: issue.message,
|
|
});
|
|
})
|
|
.describe(
|
|
"The capture must preserve its canonical scenario, comparison, and normalization SHA-256 commitments; ordered capture timestamps and contiguous sequence numbers; before and final filesystem snapshots with truncation propagated; and exit-code consistency with deadline termination. When parsing older input without an event journal, REA supplies an empty journal; empty journals are valid. A non-empty journal must reference every captured observation exactly once with unique in-range references. These cross-field invariants are checked by REA after capture.",
|
|
);
|
|
|
|
export { parseProcessCapture } from "./processCaptureParsing.js";
|
|
export type { ProcessCapture } from "./processCaptureParsing.js";
|
|
|
|
export {
|
|
compareProcessCaptures,
|
|
comparisonStatusSchema,
|
|
deriveProcessComparisonStatus,
|
|
PROCESS_COMPARISON_DIMENSIONS,
|
|
processCaptureComparisonSchema,
|
|
} from "./processComparison.js";
|