# rules/eslint/closure-no-platform-capability-redefine.eslint.rule.ts

> 150 lines of code and 31 definitions.

Tree: Governance tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-closure-no-platform-capability-redefine-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/closure-no-platform-capability-redefine.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-no-null-managed-instance.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-null-managed-instance.eslint.rule.ts.md) and before [rules/eslint/closure-no-platform-product-import.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-platform-product-import.eslint.rule.ts.md).

## Definitions

- `redefinesCapability` (lexical_declaration, line 86)
- `offencesForCapability` (lexical_declaration, line 93)
- `relativeToProject` (lexical_declaration, line 22)
- `walk` (lexical_declaration, line 26)
- `nameSlots` (lexical_declaration, line 41)
- `consumerDirs` (lexical_declaration, line 52)
- `basenameOf` (lexical_declaration, line 78)
- `findOffences` (lexical_declaration, line 111)
- `readTexts` (lexical_declaration, line 82)
- `create` (method_definition, line 130, exported)
- `ROOT` (lexical_declaration, line 11)
- `CONSUMERS_DIR` (lexical_declaration, line 12)
- `OWNS_STATE_MARKER` (lexical_declaration, line 13)
- `selfRegisteringFiles` (lexical_declaration, line 15)
- `graph` (lexical_declaration, line 16)
- `SUPPLIERS` (lexical_declaration, line 20)
- `slots` (lexical_declaration, line 42)
- `p` (lexical_declaration, line 58)
- `Offense` (interface_declaration, line 66)
- `Capability` (interface_declaration, line 73)
- `out` (lexical_declaration, line 101)
- `evidence` (lexical_declaration, line 104)
- `offences` (lexical_declaration, line 112)
- `texts` (lexical_declaration, line 114)
- `consumer` (lexical_declaration, line 115)
- `list` (lexical_declaration, line 118)
- `OFFENCES` (lexical_declaration, line 127)
- `filename` (lexical_declaration, line 134, exported)
- `hits` (lexical_declaration, line 135, exported)
- `program` (method_definition, line 140, exported)
- `payload` (lexical_declaration, line 142, exported)

## Uses

