refactor(process): remove unused paired experiment

This commit is contained in:
morluto 2026-09-29 12:16:49 +08:00
parent 3fd96506f2
commit e1d755f7cd
3 changed files with 0 additions and 617 deletions

View File

@ -1,260 +0,0 @@
import { describe, expect, it } from "vitest";
import {
runPairedProcessExperiment,
type ProcessCapturePort,
} from "./ProcessPairedExperiment.js";
import { EMPTY_PROCESS_CAPTURE_EXAMPLE } from "../domain/processCapture.fixture.js";
import {
parseProcessCapture,
type EnabledProcessExecutionPolicy,
} from "../domain/processCapture.js";
import {
analyzeProcessRepeatability,
bindProcessScenario,
processPairedExperimentSchema,
} from "../domain/processPairedExperiment.js";
import { ok } from "../domain/result.js";
const experiment = () =>
processPairedExperimentSchema.parse({
shared_scenario: {
executable: "/bin/template",
arguments: ["shared"],
working_directory: "/tmp",
environment: { SHARED: "yes" },
},
authority: {
id: "authority",
executable: "/bin/authority",
environment: { SIDE: "authority" },
},
candidate: {
id: "candidate",
executable: "/bin/candidate",
arguments: ["candidate"],
environment: { SIDE: "candidate" },
},
repeat_count: 3,
freshness_policy: { max_capture_age_ms: 60_000 },
required_dimensions: [
"terminal",
"interaction",
"exit",
"filesystem",
"protocol",
"process",
"shim",
],
});
const policy: EnabledProcessExecutionPolicy = {
status: "enabled",
executableRoots: ["/bin"],
workingRoots: ["/tmp"],
allowedEnvironment: ["SHARED", "SIDE"],
networkAccess: "none",
};
const cloneCapture = (
capture: ReturnType<typeof parseProcessCapture>,
): ReturnType<typeof parseProcessCapture> =>
parseProcessCapture(structuredClone(capture));
describe("paired process experiments", () => {
it("binds only declared side inputs onto the shared observation contract", () => {
const parsed = experiment();
const authority = bindProcessScenario(
parsed.shared_scenario,
parsed.authority,
);
const candidate = bindProcessScenario(
parsed.shared_scenario,
parsed.candidate,
);
expect(authority).toMatchObject({
executable: "/bin/authority",
arguments: ["shared"],
environment: { SHARED: "yes", SIDE: "authority" },
});
expect(candidate).toMatchObject({
executable: "/bin/candidate",
arguments: ["candidate"],
environment: { SHARED: "yes", SIDE: "candidate" },
});
});
it("runs same-side comparisons first and compares stable sides", async () => {
const observedExecutables: string[] = [];
const capture = parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
const capturePort: ProcessCapturePort = {
capture: (scenario) => {
observedExecutables.push(scenario.executable);
return Promise.resolve(ok(cloneCapture(capture)));
},
};
const result = await runPairedProcessExperiment(experiment(), policy, {
capturePort,
now: () => Date.parse("2026-01-01T00:00:01.000Z"),
});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(observedExecutables).toEqual([
"/bin/authority",
"/bin/authority",
"/bin/authority",
"/bin/candidate",
"/bin/candidate",
"/bin/candidate",
]);
expect(result.value.authority_repeatability.stable).toBe(true);
expect(result.value.candidate_repeatability.stable).toBe(true);
expect(result.value.cross_side?.status).toBe("unchanged");
expect(result.value.cross_side_blocked_reason).toBeNull();
});
it("blocks cross-side claims when a candidate has a flaky exit", async () => {
const base = parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
let candidateRun = 0;
const capturePort: ProcessCapturePort = {
capture: (scenario) => {
let capture = cloneCapture(base);
if (scenario.executable === "/bin/candidate") {
candidateRun += 1;
if (candidateRun === 2)
capture = parseProcessCapture({
...capture,
exit: { code: 1, signal: null, reason: "exited" },
});
}
return Promise.resolve(ok(capture));
},
};
const result = await runPairedProcessExperiment(experiment(), policy, {
capturePort,
now: () => Date.parse("2026-01-01T00:00:01.000Z"),
});
expect(result.ok).toBe(true);
if (!result.ok) return;
expect(result.value.candidate_repeatability).toMatchObject({
stable: false,
classification: "flaky_exit",
unstable_dimensions: ["exit"],
});
expect(result.value.cross_side).toBeNull();
expect(result.value.cross_side_blocked_reason).toMatch(/unstable/u);
});
});
describe("paired process experiment comparisons", () => {
it("distinguishes normalized timing variance from semantic output drift", () => {
const base = parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
const early = parseProcessCapture({
...base,
frames: [{ sequence: 0, at_ms: 10, data: "ready" }],
});
const late = parseProcessCapture({
...base,
frames: [{ sequence: 0, at_ms: 20, data: "ready" }],
});
expect(
analyzeProcessRepeatability([early, late], ["terminal"]),
).toMatchObject({
stable: false,
classification: "timing_variance",
unstable_dimensions: ["terminal"],
});
const changed = parseProcessCapture({
...base,
frames: [{ sequence: 0, at_ms: 20, data: "failed" }],
});
expect(
analyzeProcessRepeatability([early, changed], ["terminal"]),
).toMatchObject({ classification: "nondeterministic_output" });
});
it("stops before capture when the experiment is cancelled", async () => {
const controller = new AbortController();
controller.abort();
const capturePort: ProcessCapturePort = {
capture: () => {
throw new Error("capture must not start after cancellation");
},
};
const result = await runPairedProcessExperiment(experiment(), policy, {
capturePort,
signal: controller.signal,
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "ProcessCaptureError", reason: "cancelled" },
});
});
it("blocks full cross-side output when only a subset was repeatable", async () => {
const capture = parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
const capturePort: ProcessCapturePort = {
capture: () => Promise.resolve(ok(cloneCapture(capture))),
};
const subset = processPairedExperimentSchema.parse({
...experiment(),
required_dimensions: ["exit"],
});
const result = await runPairedProcessExperiment(subset, policy, {
capturePort,
});
expect(result).toMatchObject({
ok: true,
value: {
cross_side: null,
cross_side_blocked_reason: expect.stringMatching(/every returned/u),
},
});
});
});
describe("paired process experiment freshness", () => {
it("compares the newest stable captures under the freshness policy", async () => {
const base = parseProcessCapture(EMPTY_PROCESS_CAPTURE_EXAMPLE);
let captureIndex = 0;
const capturePort: ProcessCapturePort = {
capture: () => {
const completedAt =
captureIndex === 0
? "2026-01-01T00:00:00.000Z"
: "2026-01-01T00:00:09.500Z";
captureIndex += 1;
return Promise.resolve(
ok(
parseProcessCapture({
...structuredClone(base),
manifest: {
...base.manifest,
started_at: completedAt,
completed_at: completedAt,
},
}),
),
);
},
};
const shortFreshness = processPairedExperimentSchema.parse({
...experiment(),
freshness_policy: { max_capture_age_ms: 1_000 },
});
const result = await runPairedProcessExperiment(shortFreshness, policy, {
capturePort,
now: () => Date.parse("2026-01-01T00:00:10.000Z"),
});
expect(result).toMatchObject({
ok: true,
value: { cross_side: { status: "unchanged" } },
});
});
});

