import { SHAPE_SECTION_ICON } from "#configuration/icons/build.icons"; import { SHAPE_SECTION_ID } from "#core/ids/build.ids"; import type { Section } from "#types/document.types"; import { TYPESCRIPT_LANGUAGE } from "#configuration/constants/code.constants"; const RULE_CONTRACT_SHAPE = 'export interface RuleContract {\n readonly meta: {\n readonly type: "problem";\n readonly docs: { readonly description: string };\n readonly schema: readonly [];\n readonly messages: Readonly>;\n };\n readonly create: (context: RuleContext) => RuleListener;\n}\n\nexport default {\n meta: {\n type: "problem",\n docs: { description: "a single-instance resource is reached through its one owner" },\n schema: [],\n messages: {\n directReach: "A single-instance resource must be reached through its owner, which serialises access; route the call through the owner\'s API.",\n },\n },\n create(context) {\n return listener({\n callExpression(view, node) {\n if (reachesSharedInstance(view)) {\n context.report({ node, messageId: "directReach" });\n }\n },\n });\n },\n} satisfies RuleContract<"directReach">;'; const FINDING_SHAPE = 'export type Severity = "error";\n\nexport interface Remediation {\n readonly action: Action;\n readonly operands: Readonly>;\n}\n\nexport interface Finding {\n readonly rule: string;\n readonly path: string;\n readonly locus: { readonly line: number; readonly column: number; readonly member?: string };\n readonly trail: readonly string[];\n readonly actual: string;\n readonly expected: string | null;\n readonly remediation: Remediation;\n readonly healed: boolean;\n readonly severity: Severity;\n}'; const SHAPE_DIAGRAM = 'flowchart TB\n subgraph mechanism["The mechanism · provider-agnostic"]\n detect["Detect the shape · a tree pattern, a token sequence, a structural relation"]\n message["State the shape and its one fix, naming no instance"]\n end\n subgraph registry["The registry · data"]\n instances["Verified instances of the shape, one line each"]\n end\n detect -- reads --> instances\n next["The next instance"]\n next -- one registry line, no new check --> instances'; const TIER_DIAGRAM = 'flowchart TB\n shape["A shape to gate"]\n where{"What does the check need to see?"}\n perfile["One file\'s syntax tree · a per-file rule dropped into the rule host"]\n closure["Relations across files · a rule that reads the graph a prior stage wrote"]\n folder["A folder rather than a file · a rule that walks from the root and anchors its finding"]\n pipeline["The whole tree, or a rewrite · a stage of its own"]\n shape --> where\n where --> perfile\n where --> closure\n where --> folder\n where --> pipeline'; const REGISTRATION_DIAGRAM = 'flowchart TB\n file["A rule file dropped into the host"]\n discover["Discovered by shape · a text match on its contract"]\n index["The index is re-derived from disk"]\n active["Active at failure severity, no core file edited"]\n self["The rule host passes through the gate it enforces"]\n file --> discover --> index --> active --> self\n self -. a malformed rule fails at lint, never at load .-> file'; const SHAPE_SECTION: Section = { icon: SHAPE_SECTION_ICON, id: SHAPE_SECTION_ID, intro: "A check enforces the shape of an anti-pattern, never a provider, a package, a filename or a threshold. The check and its message transfer: the same rule catches every future occurrence of the same shape, whatever library or symbol it happens to involve. This is how a small number of checks governs a growing tree without growing with it: the mechanism stays general and a registry holds the instances, as mechanism and registry draws. A check is placed by what it needs to see, as placed by sight draws, and it becomes active by being dropped in, as dropped in draws, with the contract a rule contract types and the finding a finding types. The shapes themselves are catalogued: an anti-pattern is a decay path on the architecture page, and each carries the control whose absence lets it in.", subsections: [ { blocks: [ { application: "Write the check for the class. Detect a construct in a syntax tree, a token sequence or a structural relation, never a name. Keep the instances as data in a registry the check reads, seeded only with instances verified to have the shape. Write the message as the shape and its one fix, in terms a reader on a different library understands unchanged. Make a rule active by dropping a file.", boundary: "A shape whose classifying property is semantic rather than structural may carry a registry of classified instances. The split is strict: the mechanism reads the registry, the registry is the only place a specific name appears, and the message stays general whatever the registry holds.", cause: "A check that names an instance is correct on the case that motivated it and silent on the next case, which is the one it was written to prevent.", decision: "Gate constructs, never literals.", failureMode: "A rule bans one library's direct import by name, a second library with the same hazard arrives, and the rule says nothing because it never knew what it was protecting.", kind: "lesson", principle: "A check matches a shape, its instances are data, and its message states the fix generically.", problem: "The obvious check names the thing that went wrong, and the thing that went wrong is one instance of a shape that will recur under other names.", validation: "Read a check's message with the vendor, the path and the symbol removed. If it still says what is wrong and how to fix it, the check matches a shape. If it says nothing, the check matched an instance.", }, { caption: "mechanism and registry", kind: "mermaid", text: SHAPE_DIAGRAM }, ], title: "Mechanism general, data specific", }, { blocks: [ { kind: "text", text: "Two properties decide whether a shape is gateable, and both must hold. Detection is deterministic: the bad shape is recognisable by static analysis, an import edge, a manifest field, a path, never by a flaky runtime probe. Where the only way to catch a thing is to observe non-determinism at runtime, the work is to reduce it to the static shape that causes the non-determinism and gate that. Remediation is deterministic: there is one correct fix, statable without reference to any case. Where both hold the shape is gated. Where neither holds, it is surfaced as a question rather than hand-waved, and the honest gaps holds the ones surfaced so far.", }, ], title: "Two properties decide", }, { blocks: [ { kind: "text", text: "A check is placed by what it needs to see. A rule over one file's syntax tree is the lightest, which is the filesystem is the architecture applied to the checks. A rule over relations between files reads a graph a prior stage wrote, and it fails closed when the graph is missing, so the stage order is load-bearing rather than incidental. A rule over a folder rather than a file walks from the root, and it needs an anchor, because a finding is reported against a file it visits: a placement finding attaches to a file inside the offending folder, and a declaration pointing at something absent attaches to the manifest that declared it. A check over the whole tree that is not per file is a stage of its own, and a rewriter verifies its own output by re-parsing before it writes.", }, { caption: "placed by sight", kind: "mermaid", text: TIER_DIAGRAM }, ], title: "Placed by what it needs to see", }, { blocks: [ { kind: "text", text: "The mechanism that discovers rules is itself shape-based, and that is what lets the core stay untouched. A rule becomes active by declaring a contract a registry pattern discovers; if any core file must learn its name, the design is wrong. The contract is checked by the gate against itself: a rule missing a message, declaring a severity of its own, or reaching for an untyped escape fails at lint rather than at load. The mechanism enforcing every other rule is the one most able to decay silently, because nothing else watches it, so the rule host passes through the gate it enforces.", }, { kind: "text", text: "Matching is exact rather than approximate. A check is derived, never guessed: a check that is usually right is wrong, because its misses are invisible and its false accusations punish the party who did the right thing. Authored tooling matches by tree traversal, token comparison or exact string, never by a pattern language that hides the grammar it implements. A hand-written scanner separates use from mention, so a detector never matches its own detection strings inside a report about them, and it tests the call form rather than a list of names, because a name list is a check naming instances and reopens the moment one more name exists.", }, { caption: "dropped in", kind: "mermaid", text: REGISTRATION_DIAGRAM }, ], title: "Discovered by shape, matched exactly", }, { blocks: [ { kind: "text", text: "The rule contract is small and typed, and its type safety is what lets the registry discover it and the gate check it. A rule declares its kind, a description, an options schema and its messages, and it exports a function that returns a listener over the syntax tree. The message ids are a closed vocabulary, so a report naming an undeclared message fails to compile and a declared message nobody reports is a finding against the rule. The rule object is exported directly rather than bound to a name, because the filename is the rule's identity and a second name would be a second fact that has to agree. Severity is never declared by a rule, because there is exactly one.", }, { kind: "text", text: "What a rule emits is the finding detect, log, fix describes, typed: a closed action vocabulary, resolved operands, and a healed flag.", }, { code: RULE_CONTRACT_SHAPE, kind: "code", language: TYPESCRIPT_LANGUAGE, title: "a rule contract" }, { code: FINDING_SHAPE, kind: "code", language: TYPESCRIPT_LANGUAGE, title: "a finding" }, ], title: "The contract, typed", }, ], title: "A check matches a shape", }; export const SHAPE_SECTIONS: readonly Section[] = [SHAPE_SECTION];