# core/validators/node.validator.ts

> 131 lines of code and 28 definitions.

Tree: GovLab Context
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/context#file-context-core-validators-node-validator-ts
Source text: https://banes-lab.com/source/context/core/validators/node.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/context/core/validators.md), after [core/validators/lexicon.validator.ts](https://banes-lab.com/source/context/core/validators/lexicon.validator.ts.md) and before [core/validators/ontology.validator.ts](https://banes-lab.com/source/context/core/validators/ontology.validator.ts.md).

## Definitions

- `tokenOf` (lexical_declaration, line 73)
- `declaredNamesOf` (lexical_declaration, line 35)
- `inputHasSource` (lexical_declaration, line 44)
- `hasWord` (lexical_declaration, line 57)
- `writes` (lexical_declaration, line 68)
- `checkDefects` (lexical_declaration, line 108)
- `hasVagueCondition` (lexical_declaration, line 16)
- `declaredNameOf` (lexical_declaration, line 21)
- `inJurisdiction` (lexical_declaration, line 39)
- `isWordChar` (lexical_declaration, line 53)
- `resultDefects` (lexical_declaration, line 122)
- `gateDefects` (lexical_declaration, line 131, exported)
- `nodeDefects` (lexical_declaration, line 77, exported)
- `lower` (lexical_declaration, line 17)
- `prefix` (lexical_declaration, line 22)
- `rest` (lexical_declaration, line 26)
- `end` (lexical_declaration, line 27)
- `name` (lexical_declaration, line 31)
- `jurisdiction` (lexical_declaration, line 40)
- `input` (lexical_declaration, line 45)
- `at` (lexical_declaration, line 58)
- `lines` (lexical_declaration, line 69)
- `token` (lexical_declaration, line 78, exported)
- `defects` (lexical_declaration, line 79, exported)
- `countDefect` (lexical_declaration, line 98)
- `count` (lexical_declaration, line 99)
- `{ gate }` (lexical_declaration, line 132, exported)
- `countIssue` (lexical_declaration, line 136, exported)

## Contained in