View File

@ -1,152 +0,0 @@
import {
processCaptureCancelled,
type ProcessCaptureError,
} from "./ProcessCaptureError.js";
import { captureProcessScenario } from "./ProcessHarness.js";
import type {
ProcessCapture,
ProcessExecutionPolicy,
ProcessScenario,
} from "../domain/processCapture.js";
import { compareProcessCaptures } from "../domain/processComparison.js";
import { PROCESS_COMPARISON_DIMENSIONS } from "../domain/processComparison.js";
import {
analyzeProcessRepeatability,
bindProcessScenario,
type ProcessPairedExperiment,
} from "../domain/processPairedExperiment.js";
import { err, ok, type Result } from "../domain/result.js";
/** Capture seam used by paired process experiments. */
export interface ProcessCapturePort {
capture(
scenario: ProcessScenario,
policy: ProcessExecutionPolicy,
signal?: AbortSignal,
): Promise<Result<ProcessCapture, ProcessCaptureError>>;
}
interface PairedProcessExperimentEvidence {
readonly authority_runs: readonly ProcessCapture[];
readonly candidate_runs: readonly ProcessCapture[];
readonly authority_repeatability: ReturnType<
typeof analyzeProcessRepeatability
>;
readonly candidate_repeatability: ReturnType<
typeof analyzeProcessRepeatability
>;
}
/** Repeatability evidence and either a stable comparison or its blocking reason. */
export type PairedProcessExperimentResult = PairedProcessExperimentEvidence &
(
| {
readonly cross_side: ReturnType<typeof compareProcessCaptures>;
readonly cross_side_blocked_reason: null;
}
| {
readonly cross_side: null;
readonly cross_side_blocked_reason: string;
}
);
const productionCapturePort: ProcessCapturePort = {
capture: captureProcessScenario,
};
const captureRepeats = async (input: {
readonly scenario: ProcessScenario;
readonly repeatCount: number;
readonly policy: ProcessExecutionPolicy;
readonly port: ProcessCapturePort;
readonly signal?: AbortSignal;
}): Promise<Result<readonly ProcessCapture[], ProcessCaptureError>> => {
const captures: ProcessCapture[] = [];
for (let index = 0; index < input.repeatCount; index += 1) {
if (input.signal?.aborted === true) return err(processCaptureCancelled());
const capture = await input.port.capture(
input.scenario,
input.policy,
input.signal,
);
if (!capture.ok) return err(capture.error);
captures.push(capture.value);
}
return ok(captures);
};
/** Execute repeatability-first authority/candidate captures under one contract. */
export const runPairedProcessExperiment = async (
experiment: ProcessPairedExperiment,
policy: ProcessExecutionPolicy,
options: {
readonly signal?: AbortSignal;
readonly capturePort?: ProcessCapturePort;
readonly now?: () => number;
} = {},
): Promise<Result<PairedProcessExperimentResult, ProcessCaptureError>> => {
const port = options.capturePort ?? productionCapturePort;
const authorityScenario = bindProcessScenario(
experiment.shared_scenario,
experiment.authority,
);
const candidateScenario = bindProcessScenario(
experiment.shared_scenario,
experiment.candidate,
);
const authorityRuns = await captureRepeats({
scenario: authorityScenario,
repeatCount: experiment.repeat_count,
policy,
port,
...(options.signal === undefined ? {} : { signal: options.signal }),
});
if (!authorityRuns.ok) return authorityRuns;
const candidateRuns = await captureRepeats({
scenario: candidateScenario,
repeatCount: experiment.repeat_count,
policy,
port,
...(options.signal === undefined ? {} : { signal: options.signal }),
});
if (!candidateRuns.ok) return candidateRuns;
const authorityRepeatability = analyzeProcessRepeatability(
authorityRuns.value,
experiment.required_dimensions,
);
const candidateRepeatability = analyzeProcessRepeatability(
candidateRuns.value,
experiment.required_dimensions,
);
const stable = authorityRepeatability.stable && candidateRepeatability.stable;
const allDimensionsRequired = PROCESS_COMPARISON_DIMENSIONS.every(
(dimension) => experiment.required_dimensions.includes(dimension),
);
const authorityLatest = authorityRuns.value.at(-1);
const candidateLatest = candidateRuns.value.at(-1);
const crossSideOutcome =
stable &&
allDimensionsRequired &&
authorityLatest !== undefined &&
candidateLatest !== undefined
? {
cross_side: compareProcessCaptures(authorityLatest, candidateLatest, {
maxCaptureAgeMs: experiment.freshness_policy.max_capture_age_ms,
...(options.now === undefined ? {} : { now: options.now }),
}),
cross_side_blocked_reason: null,
}
: {
cross_side: null,
cross_side_blocked_reason: !allDimensionsRequired
? "Cross-side comparison is blocked until every returned dimension is required and repeatable."
: "Cross-side comparison is blocked because a required same-side dimension is unstable.",
};
return ok({
authority_runs: authorityRuns.value,
candidate_runs: candidateRuns.value,
authority_repeatability: authorityRepeatability,
candidate_repeatability: candidateRepeatability,
...crossSideOutcome,
});
};

