/** * Pure derivations for the two ORIENTATION parity rows the network-read-only * family senses (mmnto-ai/totem#2791; the 472 charter's § 4b / § 4c requests, * mmnto-ai/totem-strategy:operations/472-issue-disposition-preregistration.md): * * - `gh-issue-label-canon` — the label canon is PARSED from * `mmnto-ai/totem:scripts/sync-labels.ps1` at run time (Tenet 20: derived, * never mirrored) and a repo's live label list is judged against it. * - `gh-project-vocabulary` — the canonical option sets are PARSED from the * manifest row's own `expected-value-or-derivation` text, and a bound * project's single-select fields are judged against them. * * Everything here is pure over text / parsed JSON: no I/O, no network, no * module-level state. The verdict LINES (status / message) are rendered by * `parity-detect.ts`'s network-posture dispatch, which owns the cannot-verify * ladder; this module returns the drift FACTS only. The semantics are aligned * with the strategy-local twin (`mmnto-ai/totem-strategy:tools/gh-parity-twins.cjs`, * rung 1 of mmnto-ai/totem-strategy#472) so the two readers agree on every * cohort repo: colour compares without `#` and case-insensitively, description * compares exactly (untrimmed), the namespace token keeps its trailing space, * option ORDER is information and never a fault, and a governed field that is * absent IS a fault (its option set is empty, which is not the canonical set). * One disclosed asymmetry: this module strips a leading `#` from the CANON's * colour too (the twin lower-cases both sides but strips `#` on the live side * only), so a `#`-prefixed hex in the script would still match here — identical * readings while the script writes bare hex, as every one of its twenty-four * calls does today. Option NAMES are compared exactly on the live side; the canon's * authoring whitespace is trimmed (the prose grammar cannot avoid it, and a * quoted YAML member may carry it), so a padded live option name is a real * difference the board shows, never smoothed away. */ /** One canonical label as the script defines it (`gh label edit "" --color "" --description ""`). */ export interface CanonicalLabel { name: string; /** Lower-case hex without a leading `#` (the API's shape). */ color: string; description: string; } /** One retirement the script performs (`Merge-Label "" ""`). */ export interface LabelMerge { from: string; to: string; } /** The parsed canon: the defined labels, the namespace tokens derived from their names, the retirements. */ export interface LabelCanon { labels: CanonicalLabel[]; /** Derived from the canonical names, never hand-listed; the space is part of a colon token. */ namespaces: string[]; merges: LabelMerge[]; } /** One live label as `GET /repos/{owner}/{repo}/labels` returns it (already Zod-narrowed by the caller). */ export interface LiveLabel { name: string; color?: string | null; description?: string | null; } /** A canonical label whose colour or description the live repo redefined. */ export interface LabelRedefinition { name: string; /** Present when the colour differs (expected / actual, both normalized). */ color?: { expected: string; actual: string; }; /** True when the description differs from the canon's text. */ description?: boolean; } /** A live label that occupies a canonical namespace without being in the canon. */ export interface NamespaceSquatter { name: string; namespace: string; } /** The § 4c drift facts for one repo. */ export interface LabelCanonDrift { /** Every fault class empty. */ conforming: boolean; /** Canonical names absent from the live list. */ missing: string[]; /** Canonical names present with a different colour or description. */ redefined: LabelRedefinition[]; /** Live names inside a canonical namespace but outside the canon. */ squatters: NamespaceSquatter[]; /** Live names outside every canonical namespace — permitted, reported. */ extra: string[]; /** Retired names (a `Merge-Label` source) still present — reported, never a fault. */ retiredPresent: string[]; } /** Normalize a colour to the API's shape: no leading `#`, lower-case. */ export declare function normalizeLabelColor(raw: string | null | undefined): string; /** * Parse the label canon out of the script text. PURE. An empty `labels` array * means the text defined nothing — the CALLER refuses to judge against it * (never a conformance verdict from an empty canon). */ export declare function parseLabelCanon(scriptText: string): LabelCanon; /** * The namespace tokens the canonical names imply: the name through its first * `: ` (the space IS part of the token — `type:audit` does not squat on * `type: `), else through its first `-` (`tier-1` → `tier-`). A name with * neither contributes no token. Derived, so a new namespace in the script * becomes a sensed namespace here without a code change. */ export declare function namespaceTokensOf(names: readonly string[]): string[]; /** * The § 4c predicate over one repo's live labels. PURE. * * Faults: a canonical name absent; a canonical name present with a different * colour or description; a live name inside a canonical namespace that is not * in the canon (the "never redefine a canonical namespace" half). Live names * outside every canonical namespace (`routine:*`, bare words) are permitted * additions, reported and never flagged; a retired name still present is * reported the same way. (`disposition:*` is canonical since the script grew * its six `edit` lines — mmnto-ai/totem#2792 — so a stray value there is a * squatter, not an addition.) */ export declare function labelCanonDrift(live: readonly LiveLabel[], canon: LabelCanon): LabelCanonDrift; /** * Parse the canonical option sets out of the row's `expected-value-or-derivation` * text. The grammar is strict and small: clauses split on `;`, each clause * `Field = a | b | c`; a clause without `=` (e.g. "extra fields permitted") is * ignored; an empty field name or an empty option list drops the clause. The * Map preserves clause order. An EMPTY map means the row text yields no * governed field — the caller renders cannot-verify, never a hardcoded pass. */ export declare function parseExpectedOptionSets(text: string): Map; /** One single-select field as the project's field config exposes it (already narrowed by the caller). */ export interface ProjectSingleSelectField { name: string; options: readonly { name: string; }[]; } /** * `{ Status: [...], Priority: [...] }` for every single-select field on the * project. Option names are kept RAW: the live side is what the board shows, and * the canon side is the one that carries authoring whitespace (trimmed there). */ export declare function optionSetsOfProjectFields(fields: readonly ProjectSingleSelectField[]): Map; /** One governed field's fault. */ export interface ProjectVocabularyFault { field: string; kind: 'field-missing' | 'option-set-differs'; /** `option-set-differs` only: canonical options absent from the project. */ missing: string[]; /** `option-set-differs` only: project options outside the canonical set. */ extra: string[]; } /** The § 4b drift facts for one bound project. */ export interface ProjectVocabularyDrift { conforming: boolean; faults: ProjectVocabularyFault[]; /** Governed fields whose SET equals the canon in a different order or multiplicity (a duplicated option) — information, never a fault. */ orderDiffers: string[]; /** Single-select fields beyond the governed ones — permitted additions (LC's `M`), reported. */ added: string[]; } /** * The § 4b predicate: every governed field (a key of `expected`) present on the * project with EXACTLY the canonical option set, order-insensitive. PURE. */ export declare function projectVocabularyDrift(actual: ReadonlyMap, expected: ReadonlyMap): ProjectVocabularyDrift; //# sourceMappingURL=parity-label-canon.d.ts.map