// Known model corruptions for demonstrating the validator end-to-end. // // Each "break" returns a model variant that should fire one specific rule // (or a small cluster) without firing others. Used by the editor's `?break=...` // query param to make the validator's behavior visible. import type { SysMLModel } from "./model"; export type BreakName = | "S1-dangling" | "S2-dangling-constraint" | "S3-untraced-satisfier" | "M2-cycle" | "M3-multi-system" | "M5-dup-property" | "T1-untraced-req" | "T2-unused-block"; export const BREAKS: Record SysMLModel }> = { "S1-dangling": { label: "S1 — dangling association endpoint", description: "Add an association whose target block doesn't exist.", apply(m) { return { ...m, associations: [ ...m.associations, { id: "a-bad", fromBlockId: "aristotle", toBlockId: "ghost-block", label: "haunts", kind: "association" }, ], }; }, }, "S2-dangling-constraint": { label: "S2 — constraint applies to unknown block", description: "Add a constraint pointing at a block that doesn't exist.", apply(m) { return { ...m, constraints: [ ...m.constraints, { id: "c-bad", label: "Phantom rule", expression: "{}", appliesTo: ["ghost-block"] }, ], }; }, }, "S3-untraced-satisfier": { label: "S3 — requirement satisfied by unknown block", description: "Reroute REQ-001 to a non-existent block.", apply(m) { return { ...m, requirements: m.requirements.map(r => r.tag === "REQ-001" ? { ...r, relations: [{ kind: "satisfy" as const, blockId: "ghost-block" }] } : r ), }; }, }, "M2-cycle": { label: "M2 — cyclic composition", description: "Add a composition Assignment → Course (the inverse of the existing Course → Assignment).", apply(m) { return { ...m, associations: [ ...m.associations, { id: "a-cycle", fromBlockId: "assignment", toBlockId: "course", label: "wraps", kind: "composition", }, ], }; }, }, "M3-multi-system": { label: "M3 — two System-of-Interest blocks", description: "Mark both Aristotle and Course as kind=system.", apply(m) { return { ...m, blocks: m.blocks.map(b => b.id === "aristotle" || b.id === "course" ? { ...b, kind: "system" as const, stereotypes: ["system"] } : b ), }; }, }, "M5-dup-property": { label: "M5 — duplicate property name in a block", description: "Add a second `interaction_style` property to Aristotle.", apply(m) { return { ...m, blocks: m.blocks.map(b => b.id === "aristotle" ? { ...b, properties: [ ...b.properties, { id: "p-dup", name: "interaction_style", type: { kind: "string" as const }, multiplicity: "0..1" as const }, ], } : b ), }; }, }, "T1-untraced-req": { label: "T1 — requirement with no satisfier", description: "Already present in the fixture (REQ-003 has no satisfy relation).", apply(m) { // No mutation needed; the fixture already triggers T1 on REQ-003. return m; }, }, "T2-unused-block": { label: "T2 — block with no incoming references", description: "Add a Tutor block that nothing connects to.", apply(m) { return { ...m, blocks: [ ...m.blocks, { id: "tutor-orphan", label: "TutorOrphan", kind: "block", stereotypes: ["block"], properties: [], }, ], }; }, }, }; export function applyBreaks(model: SysMLModel, names: BreakName[]): SysMLModel { return names.reduce((m, name) => BREAKS[name].apply(m), model); }