183 lines
5.7 KiB
TypeScript
183 lines
5.7 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { browserScenarioCaptureSchema } from "./browserScenarioCapture.js";
|
|
|
|
/** Scenario artifact families compared after step alignment. */
|
|
export const browserScenarioArtifactKindSchema = z.enum([
|
|
"action_state",
|
|
"screenshot",
|
|
"dom",
|
|
"accessibility",
|
|
"url",
|
|
"history",
|
|
"storage",
|
|
"events",
|
|
]);
|
|
/** Scenario artifact family. */
|
|
export type BrowserScenarioArtifactKind = z.infer<
|
|
typeof browserScenarioArtifactKindSchema
|
|
>;
|
|
|
|
const normalizableArtifactKindSchema = z.enum([
|
|
"action_state",
|
|
"dom",
|
|
"accessibility",
|
|
"url",
|
|
"history",
|
|
"storage",
|
|
"events",
|
|
]);
|
|
|
|
const normalizationRuleSchema = z.strictObject({
|
|
rule_id: z
|
|
.string()
|
|
.trim()
|
|
.min(1)
|
|
.describe("Stable rule identity; rules execute in rule_id order."),
|
|
artifacts: z
|
|
.array(normalizableArtifactKindSchema)
|
|
.min(1)
|
|
.describe("Artifact families whose durable string fields use this rule."),
|
|
match: z
|
|
.string()
|
|
.min(1)
|
|
.describe(
|
|
"Exact public literal to replace; regular expressions are not used.",
|
|
),
|
|
replacement: z
|
|
.string()
|
|
.describe("Recorded replacement text used in normalized comparison."),
|
|
});
|
|
|
|
const normalizationInputSchema = z
|
|
.strictObject({
|
|
rules: z.array(normalizationRuleSchema).default([]),
|
|
})
|
|
.superRefine((normalization, context) => {
|
|
const ruleIds = new Set<string>();
|
|
for (const [index, rule] of normalization.rules.entries()) {
|
|
if (ruleIds.has(rule.rule_id))
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["rules", index, "rule_id"],
|
|
message: "Normalization rule IDs must be unique",
|
|
});
|
|
ruleIds.add(rule.rule_id);
|
|
if (new Set(rule.artifacts).size !== rule.artifacts.length)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["rules", index, "artifacts"],
|
|
message: "Normalization rule artifact kinds must be unique",
|
|
});
|
|
}
|
|
});
|
|
|
|
/** Input for step-aligned comparison of two browser scenario captures. */
|
|
export const compareBrowserScenariosInputSchema = z.strictObject({
|
|
before_scenario: browserScenarioCaptureSchema.describe(
|
|
"Earlier complete normalized_result from capture_browser_scenario.",
|
|
),
|
|
after_scenario: browserScenarioCaptureSchema.describe(
|
|
"Later complete normalized_result from capture_browser_scenario.",
|
|
),
|
|
normalization: normalizationInputSchema
|
|
.default({ rules: [] })
|
|
.describe("Exact-literal rules committed in the comparison result."),
|
|
});
|
|
/** Parsed browser scenario comparison input. */
|
|
export type CompareBrowserScenariosInput = z.infer<
|
|
typeof compareBrowserScenariosInputSchema
|
|
>;
|
|
|
|
const evidenceStateSchema = z.enum([
|
|
"captured",
|
|
"not_requested",
|
|
"missing",
|
|
"truncated",
|
|
]);
|
|
|
|
const artifactDiffSchema = z.strictObject({
|
|
artifact: browserScenarioArtifactKindSchema,
|
|
status: z.enum(["changed", "unknown"]),
|
|
before_state: evidenceStateSchema,
|
|
after_state: evidenceStateSchema,
|
|
before_sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.nullable(),
|
|
after_sha256: z
|
|
.string()
|
|
.regex(/^[a-f0-9]{64}$/u)
|
|
.nullable(),
|
|
reason: z.string().min(1).nullable(),
|
|
});
|
|
const alignmentFailureSchema = z.strictObject({
|
|
code: z.enum([
|
|
"duplicate_before_step_id",
|
|
"duplicate_after_step_id",
|
|
"missing_before_step",
|
|
"missing_after_step",
|
|
"action_mismatch",
|
|
"capture_context_mismatch",
|
|
]),
|
|
step_id: z.string().min(1).nullable(),
|
|
reason: z.string().min(1),
|
|
});
|
|
const builtInNormalizationSchema = z.enum([
|
|
"ignore_step_elapsed_ms",
|
|
"ignore_event_sequence",
|
|
"ignore_event_step_index",
|
|
"compare_screenshot_content_digest",
|
|
"compare_normalized_text_content",
|
|
]);
|
|
|
|
/** Step alignment, recorded normalization, and inline artifact differences. */
|
|
export const browserScenarioDiffSchema = z.strictObject({
|
|
comparison_kind: z.literal("browser_scenario"),
|
|
overall_status: z.enum(["changed", "unchanged", "unknown"]),
|
|
normalization: z.strictObject({
|
|
built_in_rules: z.array(builtInNormalizationSchema).length(5),
|
|
rules: z.array(normalizationRuleSchema),
|
|
sha256: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
}),
|
|
alignment: z.strictObject({
|
|
status: z.enum(["aligned", "partial", "failed"]),
|
|
aligned_steps: z.number().int().min(0),
|
|
before_only: z.array(z.string().min(1)),
|
|
after_only: z.array(z.string().min(1)),
|
|
failures: z.array(alignmentFailureSchema),
|
|
}),
|
|
steps: z.array(
|
|
z.strictObject({
|
|
step_id: z.string().min(1),
|
|
before_step_index: z.number().int().min(0),
|
|
after_step_index: z.number().int().min(0),
|
|
before_action: z.string().min(1),
|
|
after_action: z.string().min(1),
|
|
status: z.enum(["changed", "unchanged", "unknown"]),
|
|
artifact_diffs: z.array(artifactDiffSchema),
|
|
}),
|
|
),
|
|
artifact_diffs: z.strictObject({
|
|
total: z.number().int().min(0),
|
|
}),
|
|
limitations: z.array(z.string().min(1)),
|
|
});
|
|
/** Browser scenario comparison result. */
|
|
export type BrowserScenarioDiff = z.infer<typeof browserScenarioDiffSchema>;
|
|
/** Reportable changed or unknown scenario artifact. */
|
|
export type BrowserScenarioArtifactDiff =
|
|
BrowserScenarioDiff["steps"][number]["artifact_diffs"][number];
|
|
/** One explicit reason scenario step alignment was not exact. */
|
|
export type BrowserScenarioAlignmentFailure =
|
|
BrowserScenarioDiff["alignment"]["failures"][number];
|
|
/** Comparable capture state. */
|
|
export type BrowserScenarioEvidenceState =
|
|
BrowserScenarioArtifactDiff["before_state"];
|
|
/** Scenario normalization policy after boundary parsing. */
|
|
export type BrowserScenarioNormalization =
|
|
CompareBrowserScenariosInput["normalization"];
|
|
/** One exact literal scenario normalization rule. */
|
|
export type BrowserScenarioNormalizationRule =
|
|
BrowserScenarioNormalization["rules"][number];
|