- [shared/factories/listener.factory.ts](https://banes-lab.com/source/governance/shared/factories/listener.factory.ts.md)
- [shared/manifests/capability.manifest.ts](https://banes-lab.com/source/governance/shared/manifests/capability.manifest.ts.md)

## Records this file checks

- [Do Not Repeat Yourself](https://banes-lab.com/records/architecture/duplicate-code.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { MEMBER_ROOT, normalizePath } from "../../shared/resolvers/anchor.resolver.ts";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { PRODUCT_PATH_PREFIXES } from "../../shared/manifests/layer.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { join } from "node:path";
import { listener } from "../../shared/factories/listener.factory.ts";
import { loadClosureGraph } from "../../shared/loaders/graph.loader.ts";
import { platformCapabilities } from "../../shared/manifests/capability.manifest.ts";

const ROOT = MEMBER_ROOT;
const CONSUMERS_DIR = join(ROOT, (PRODUCT_PATH_PREFIXES[0] ?? "").slice(0, -1));
const OWNS_STATE_MARKER = "export class";

const selfRegisteringFiles = function selfRegisteringFiles(): Set<string> {
    const graph = loadClosureGraph();
    return new Set<string>(graph === null ? [] : graph.registers.map((row) => row.file));
};

const SUPPLIERS = selfRegisteringFiles();

const relativeToProject = function relativeToProject(absPath: string): string {
    return normalizePath(absPath).slice(normalizePath(ROOT).length + 1);
};

const walk = function walk(dir: string): string[] {
    const out: string[] = [];
    for (const name of readdirSync(dir)) {
        const p = join(dir, name);
        if (statSync(p).isDirectory()) {
            out.push(...walk(p));
            continue;
        }
        if (p.endsWith(".ts")) {
            out.push(p);
        }
    }
    return out;
};

const nameSlots = function nameSlots(basename: string): Set<string> {
    const slots = new Set<string>();
    for (const dotted of basename.split(".")) {
        slots.add(dotted);
        for (const part of dotted.split("-")) {
            slots.add(part);
        }
    }
    return slots;
};

const consumerDirs = function consumerDirs(): string[] {
    if (!existsSync(CONSUMERS_DIR)) {
        return [];
    }
    const out: string[] = [];
    for (const name of readdirSync(CONSUMERS_DIR)) {
        const p = join(CONSUMERS_DIR, name);
        if (statSync(p).isDirectory()) {
            out.push(p);
        }
    }
    return out;
};

interface Offense {
    consumer: string;
    evidence: string;
    noun: string;
    platformPath: string;
}

interface Capability {
    noun: string;
    platformPath: string;
}

const basenameOf = function basenameOf(file: string): string {
    return (normalizePath(file).split("/").pop() ?? "").toLowerCase();
};

const readTexts = function readTexts(consumerDir: string): Map<string, string> {
    return new Map(walk(consumerDir).map((file) => [file, readFileSync(file, "utf8")]));
};

const redefinesCapability = function redefinesCapability(file: string, text: string, cap: Capability): boolean {
    if (!nameSlots(basenameOf(file)).has(cap.noun)) {
        return false;
    }
    return !SUPPLIERS.has(relativeToProject(file)) && text.includes(OWNS_STATE_MARKER);
};

const offencesForCapability = function offencesForCapability(
    texts: Map<string, string>,
    consumer: string,
    cap: Capability,
): [string, Offense][] {
    if ([...texts.values()].some((text) => text.includes(cap.platformPath))) {
        return [];
    }
    const out: [string, Offense][] = [];
    for (const [file, text] of texts) {
        if (redefinesCapability(file, text, cap)) {
            const evidence = basenameOf(file);
            out.push([file, { consumer, evidence, noun: cap.noun, platformPath: cap.platformPath }]);
        }
    }
    return out;
};

const findOffences = function findOffences(): Map<string, Offense[]> {
    const offences = new Map<string, Offense[]>();
    for (const consumerDir of consumerDirs()) {
        const texts = readTexts(consumerDir);
        const consumer = normalizePath(consumerDir).split("/").pop() ?? consumerDir;
        for (const cap of platformCapabilities()) {
            for (const [file, offense] of offencesForCapability(texts, consumer, cap)) {
                const list = offences.get(file) ?? [];
                list.push(offense);
                offences.set(file, list);
            }
        }
    }
    return offences;
};

const OFFENCES = findOffences();

export default {
    create(context: RuleContext): RuleListener {
        if (OFFENCES.size === 0) {
            return {};
        }
        const filename = normalizePath(context.filename);
        const hits = [...OFFENCES].find(([file]) => normalizePath(file) === filename)?.[1] ?? null;
        if (hits === null) {
            return {};
        }
        return listener({
            program(_view, node) {
                for (const hit of hits) {
                    const payload = {
                        consumer: hit.consumer,
                        evidence: hit.evidence,
                        noun: hit.noun,
                        platformPath: hit.platformPath,
                    };
                    context.report({ data: payload, messageId: "redefined", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:duplicate-code"] }),
            description:
                "A product module must not re-define a capability the platform tier already owns. Both halves are DERIVED, never listed. The capability set is every directory under the platform-side container. A file NAMES a capability when a whole filename slot equals the capability noun — the basename is split on the taxonomy separators and matched slot-wise. The re-implementation test is behavioral: a product file naming the capability is SUPPLYING content when it self-registers into a registry, read from the closure graph's register call sites, and OWNING it when it exports a class instead. A local variant registry is architecture, not re-implementation. Foundational primitives are exempt.",
        },
        messages: {
            redefined:
                "`{{ consumer }}` names the `{{ noun }}` capability (see `{{ evidence }}`) but never imports `{{ platformPath }}`, where that capability already exists. Compose it, or extend the platform module.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