View File

@ -1,205 +0,0 @@
import { z } from "zod";
import type { ProcessCapture } from "./processCapture.js";
import {
PROCESS_COMPARISON_DIMENSIONS,
compareProcessCaptures,
processDimensionObservations,
type ProcessCaptureComparison,
} from "./processComparison.js";
import {
digestProcessCommitment,
processScenarioSchema,
type ProcessScenario,
} from "./processScenario.js";
const sideIdentifierSchema = z
.string()
.regex(/^[A-Za-z][A-Za-z0-9._-]{0,127}$/u);
const environmentNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/u);
const processDimensionSchema = z.enum(PROCESS_COMPARISON_DIMENSIONS);
const sideBindingSchema = z
.object({
id: sideIdentifierSchema,
executable: z.string().startsWith("/"),
arguments: z.array(z.string()).max(256).optional(),
environment: z.record(environmentNameSchema, z.string()).default({}),
})
.strict();
/** Boundary contract for one bounded authority/candidate process experiment. */
export const processPairedExperimentSchema = z
.object({
shared_scenario: processScenarioSchema,
authority: sideBindingSchema,
candidate: sideBindingSchema,
repeat_count: z.number().int().min(2).max(10).default(3),
freshness_policy: z
.object({
max_capture_age_ms: z.number().int().positive().max(86_400_000),
})
.strict(),
required_dimensions: z
.array(processDimensionSchema)
.min(1)
.max(PROCESS_COMPARISON_DIMENSIONS.length)
.default([...PROCESS_COMPARISON_DIMENSIONS]),
})
.strict()
.superRefine((experiment, context) => {
if (experiment.authority.id === experiment.candidate.id)
context.addIssue({
code: "custom",
message: "authority and candidate identities must differ",
path: ["candidate", "id"],
});
if (
new Set(experiment.required_dimensions).size !==
experiment.required_dimensions.length
)
context.addIssue({
code: "custom",
message: "required process dimensions must be unique",
path: ["required_dimensions"],
});
});
export type ProcessPairedExperiment = z.infer<
typeof processPairedExperimentSchema
>;
/** Executable and environment values applied to one experiment side. */
export type ProcessSideBinding = ProcessPairedExperiment[
| "authority"
| "candidate"];
type ProcessRepeatabilityClassification =
| "stable"
| "timing_variance"
| "nondeterministic_output"
| "flaky_exit"
| "environmental_drift"
| "insufficient_samples";
/** Stability classification for repeated captures of one experiment side. */
export interface ProcessSideRepeatability {
readonly classification: ProcessRepeatabilityClassification;
readonly stable: boolean;
readonly digest: string;
readonly comparisons: readonly ProcessCaptureComparison[];
readonly unstable_dimensions: readonly (typeof PROCESS_COMPARISON_DIMENSIONS)[number][];
}
/** Apply a side binding without changing the shared observation contract. */
export const bindProcessScenario = (
shared: ProcessScenario,
side: ProcessSideBinding,
): ProcessScenario =>
processScenarioSchema.parse({
...shared,
executable: side.executable,
arguments: side.arguments ?? shared.arguments,
environment: { ...shared.environment, ...side.environment },
});
const classifyInstability = (
dimensions: ReadonlySet<(typeof PROCESS_COMPARISON_DIMENSIONS)[number]>,
): ProcessRepeatabilityClassification => {
if (dimensions.has("exit")) return "flaky_exit";
if (dimensions.has("filesystem") || dimensions.has("process"))
return "environmental_drift";
return "nondeterministic_output";
};
const timingKeys = new Set([
"at_ms",
"scheduled_at_ms",
"dispatched_at_ms",
"elapsed_ms",
]);
const withoutTiming = (value: unknown): unknown =>
typeof value !== "object" || value === null || Array.isArray(value)
? value
: Object.fromEntries(
Object.entries(value).filter(([key]) => !timingKeys.has(key)),
);
const observationsWithoutTiming = (
capture: ProcessCapture,
dimension: (typeof PROCESS_COMPARISON_DIMENSIONS)[number],
): readonly unknown[] =>
dimension === "exit"
? [
{
...capture.exit,
settlement: {
state: capture.settlement.state,
cleanup_outcome: capture.settlement.cleanup_outcome,
},
},
]
: processDimensionObservations(capture, dimension).map(withoutTiming);
const hasOnlyTimingVariance = (
captures: readonly ProcessCapture[],
dimensions: readonly (typeof PROCESS_COMPARISON_DIMENSIONS)[number][],
): boolean =>
dimensions.every((dimension) => {
const first = captures[0];
if (first === undefined) return false;
const baseline = JSON.stringify(
observationsWithoutTiming(first, dimension),
);
return captures
.slice(1)
.every(
(capture) =>
JSON.stringify(observationsWithoutTiming(capture, dimension)) ===
baseline,
);
});
/** Compare same-side repeats before permitting a cross-side claim. */
export const analyzeProcessRepeatability = (
captures: readonly ProcessCapture[],
requiredDimensions: readonly (typeof PROCESS_COMPARISON_DIMENSIONS)[number][],
): ProcessSideRepeatability => {
if (captures.length < 2)
return {
classification: "insufficient_samples",
stable: false,
digest: digestProcessCommitment({ captures: [], requiredDimensions }),
comparisons: [],
unstable_dimensions: [],
};
const baseline = captures[0];
if (baseline === undefined)
throw new TypeError("Repeatability analysis lost its baseline capture");
const comparisons = captures
.slice(1)
.map((capture) => compareProcessCaptures(baseline, capture));
const unstable = new Set(
requiredDimensions.filter((dimension) =>
comparisons.some((comparison) => comparison[dimension] !== "unchanged"),
),
);
const unstableDimensions = PROCESS_COMPARISON_DIMENSIONS.filter((dimension) =>
unstable.has(dimension),
);
return {
classification:
unstable.size === 0
? "stable"
: hasOnlyTimingVariance(captures, unstableDimensions)
? "timing_variance"
: classifyInstability(unstable),
stable: unstable.size === 0,
digest: digestProcessCommitment({
requiredDimensions,
comparisons,
}),
comparisons,
unstable_dimensions: unstableDimensions,
};
};