- [core/validators](https://banes-lab.com/anatomy/context/folder-context-core-validators.md)

## Linked from

- [core/validators](https://banes-lab.com/anatomy/context/folder-context-core-validators.md)

## Source

```typescript
import {
    ARTIFACT_SHAPE,
    DECLARING_PREFIXES,
    GATE_MAX_CONDITIONS,
    GATE_MIN_CONDITIONS,
    JURISDICTION_KEY,
    NAME_TERMINATORS,
    RESULT_BLOCKED,
    SOURCE_TOKENS,
    VAGUE_TERMS,
    WRITING_TOKENS,
    ZERO,
} from "#configuration/constants/grammar.constants";
import type { PagDefect, PagDocument, PagGate, PagNode } from "#types/grammar.document.types";

const hasVagueCondition = function hasVagueCondition(condition: string): boolean {
    const lower = condition.toLowerCase();
    return VAGUE_TERMS.some((term) => lower.includes(term));
};

const declaredNameOf = function declaredNameOf(directive: string): string | null {
    const prefix = DECLARING_PREFIXES.find((candidate) => directive.startsWith(candidate));
    if (prefix === undefined) {
        return null;
    }
    const rest = directive.slice(prefix.length);
    let end = 0;
    while (end < rest.length && !NAME_TERMINATORS.has(rest.charAt(end))) {
        end += 1;
    }
    const name = rest.slice(0, end);
    return name === "" ? null : name;
};

const declaredNamesOf = function declaredNamesOf(doc: PagDocument): string[] {
    return doc.nodes.flatMap((node) => node.directives.flatMap((directive) => declaredNameOf(directive) ?? []));
};

const inJurisdiction = function inJurisdiction(node: PagNode, doc: PagDocument, input: string): boolean {
    const jurisdiction = doc.meta[JURISDICTION_KEY] ?? "";
    return doc.nodes[0] === node && jurisdiction !== "" && jurisdiction.includes(input);
};

const inputHasSource = function inputHasSource(node: PagNode, doc: PagDocument, names: string[]): boolean {
    const input = node.input ?? "";
    return (
        SOURCE_TOKENS.some((token) => input.includes(token)) ||
        names.some((name) => input.includes(name)) ||
        inJurisdiction(node, doc, input)
    );
};

const isWordChar = function isWordChar(ch: string): boolean {
    return ch !== "" && ch.toLowerCase() !== ch.toUpperCase();
};

const hasWord = function hasWord(line: string, token: string): boolean {
    let at = line.indexOf(token);
    while (at !== -1) {
        if (!isWordChar(line.charAt(at - 1))) {
            return true;
        }
        at = line.indexOf(token, at + 1);
    }
    return false;
};

const writes = function writes(node: PagNode): boolean {
    const lines = [node.transform ?? "", ...node.directives];
    return lines.some((line) => WRITING_TOKENS.some((token) => hasWord(line, token)));
};

const tokenOf = function tokenOf(node: PagNode): string {
    return node.number ?? node.title;
};

export const nodeDefects = function nodeDefects(node: PagNode, doc: PagDocument): PagDefect[] {
    const token = tokenOf(node);
    const defects: PagDefect[] = [];
    if (node.tag === null) {
        defects.push({ code: "node_tag_malformed", line: node.line, token });
    }
    if (node.input !== null && !inputHasSource(node, doc, declaredNamesOf(doc))) {
        defects.push({ code: "input_without_source", line: node.line, token });
    }
    if (writes(node) && node.gate?.refusal === null) {
        defects.push({ code: "write_without_refusal", line: node.gate.line, token });
    }
    if (node.tag?.yields.includes(ARTIFACT_SHAPE) === true && node.freshness === null) {
        defects.push({ code: "artifact_without_freshness", line: node.line, token });
    }
    if (node.gate === null) {
        defects.push({ code: "node_without_gate", line: node.line, token });
    }
    return defects;
};

const countDefect = function countDefect(gate: PagGate): PagDefect | null {
    const count = gate.checks.length;
    if (count < GATE_MIN_CONDITIONS) {
        return { code: "gate_too_few_conditions", line: gate.line, token: String(count) };
    }
    return count > GATE_MAX_CONDITIONS
        ? { code: "gate_too_many_conditions", line: gate.line, token: String(count) }
        : null;
};

const checkDefects = function checkDefects(gate: PagGate): PagDefect[] {
    return gate.checks.flatMap((check): PagDefect[] => [
        ...(hasVagueCondition(check.condition)
            ? [{ code: "vague_condition" as const, line: check.line, token: check.marker }]
            : []),
        ...(check.evidence === null || check.evidence === ""
            ? [{ code: "check_without_evidence" as const, line: check.line, token: check.marker }]
            : []),
        ...(check.population?.measured === ZERO && check.population.whole === ZERO
            ? [{ code: "empty_population" as const, line: check.line, token: check.population.set }]
            : []),
    ]);
};

const resultDefects = function resultDefects(gate: PagGate, nodeToken: string): PagDefect[] {
    if (gate.result === null) {
        return [{ code: "result_missing", line: gate.line, token: nodeToken }];
    }
    return gate.result.unknown === RESULT_BLOCKED
        ? []
        : [{ code: "unknown_unrouted", line: gate.result.line, token: nodeToken }];
};

export const gateDefects = function gateDefects(node: PagNode): PagDefect[] {
    const { gate } = node;
    if (gate === null) {
        return [];
    }
    const countIssue = countDefect(gate);
    return [
        ...(countIssue ? [countIssue] : []),
        ...(gate.checks.some((check) => check.population !== null)
            ? []
            : [{ code: "gate_without_population" as const, line: gate.line, token: tokenOf(node) }]),
        ...checkDefects(gate),
        ...resultDefects(gate, tokenOf(node)),
    ];
};
```
