# shared/matchers/folder.matcher.ts

> 89 lines of code and 19 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-shared-matchers-folder-matcher-ts
Source text: https://banes-lab.com/source/governance/shared/matchers/folder.matcher.ts.txt

Listed in [shared/matchers](https://banes-lab.com/api/source/governance/shared/matchers.md), after [shared/matchers/filename.matcher.ts](https://banes-lab.com/source/governance/shared/matchers/filename.matcher.ts.md) and before [shared/matchers/location.matcher.ts](https://banes-lab.com/source/governance/shared/matchers/location.matcher.ts.md).

## Definitions

- `ranksAtDepth` (lexical_declaration, line 23)
- `holdsRole` (lexical_declaration, line 10)
- `assignRoles` (lexical_declaration, line 44)
- `terminalRankOf` (lexical_declaration, line 39)
- `folderPathError` (lexical_declaration, line 75, exported)
- `roleAtDepth` (lexical_declaration, line 35, exported)
- `{ roleOrder, rolesAtDepth }` (lexical_declaration, line 24)
- `ranks` (lexical_declaration, line 25)
- `{ roleOrder, terminalRole }` (lexical_declaration, line 40)
- `name` (lexical_declaration, line 50)
- `rest` (lexical_declaration, line 55)
- `deepestRank` (lexical_declaration, line 63)
- `after` (lexical_declaration, line 64)
- `rank` (lexical_declaration, line 66)
- `vocabulary` (lexical_declaration, line 76, exported)
- `{ roleOrder, rolesAtDepth, terminalRole }` (lexical_declaration, line 77, exported)
- `{ at, after }` (lexical_declaration, line 89, exported)
- `allowed` (lexical_declaration, line 91, exported)
- `later` (lexical_declaration, line 92, exported)

## Uses

- [shared/manifests/taxonomy.manifest.ts](https://banes-lab.com/source/governance/shared/manifests/taxonomy.manifest.ts.md)
- [shared/matchers/filename.matcher.ts](https://banes-lab.com/source/governance/shared/matchers/filename.matcher.ts.md)

## Source

```typescript
import {
    isConcernFolder,
    isDeclaredContainer,
    isLegalSubject,
    isMarkerFolder,
    vocabularyFor,
} from "../manifests/taxonomy.manifest.ts";
import { isKebab } from "./filename.matcher.ts";

const holdsRole = function holdsRole(root: string, name: string, role: string): boolean {
    if (role === "container") {
        return isDeclaredContainer(root, name);
    }
    if (role === "subject") {
        return isLegalSubject(name, root);
    }
    if (role === "concern") {
        return isConcernFolder(name, root) || isMarkerFolder(name, root);
    }
    return false;
};

const ranksAtDepth = function ranksAtDepth(root: string, name: string, depth: number, after: number): number[] {
    const { roleOrder, rolesAtDepth } = vocabularyFor(root).roles;
    const ranks: number[] = [];
    for (const role of rolesAtDepth[depth - 1] ?? []) {
        const rank = roleOrder.indexOf(role);
        if (rank > after && holdsRole(root, name, role)) {
            ranks.push(rank);
        }
    }
    return ranks;
};

export const roleAtDepth = function roleAtDepth(root: string, name: string, depth: number, after: number): number {
    return ranksAtDepth(root, name, depth, after)[0] ?? -1;
};

const terminalRankOf = function terminalRankOf(root: string): number {
    const { roleOrder, terminalRole } = vocabularyFor(root).roles;
    return roleOrder.indexOf(terminalRole);
};

const assignRoles = function assignRoles(
    root: string,
    segments: readonly string[],
    index: number,
    after: number,
): number[] | undefined {
    const name = segments[index];
    if (name === undefined) {
        return after === terminalRankOf(root) ? [] : undefined;
    }
    for (const rank of ranksAtDepth(root, name, index + 1, after)) {
        const rest = assignRoles(root, segments, index + 1, rank);
        if (rest !== undefined) {
            return [rank, ...rest];
        }
    }
    return undefined;
};

const deepestRank = function deepestRank(root: string, segments: readonly string[]): { at: number; after: number } {
    let after = -1;
    for (const [index, name] of segments.entries()) {
        const rank = roleAtDepth(root, name, index + 1, after);
        if (rank === -1) {
            return { after, at: index };
        }
        after = rank;
    }
    return { after, at: segments.length };
};

export const folderPathError = function folderPathError(root: string, segments: readonly string[]): string | undefined {
    const vocabulary = vocabularyFor(root);
    const { roleOrder, rolesAtDepth, terminalRole } = vocabulary.roles;
    if (segments.length > vocabulary.maxDepth) {
        return `nests ${segments.length} folders below the governed root; the cap is ${vocabulary.maxDepth}. overflow relieves sideways — the filename variant slot for a collision, a sibling folder for breadth — never downward`;
    }
    for (const name of segments) {
        if (!isKebab(name, root)) {
            return `folder '${name}' is not ${vocabulary.case}-case`;
        }
    }
    if (assignRoles(root, segments, 0, -1) !== undefined) {
        return undefined;
    }
    const { at, after } = deepestRank(root, segments);
    if (at < segments.length) {
        const allowed = (rolesAtDepth[at] ?? []).join(" | ");
        const later = roleOrder.slice(after + 1).join(" | ");
        return `folder '${segments[at] ?? ""}' resolves to no role at depth ${at + 1}. that depth accepts ${allowed}, and the path has already consumed up to '${roleOrder[after] ?? "(root)"}', so only ${later} may follow`;
    }
    return `the file's parent folder resolves to '${roleOrder[after] ?? "(root)"}', not '${terminalRole}'. every governed file sits directly inside its concern folder`;
};
```
