/** * Version-pinned parity drift detector (PR-1, mmnto-ai/totem#2069). * * First detection slice on top of the merged manifest-parser skeleton (#2070). * Senses ONE tractability class — `version-pinned` — and only its DEPS subset * (the four `@mmnto/*` cohort-floor contracts: `mmnto-cli-version`, * `mmnto-totem-version`, `mmnto-mcp-version`, * `mmnto-pack-rust-architecture-version`). The verdict is **pin currency only** * (Tenet 20 claim-class bound): does the consumer's `@mmnto/*` pin resolve to * the current published cohort floor? It NEVER asserts semantic / file-content * drift — that would over-claim the `version-pinned` class. * * Layering: core must NOT import cli's `DiagnosticResult` (wrong dependency * direction). This module returns a core-local `ParityContractVerdict`; the CLI * (`doctor-parity.ts`) maps it to `DiagnosticResult` and owns the * `--strict`/`blocking` fail-promotion. The detector itself returns ONLY * `pass`/`warn`/`skip` — never `fail` — so the gate edge stays a CLI concern. * * Design invariants (mirroring `parity-manifest.ts` + `strategy-resolver.ts`): * - **Honest-absent (Tenet 14):** absence is never an error. Not-a-consumer, * floor-unresolvable, or a doctrine pin this slice doesn't handle → `skip` * (the manifest's `-` "cohort permits absence"). An *applicable* consumer * missing an expected pin is also `skip` while the manifest is scaffold, but * kept DISTINCT (expected-but-absent → a `warn` once the consumers lists are * verified) so the `consumers` field still catches the missing case. Never a * fabricated verdict. * - **NEVER networks:** the cohort floor is derived LOCALLY — self-in-tree * (the totem monorepo at the current git root), a `../totem` sibling * checkout, or — for a package totem doesn't publish — the contract's own * canonical-source repo (e.g. `../totem-strategy`). None reachable → * honest-absent `skip` with a reason. * - **Side-effect-free / no caching:** every call reads from scratch. Each * filesystem / git seam is injectable so tests drive synthetic fixtures. * - **Never throws:** every read failure degrades to a `skip`/`warn` verdict; * the sensor must never crash the doctor pipeline. */ import { type ParityContract, type ParitySense } from './parity-manifest.js'; import { type StrategyResolverOptions, type StrategyRootStatus } from './strategy-resolver.js'; /** * Parity dimension for toolchain-version rows. A row in this dimension that * resolves NO deps package (e.g. `pnpm-engine-version`) pins its engine via the * consumer's `packageManager` field instead — the toolchain reader senses it * (mmnto-ai/totem#2115). `mmnto-cli-version` shares this dimension but resolves * `@mmnto/cli`, so it stays on the deps path. */ export declare const TOOLCHAIN_DIMENSION = "toolchain-version"; /** * Core-local per-contract verdict. The CLI renders this directly (core cannot * depend on cli's `DiagnosticResult`/`CheckStatus`). The verdict vocabulary is * intentionally WIDER than the shared `CheckStatus` so the parity sensor can * honor the round's verdict-state split (mmnto-ai/totem#2073 req #1 — don't * collapse to a binary pass/fail) without rippling `CheckStatus` across every * unrelated doctor check: * - `pass` — verified equal / current. * - `warn` — drift (sensor-not-gate default; the CLI promotes a `warn` from * a `blocking: true` contract to `fail` ONLY under `--strict`). * - `info` — an intentional, attested fork (req #7) — NEVER gated/promoted. * - `unknown` — the Stale-Doctor-Paradox state: the canonical could not be * resolved, so the doctor can prove neither drift NOR currency. * NEVER rendered as `pass` (no self-certification); NEVER gated. * - `skip` — not-applicable / cohort-permits-absence / out of this slice. * * `fail` stays in the union as the CLI-edge promotion target, but a DETECTOR * never emits it (the gate edge is a CLI concern, unchanged from PR-1). */ export interface ParityContractVerdict { status: 'pass' | 'warn' | 'fail' | 'info' | 'unknown' | 'skip'; message: string; remediation?: string; } /** Test seams for {@link deriveCohortRepoId} (mirrors `StrategyResolverOptions`). */ export interface DeriveCohortRepoIdOptions { /** * Pre-resolved git `origin` remote URL. Production callers omit it and the * helper shells out via `git remote get-url origin`. When provided (even as * `undefined`, signalling "no remote"), the shell-out is skipped. */ remoteUrl?: string; /** * Full override of the remote reader. Takes precedence over `remoteUrl`. A * throwing reader is swallowed (git failure → fall through), so passing one * that throws exercises the no-network / no-throw path. */ readRemote?: (cwd: string) => string | undefined; /** Test seam — production callers omit and the helper invokes `resolveGitRoot(cwd)`. */ gitRoot?: string | null; } /** * Derive the current repo's cohort id (e.g. `totem-status`) used to evaluate a * contract's `consumers` applicability. Precedence: * 1. git `origin` remote — `…mmnto-ai/(.git)?` → ``. * 2. `package.json` `name` basename (scope stripped) — `@mmnto/totem-status` * → `totem-status`. * 3. git-root directory basename. * * Returns `undefined` only when nothing resolves. NEVER throws and NEVER * networks beyond the local `git remote get-url` read (which is swallowed on * failure — git being unavailable is a routine fall-through, not an error). */ export declare function deriveCohortRepoId(cwd: string, options?: DeriveCohortRepoIdOptions): string | undefined; /** * Resolve the `@mmnto/*` (or vendor) package name a deps/vendor contract pins. * Precedence: * 1. **explicit `package:` field** (mmnto-ai/totem-strategy#517) — the * machine-parseable name, derive-not-guess. Preferred when present. * 2. **canonical-source path locator** — when `canonicalSource` carries a * `:path/to/package.json` segment (e.g. `mmnto-cli-version`'s * `mmnto-ai/totem:packages/cli/package.json#version`), read the `name` * from that package.json under `floorRoot`. Authoritative — no id guess. * 3. **id convention** — `mmnto--version` → `@mmnto/` (fallback until all * contracts carry `package:`). * * Returns `undefined` for contracts with no `package:`, no path locator, and an * id that doesn't match the convention (e.g. `governance-doctrine`, `gate-config`) * so ONLY the contracts this slice handles resolve a name; the CLI keeps the rest * as `skip` stubs. * * @param floorRoot Optional root the canonical-source path locator anchors at * (the resolved cohort-floor repo). Omit when only `package:` / * the id convention is wanted. */ export declare function packageNameForContract(contract: ParityContract, floorRoot?: string): string | undefined; /** * Honest-absent cohort-floor resolution outcome (discriminated union, mirroring * `StrategyRootStatus`): * - `resolved: true` — `version` is the floor; `source` tags WHICH local * layer supplied it (`self-in-tree` | `sibling`). * - `resolved: false` — `reason` is an agent-surfacing string (e.g. clone the * monorepo as a sibling). The sensor renders this as a `skip`. */ export type CohortFloorStatus = { resolved: true; version: string; source: 'self-in-tree' | 'sibling' | 'canonical-source'; } | { resolved: false; reason: string; }; /** * Resolve the "current published version" cohort floor for `packageName`, * derived LOCALLY (NEVER networks), in precedence order: * (a) **self-in-tree** — the current git root IS the canonical-source repo * (the totem monorepo): glob `/packages/*​/package.json`, find * the one whose `name === packageName`, read its `version`. * (b) **sibling** — `/../totem` exists as a directory: glob its * `packages/*​/package.json` the same way. * (c) **canonical-source repo** — when `canonicalSource` names a repo totem * does NOT publish (e.g. `mmnto-ai/totem-strategy` for * `@mmnto/strategy-doctrine`), the floor lives in that repo's * `packages/*`, not totem's. Locate it via `resolveStrategyRoot` * (env / config / `../totem-strategy` sibling) and glob the same way. * Without this, strategy-published rows resolve `skip` instead of the * consumer-side `pass`/`warn` (mmnto-ai/totem#2108). * (d) **honest-absent** — none reachable → `{ resolved: false, reason }`. * * NEVER fabricates a floor and NEVER fetches (`resolveStrategyRoot` is local * fs only). Anchors at `gitRoot`, not cwd (mirroring `strategy-resolver.ts`). * Read failures within the glob are swallowed per-file so one corrupt * package.json can't crash the resolver. * * The floor is keyed structurally on `packageName` (the matching * `packages/*​/package.json` `name`), NOT on the consumer's cohort id — a * misderived repoId can't mask a genuine in-tree floor. */ export declare function resolveCohortFloor(packageName: string, gitRoot: string, canonicalSource?: string | null): CohortFloorStatus; /** Test seams + context for {@link detectVersionPinnedContract}. */ export interface DetectVersionPinnedContext { /** The consumer repo to read `package.json` / `node_modules` from. */ cwd: string; /** The git root to anchor cohort-floor resolution at (NOT cwd — mirrors the resolver). */ gitRoot: string; /** The current repo's cohort id (from {@link deriveCohortRepoId}) for `consumers` applicability. */ repoId?: string; /** * Package name pre-resolved by the caller (the CLI resolves it once for * routing — {@link packageNameForContract}). When provided, the detector skips * re-resolving it (avoids a duplicate locator package.json read). Omit and the * detector resolves it itself. */ packageName?: string; /** * Test seam — override the consumer package.json read. Production callers omit * it and the detector reads `/package.json`. */ readPackageJson?: (absPath: string) => PackageJsonShape | undefined; } /** * Minimal package.json shape the detector reads. Deliberately loose — the file * is untrusted on-disk JSON, so every field is optional + runtime-checked. */ export interface PackageJsonShape { name?: string; version?: string; dependencies?: Record; devDependencies?: Record; optionalDependencies?: Record; /** Corepack engine pin (`@(+)?`) — read by the toolchain reader (mmnto-ai/totem#2115). */ packageManager?: string; } /** * Detect drift for ONE `version-pinned` deps contract. Returns a * `ParityContractVerdict`: * - **pass** — the consumer's resolved-installed `@mmnto/*` version is ≥ the * cohort floor (current). * - **warn** — installed < floor (stale pin). Sensor-not-gate default: the * detector returns `warn` even for a `blocking` contract; the CLI promotes * to `fail` only under `--strict`. * - **skip** — not-a-consumer / pin not declared / floor unresolvable / not a * deps contract this slice handles / unparseable range (honest-absent). * * NEVER emits `fail` (CLI-edge concern). NEVER throws (read failures degrade to * `skip`). NEVER networks (floor is local-only). NEVER asserts content drift * (claim-class bound to pin currency). */ export declare function detectVersionPinnedContract(contract: ParityContract, ctx: DetectVersionPinnedContext): ParityContractVerdict; /** * Inputs + test seams for {@link detectManualAttestationContract}. The sub-class * discriminant (`package:`) + the canonical source are read DIRECTLY off the * `contract` argument (single source of truth) — the context carries only the * consumer-local read seams + the reserved attestation date. */ export interface DetectManualAttestationContext { /** The consumer repo to read `package.json` from (the vendor-SDK pin read). */ cwd: string; /** The current repo's cohort id (from {@link deriveCohortRepoId}) for `consumers` applicability. */ repoId?: string; /** * OPTIONAL local attestation date (ISO-8601), sourced from the manifest's * `last-attested:` field (strategy#540 shipped the producer; the doctor wires * it through per mmnto-ai/totem#2125). When present the message reports it, * but the VERDICT stays `info` regardless — staleness is a message * refinement, NEVER a status change (manual-attestation never warns). */ attested?: string; /** * Test seam — override the consumer package.json read. Production callers omit it * and the detector reads `/package.json`. Invoked ONLY on the vendor-SDK * path; the doctrine-row path performs no read at all (a throwing seam proves it). * * Scope note (Greptile review on mmnto-ai/totem#2080): this seam covers ONLY the * top-level consumer package.json read (the declared-range lookup). The * installed-version half (`resolveInstalledVersion`) reads the REAL `node_modules` * ancestry and, for any VALID range, falls back to `semver.minVersion(range)` — so * `"installed: unresolved"` arises solely from an UNPARSEABLE range, never from * absent node_modules. A test injecting this seam with a valid range but no on-disk * install therefore sees the minVersion fallback BY DESIGN. The boundary is shared * verbatim with `detectVersionPinnedContract` (same `resolveInstalledVersion`, same * seam scope) — deliberately not widened here to keep the two detectors aligned. */ readPackageJson?: (absPath: string) => PackageJsonShape | undefined; } /** * Detect "drift" for ONE `manual-attestation` contract — the claim-class with NO * mechanical sensor (Tenet 19). The verdict ceiling is **`info` or `skip` ONLY**: * the doctor may SURFACE the tracked coupling/doctrine + FLAG staleness, but may * NEVER assert drift (`warn`), failure (`fail`), currency (`pass`), or an * unprovable-canonical (`unknown`) — there is no canonical to prove against. This * is the manifest's "surfaces last-attested + flags staleness only, NEVER fails" * contract, claim-class-tighter than the mechanical / version-pinned detectors * (which may `warn`). The `info`/`skip` ceiling means the contract can never enter * the CLI's `blockingDriftIds`, so it is structurally incapable of failing even * under `--strict`. * * Two sub-classes, discriminated by the contract's `package:`: * - **vendor-SDK coupling** (`packageName` set — `@google/genai`, * `@anthropic-ai/sdk`): reads the consumer's LOCAL pin (declared range + * resolved installed version, reusing the version-pinned machinery) and * surfaces it as `info` — NO cohort floor exists, so NO currency claim is made * (Tenet 16, attest-don't-enforce). The DURABLE manual-attestation case. * - **doctrine row** (`packageName` unset — `governance-doctrine`, * `agent-memory-doctrine`): `canonicalSource` is a cross-repo AGENTS.md the * local-read-only doctor must NOT fetch. Emits a pure `info` doctrine-currency * surface from the contract fields with ZERO on-disk I/O. TRANSIENT — graduates * to version-pinned when doctrine-distribution ships (strategy#511 / #526). * * NEVER throws (reads degrade to skip), NEVER networks, NEVER reads the cross-repo * `canonicalSource`, and NEVER emits `pass`/`warn`/`fail`/`unknown`. */ export declare function detectManualAttestationContract(contract: ParityContract, ctx: DetectManualAttestationContext): ParityContractVerdict; /** * A consumer's hand-added fork/override marker (mmnto-ai/totem#2073 req #7). * When present on a managed-block artifact, a content difference reads as an * INTENTIONAL, attested fork (`info`) rather than drift (`warn`). Sibling to — * NOT merged with — Proposal 292's publisher-generated currency sidecar * (strategy-claude 2026-06-04T0158Z: shared `totem:` namespace + `attested` * (ISO-8601) / `owner` field conventions, but separate author + lifecycle). * * Shape: ``. * Every attribute is optional — a bare `totem:fork` marker still flags a fork. */ export interface ForkMarker { reason?: string; owner?: string; /** ISO-8601 date the fork was last attested (as authored; not validated here). */ attested?: string; } /** The marker pair delimiting a managed block within a distributed artifact. */ export interface ManagedBlockMarkers { start: string; end: string; } /** Inputs + test seams for {@link detectMechanicalContract}. */ export interface DetectMechanicalContext { /** * The canonical managed-block content, ALREADY extracted from the running * `@mmnto/cli`'s own template by the CLI (core cannot import init-templates — * wrong dependency direction). `undefined`/empty signals the canonical was * unresolvable → `unknown` (the Stale-Doctor-Paradox guard). */ canonicalBlock: string | undefined; /** Absolute path to the consumer artifact on disk (e.g. `.claude/skills//SKILL.md`). */ consumerPath: string; /** The marker pair delimiting the managed block in BOTH canonical + consumer. */ markers: ManagedBlockMarkers; /** * Running `@mmnto/cli` provenance for the req-#5 self-report. The * Stale-Doctor-Paradox includes the doctor ITSELF being a shadowed/stale * binary supplying a stale in-process canonical; surfacing which binary * computed the verdict keeps the skills verdict honest about its own * provenance (a one-line self-report, not a resolver cascade — that's the * on-disk hooks case). */ binary?: { version: string; path: string; }; /** * Test seam — override the consumer file read. Production callers omit it and * the detector reads `` (UTF-8); a read failure is honest-absent. */ readFile?: (absPath: string) => string | undefined; /** * The remedy named by the remediation strings, composed as "via ${installHint}" * — a command (`totem init`, the default) or a short descriptive imperative. * Threaded per-artifact because a contract's consumer surface may not be one * `totem init` writes yet — naming a remedy that does not touch the surface * ships an inert instruction (mmnto-ai/totem#2532 slice-1 falsification round; * kin of the #2082 per-class installCommand). Prose beats a literal command * here: a command-shaped hint must be platform-true AND self-containing * (destination dirs, source presence, cwd) or it fails exactly in the states * it is prescribed for (#2559 cross-bot round). */ installHint?: string; } /** * Normalize a managed block for content comparison (req #3): CRLF / lone-CR → LF, * strip trailing whitespace per line, and trim leading/trailing blank lines. Two * blocks that differ ONLY in line endings or trailing whitespace — the win32 * checkout false-positive class — normalize equal. */ export declare function normalizeManagedBlock(block: string): string; /** * Extract the content BETWEEN the first `markers.start` and the first following * `markers.end`. Returns undefined when either marker is absent (an unmanaged or * marker-stripped file). The markers themselves are excluded from the result. */ export declare function extractManagedBlock(content: string, markers: ManagedBlockMarkers): string | undefined; /** * Parse a `` marker from * anywhere in `content`. Whitespace-tolerant (mirrors `REFLEX_VERSION_RE`). * Returns undefined when no marker is present; each attribute is independently * optional, so a bare `` returns an empty marker object * (still a fork signal). The attribute patterns are fixed literals (no dynamic * RegExp) and linear (no nested quantifiers) — ReDoS-safe. */ export declare function parseForkMarker(content: string): ForkMarker | undefined; /** * Short content hash (sha256, first 12 hex chars) of a normalized block, for the * verdict's machine-readable record (req #6). The hash IS the content-equality * evidence — bytes, never prose-parsing (req #2's spirit). */ export declare function hashManagedBlock(normalized: string): string; /** * Detect drift for ONE mechanical managed-block contract (the #2073 mechanical * skills slice). Compares the consumer's installed managed-block against the * running `@mmnto/cli`'s own canonical block (passed in by the CLI), normalized * for line-endings + trailing whitespace (req #3) and content-hashed (req #6). * Honors the verdict-state split (req #1) + the fork marker (req #7): * * - `pass` — normalized blocks equal. * - `warn` — blocks differ, no fork marker (drift); reports both short hashes. * - `info` — blocks differ AND a `totem:fork` marker is present (attested fork). * - `unknown` — canonical unresolvable (the doctor may itself be stale/shadowed); * never self-certify as `pass`. * - `skip` — consumer artifact not installed (cohort permits absence). * * NEVER throws (reads degrade), NEVER networks (canonical is in-process), NEVER * emits `fail` (the gate edge is a CLI concern, unchanged from PR-1). */ export declare function detectMechanicalContract(ctx: DetectMechanicalContext): ParityContractVerdict; /** Inputs + test seams for {@link detectGeneratedArtifactContract}. */ export interface DetectGeneratedArtifactContext { /** * The canonical artifact content, REGENERATED by the CLI from the running * `@mmnto/cli`'s own generator (e.g. `buildPrePushHook(getFallbackCommand(repo), tier)`) * for THIS repo's package-manager + tier — never a frozen string (a pnpm-flavored * canonical would false-positive an npm consumer). `undefined` signals the * generator could not produce it → `unknown` (the Stale-Doctor-Paradox guard). */ canonicalContent: string | undefined; /** Absolute path to the consumer artifact on disk (e.g. `.git/hooks/pre-push`). */ consumerPath: string; /** * The totem ownership/presence marker substring (e.g. `[totem] pre-push hook`). * Its ABSENCE in a present file means the artifact is a pure user file with no * totem content → `skip` (cohort permits absence), NOT drift. This presence * semantics is why generated artifacts need their own detector vs the skills * managed-block model — there, a markerless file IS stripped-drift. */ ownershipMarker: string; /** * Optional end marker bracketing the totem-owned region. post-merge / post-checkout * carry one; pre-commit / pre-push do not. When present, a totem block APPENDED * into a user-modified hook can be isolated for comparison; when absent, an * appended block degrades to `unknown` (cannot prove drift — claim-class-tight). */ endMarker?: string; /** * Running `@mmnto/cli` provenance for the req-#5 self-report (Stale-Doctor-Paradox: * a shadowed/stale doctor would regenerate a stale canonical — surface which binary * computed the verdict). The full ADR-072 resolver cascade lives in the CLI's * canonical-generator resolution; the detector just reports the resolved binary. */ binary?: { version: string; path: string; }; /** * Human-facing noun for this artifact class in the absence/drift copy (e.g. * `'git hook'`, `'SessionStart hook'`). Defaults to `'artifact'`. Threaded so the * detector — now shared across git hooks AND the static SessionStart hooks — never * hardcodes one class's terminology (Greptile review on mmnto-ai/totem#2082). */ artifactLabel?: string; /** * The install/repair command the remediation points at — `'totem hook install'` for * git hooks, `'totem init'` for SessionStart hooks. Defaults to `'totem init'` so a * SessionStart absence/drift is never told to run the git-hook installer. */ installCommand?: string; /** Test seam — override the consumer file read. Production callers omit it. */ readFile?: (absPath: string) => string | undefined; } /** * Detect drift for ONE generated-artifact contract (the mmnto-ai/totem#2073 hooks * slice) — the git hooks (`pre-commit` / `pre-push` / `post-merge` / `post-checkout`), * which the CLI regenerates per-repo via `build*Hook(getFallbackCommand(repo), tier)` * so the canonical matches THIS repo's package manager + tier (no frozen-string * false-positive). Honors the verdict-state split + the fork marker, and detects * STALE-VERSION drift (a hook frozen at an older generator's output differs from * today's regenerated canonical → `warn` — the detection half of mmnto-ai/totem#1854): * * - `pass` — the totem-owned content equals the regenerated canonical. * - `warn` — an owned hook drifted (incl. a stale pre-mmnto-ai/totem#2053 resolve order); reports both hashes. * - `info` — drift AND a `totem:fork` marker — an attested intentional fork. * - `unknown` — canonical unregenerable, OR a totem block appended inside a user * hook with no end marker to isolate it (cannot prove drift). * - `skip` — hook absent, OR present but not totem-managed (cohort permits absence). * * NEVER throws (reads degrade), NEVER networks (canonical is in-process), NEVER * emits `fail` (the gate edge is a CLI concern, unchanged from PR-1). */ export declare function detectGeneratedArtifactContract(ctx: DetectGeneratedArtifactContext): ParityContractVerdict; /** * The probe sub-kinds this slice ships (the two deliverable-1 rows): * - `mcp-registration` — does `.mcp.json` register a totem MCP server? The * PRESENT rung of `knowledge-search-access` ("a working query path exists * from this agent surface"). The usable rung (a live bounded search exec) * is deliberately NOT shipped: real `totem search` embeds the query via a * cloud API, which a §12.5 never-network probe cannot run — flagged to * strategy with the row-downshift question. * - `settings-floor` — does `.claude/settings.json` suppress the governance * floor (`claude-settings-minimum-capability`)? Canonical-at-intent-altitude * (296 §6(c)): the canonical is a minimum-capability CONTRACT, so the probe * senses explicit SUPPRESSION only — an absent file (or any unrelated * content) is `pass`; the doctor never prescribes settings content. */ export type CapabilityProbeKind = 'mcp-registration' | 'settings-floor'; /** Inputs + test seams for {@link detectCapabilityProbeContract}. */ export interface DetectCapabilityProbeContext { kind: CapabilityProbeKind; /** Absolute path of the probed file (`.mcp.json` / `.claude/settings.json`). */ consumerPath: string; /** * `settings-floor` only: absolute path to `.mcp.json`, read to DERIVE the * totem MCP server names cross-checked against `disabledMcpjsonServers` * (derive-not-hardcode, Tenet 20). Absent/unreadable → nothing to cross-check * (the MCP half of the floor is vacuous; the hooks half still applies). */ mcpJsonPath?: string; /** The state-level THIS probe proves by design (`present` for both kinds today). */ probedLevel: ParitySense; /** * The row's declared `senses:` (open string off the contract). When it names * a RECOGNIZED level stronger than `probedLevel`, a would-be `pass` is capped * at `unknown` — the green-halo invariant at probe altitude: a presence-PASS * must never render as a capability-PASS (296 §6(a)3 / strategy#591). */ declaredSenses?: string; /** Test seam — override the file read. Production callers omit it. */ readFile?: (absPath: string) => string | undefined; } /** * Detect ONE capability-probe contract (`manifestation: capability-probe`, * 296 §6(a)2). Deterministic, local-read-only, NEVER networks, NEVER throws, * and NEVER emits `fail` (CLI edge owns promotion) or `info` (probes decide, * they do not attest — "sense-only" means no actuator, not an info ceiling; * the 296 §6(a)4 settled vocabulary is pass/warn/skip/unknown). */ export declare function detectCapabilityProbeContract(ctx: DetectCapabilityProbeContext): ParityContractVerdict; /** * A parsed `` declaration * marker (Prop 305 §3 agent-bus class). Every attribute is independently * optional at PARSE time; the VALIDITY rule (a real declaration binds BOTH a * role and a seat) lives in {@link detectDeclaredContract} so a missing * attribute surfaces as a NAMED why-not, never a silent drop (fail loud, Tenet 4). */ export interface DeclarationMarker { role?: string; seat?: string; /** ISO-8601 date the declaration was authored (as authored; not validated here). */ declared?: string; } /** * Parse a `` declaration marker from * anywhere in `content`. Modeled on {@link parseForkMarker}: dotAll + whitespace * tolerant, `.*?` non-greedy + bounded by the first `-->` (linear, no nested * quantifiers → ReDoS-safe). `token` is the full bare marker name (e.g. * `totem:agent-bus`) and is regex-escaped via the shared `escapeRegex`, so a * `:` or any metachar is matched as a literal, never as a live pattern (the * token is code-owned by the CLI registry, but escaping keeps it robust * regardless). The FIRST marker for `token` wins — duplicates are ignored, the * same single-match posture as {@link parseForkMarker}. The token must be * followed by whitespace or `-->` (a lookahead, not `\b`), so a token never * prefix-matches a longer sibling (`totem:agent-bus` vs `totem:agent-bus-v2`) * and non-word-char-ending tokens need no special casing. Returns undefined * when no marker for `token` is present. */ export declare function parseDeclarationMarker(content: string, token: string): DeclarationMarker | undefined; /** Inputs + test seams for {@link detectDeclaredContract}. */ export interface DetectDeclaredContext { /** Absolute path of the file that carries the declaration marker (AGENTS.md). */ filePath: string; /** The bare `totem:` marker name the declaration is authored under (e.g. `totem:agent-bus`). */ markerToken: string; /** Test seam — override the file read. Production callers omit it. */ readFile?: (absPath: string) => string | undefined; } /** * Detect ONE `manifestation: declared` contract (Prop 305 §3 agent-bus class). * A declaration SURFACE — the repo AUTHORS the binding itself in a file * (AGENTS.md) via a `` HTML-comment * marker rather than installing a managed block. This sensor claims DECLARATION * PRESENCE ONLY; whether the declared bus actually EXECUTES its duties is * adherence-class (Tenet 19 / Prop 305 §3.5) and is NEVER inferred from this row. * * Deterministic, local-read-only, NEVER networks, NEVER throws, NEVER emits * `fail` (CLI edge owns promotion), and NEVER `warn`/`fail` on absence — an * undeclared repo is honest-absent (`skip`), the row's own "honest-absent until * a repo declares" semantics, not drift. * * - `pass` — marker present with BOTH role + seat parsed (a well-formed binding). * - `skip` — file absent, marker absent, or the marker is missing role/seat * (the why-not names the missing attribute — fail loud, Tenet 4). */ export declare function detectDeclaredContract(ctx: DetectDeclaredContext): ParityContractVerdict; /** * The parse mode for a value-equality field's on-disk config file. Declared per * row in the CLI registry (`valueEqualityFieldsFor`) — derive-not-guess, so an * unrecognized format yields `unknown`, never a silent mis-parse. */ export type ValueEqualityFormat = 'yaml' | 'json'; /** * One value-equality field spec, resolved by the CLI registry from a contract id. * The EXPECTED value is deliberately NOT carried here — it is read from the * contract's own `expectedValueOrDerivation` (strategy#738 Q1: the manifest field * is the canonical, derived LOCALLY per Tenet 6). The registry supplies only WHERE * to look, never WHAT to expect (no second source of truth). */ export interface ValueEqualityField { /** Absolute path to the consumer config file (e.g. `/.coderabbit.yaml`). */ consumerPath: string; /** * The dotted config path as discrete SEGMENTS (`['reviews', 'profile']`), NOT a * pre-split string — a literal key containing a `.` would be mis-split otherwise * (totem-codex panel, strategy#738). The CLI registry owns the segments. */ pathSegments: string[]; /** Parse mode for {@link consumerPath}. */ format: ValueEqualityFormat; /** Display name for the verdict line. */ lineName: string; } /** Inputs + test seams for {@link detectValueEqualityContract}. */ export interface DetectValueEqualityContext { /** Current repo's cohort id for `consumers` applicability (verbatim parity with the other detectors). */ repoId?: string; /** The field spec (file + path + format) the CLI registry resolved for this contract. */ field: ValueEqualityField; /** Test seam — override the consumer file read. Production callers omit it. */ readFile?: (absPath: string) => string | undefined; } /** * Detect drift for ONE `manifestation: value-equality` contract (strategy#738 * Slice A, the Proposal 296 §13 promotion of the bot-review-config rows up from * `attestation`). Reads a scalar at a dotted path in the consumer's on-disk * config file and compares it — typed, never blanket-stringified — against the * row's own `expectedValueOrDerivation` (the canonical, read LOCALLY; NEVER * networks). Honors the verdict-state split + the honest-absent taxonomy the * cohort panel settled (strategy#738): * * - `pass` — the path resolves and the value equals the expected scalar. * - `warn` — present-but-mismatched, OR the path is absent on a present file * (incl. traversal through a non-object): applicable drift. * - `unknown` — the file is present but unparseable: equality is unprovable * either way (value-equality is NOT a config-validity detector). * - `skip` — not-a-consumer / repo-id-unresolvable-under-scope / the file is * wholly absent (applicable-but-missing scaffold hedge, mirroring * detectVersionPinnedContract — flips to a drift `warn` once the * consumers lists are verified) / no expected declared. * * NEVER emits `fail` (the CLI edge owns `--strict` promotion). NEVER throws * (reads/parses degrade to skip/unknown). NEVER networks. Claim-class bound: a * `pass` asserts ONLY "this dotted path holds this scalar" — never that the bot * is on-demand, that the vendor loads it, or that the surface is enforced (those * are loaded/usable claims outside Slice A). */ export declare function detectValueEqualityContract(contract: ParityContract, ctx: DetectValueEqualityContext): ParityContractVerdict; /** * §6 normalize-before-hash for a distributed lock artifact — byte-for-byte the * canonical `tools/build-strategy-doctrine.cjs` `normalize()` the publisher hashes * with (the builder header MANDATES the verifier reconcile with it): CRLF / lone-CR * → LF, strip trailing spaces/tabs per line, pop ALL trailing blank lines, join with * exactly one terminal `\n`. Idempotent — re-normalizing a shipped (already-normalized) * file is a no-op, so `hash(shipped)` holds cross-platform. * * DISTINCT from {@link normalizeManagedBlock} (which ALSO trims LEADING blank lines and * leaves NO terminal newline) — the two must not be conflated; this one mirrors the lock * publisher exactly. A golden test pins the byte-for-byte parity against a precomputed hash. */ export declare function normalizeLockArtifact(text: string): string; /** * Full sha256 of a normalized lock artifact, `sha256:`-prefixed hex — byte-for-byte * the publisher's `sha256` helper (`'sha256:' + sha256hex(utf8)`). DISTINCT from * {@link hashManagedBlock} (a 12-char SHORT hash for managed-block verdict records); * the lock content-hash is the FULL digest the lock records. */ export declare function hashLockArtifact(normalized: string): string; /** The single lock schema version this reader understands (mirrors the manifest gate). */ export declare const SUPPORTED_LOCK_SCHEMA_VERSION = 1; /** One parsed `artifacts[]` entry from a strategy-doctrine lock (camelCase). */ export interface LockArtifact { /** Package-relative path to the distributed snapshot (resolves under the package dir). */ path: string; /** `repo:path` strategy-canonical source for the vs-canonical layer. */ canonicalSource: string; /** `sha256:`-prefixed hex of the normalized canonical content at publish. */ contentHash: string; /** Strategy-canonical commit the snapshot was cut from (provenance-info only). */ lastPublishedSha: string; } /** A parsed + validated strategy-doctrine lock (Proposal 292 §10.6). */ export interface StrategyDoctrineLock { schemaVersion: number; package: string; version: string; published: string; artifacts: LockArtifact[]; } /** * Honest-absent lock parse outcome (discriminated union, mirroring * {@link parseParityManifest}): * - `unparseable` — invalid JSON or schema-validation failure. * - `unsupported-schema` — `schema-version` ≠ the supported version. * - `ok` — a fully parsed + validated lock. * NEVER throws — every failure is a first-class return value. */ export type LockParseResult = { status: 'unparseable'; reason: string; } | { status: 'unsupported-schema'; schemaVersion: number; } | { status: 'ok'; lock: StrategyDoctrineLock; }; /** * Parse raw `strategy-doctrine.lock` JSON into a validated {@link StrategyDoctrineLock}. * The `schema-version` gate runs BEFORE full validation so an incompatible future shape * is rejected with a clear `unsupported-schema` signal rather than a confusing * v1-shaped Zod failure (mirrors {@link parseParityManifest}). NEVER throws. */ export declare function parseStrategyDoctrineLock(jsonText: string): LockParseResult; /** One verdict line from {@link detectLockContentContract} — per artifact × per layer. */ export interface LockContentLine { lineName: string; verdict: ParityContractVerdict; } /** Inputs + test seams for {@link detectLockContentContract}. */ export interface DetectLockContentContext { /** Current repo's cohort id for `consumers` applicability (verbatim parity with the other detectors). */ repoId?: string; /** * The installed `@mmnto/strategy-doctrine` package dir (e.g. * `/node_modules/@mmnto/strategy-doctrine`). The lock + each * `artifacts[].path` resolve under it; the CLI registry constructs it. */ packageDir: string; /** Lock filename within {@link packageDir} (default `strategy-doctrine.lock`). */ lockFileName?: string; /** Anchor for the vs-canonical sibling resolution (the git root). */ gitRoot: string; /** Test seam — override file reads. Production callers omit it (reads UTF-8 on disk). */ readFile?: (absPath: string) => string | undefined; /** Test seam — override the dir-exists probe. Production callers omit it. */ dirExists?: (absPath: string) => boolean; /** * Test seam — override the real-path resolver for the symlink-escape guard (default * {@link defaultRealpath}). Returns the canonicalized absolute path, or undefined when * the target does not exist / is unresolvable. */ realpath?: (absPath: string) => string | undefined; /** Test seam — override the strategy-root resolver (default {@link resolveStrategyRoot}). */ resolveStrategyRootFn?: (cwd: string, options?: StrategyResolverOptions) => StrategyRootStatus; /** Test seam — local git-object existence check for the last-published-sha note. */ gitObjectExists?: (sha: string, cwd: string) => boolean; } /** * Detect drift for the `manifestation: content-hash` strategy-doctrine lock-content * contract (mmnto-ai/totem#2107, strategy#754). Re-derives each distributed artifact's * `content-hash` from the consumed lock via the §6 normalize+sha256 contract and compares * it in TWO honest-absent layers, NEVER a fetch (Tenet 6/13): * * - **self-consistency** (ALWAYS, local): `sha256(normalize(packaged file at path)) == * its lock content-hash` — proves the shipped snapshot is internally intact. * - **vs-canonical** (ONLY when a local `../totem-strategy` sibling resolves): the same * recompute against the artifact's lock `canonical-source` within the sibling — proves * the pin still reflects strategy-canonical. SKIP honest-absent otherwise. * * Returns ONE line per artifact × per layer (the layers render SEPARATELY — a collapsed * "content drift" verdict would over-claim which layer drifted). `last-published-sha` is * provenance-INFO only (a LOCAL git-object existence note when a sibling resolves) — NEVER * a gating comparator, NEVER `sha == HEAD`. * * Top-level honest-absent: not-a-consumer / repo-id-unresolvable → skip; package not * installed → skip (the version-pin currency row senses the pin); lock absent while the * package is present → warn (structurally incomplete); lock unparseable / unsupported-schema * → warn. * * NEVER emits `fail` (the CLI edge owns `--strict` promotion). NEVER throws (reads * degrade). NEVER networks. */ export declare function detectLockContentContract(contract: ParityContract, ctx: DetectLockContentContext): LockContentLine[]; /** * Per-surface fetch outcome the CLI edge resolves for one governed-state read * (Prop 296 §14 clause 2). The detector maps each to a verdict WITHOUT ever * networking: * - `ok` — a 200 with a parseable body (the detector inspects it). * - `no-transport` — `gh` unavailable / spawn failed / offline → honest-absent * `skip` (§14 clause 4). Distinct from an auth failure. * - `auth` — no token, an under-privileged token, or a 401/403 → `unknown`. * - `not-found` — a 404 on the governed surface (repo/branch invisible, or a * repo-scoped CI token that cannot see the sibling) → `unknown`. * - `error` — a 5xx / timeout / unparseable body → `unknown` (transient). */ export type NetworkSurfaceOutcome = 'ok' | 'no-transport' | 'auth' | 'not-found' | 'error'; /** * One fetched governed-state surface. `data` carries the RAW parsed JSON only on * an `ok` outcome — untrusted boundary input the detector Zod-narrows before * trusting any field. `detail` is optional render context (e.g. `HTTP 403`). */ export interface NetworkSurfaceSnapshot { outcome: NetworkSurfaceOutcome; /** Raw parsed JSON payload when `outcome === 'ok'`; undefined otherwise. Untrusted — narrowed in the detector. */ data?: unknown; /** Optional human-readable outcome detail for the rendered line (e.g. an HTTP status). */ detail?: string; } /** * The externally-hosted surfaces a network-read-only probe reads for one * repo. Each is present only when the CLI edge attempted it (the fetch step * fetches per repo the union of surfaces the in-scope rows need): * - `repoSettings` — `GET /repos/{owner}/{repo}` (row-1 merge posture). * - `rulesets` — the repo's ruleset DETAILS (rows 2 + 3). * - `branchProtection` — classic `GET …/branches/{branch}/protection` (row-3). * - `labels` — `GET /repos/{owner}/{repo}/labels`, EVERY page * concatenated (`gh-issue-label-canon`); a page cap * degrades the whole surface, never an undercount. * - `projectFields` — the bound project's field config, one GraphQL read * (`gh-project-vocabulary`); the raw response body. */ export interface NetworkRepoSurfaces { repoSettings?: NetworkSurfaceSnapshot; rulesets?: NetworkSurfaceSnapshot; branchProtection?: NetworkSurfaceSnapshot; labels?: NetworkSurfaceSnapshot; projectFields?: NetworkSurfaceSnapshot; } /** * Which GH Project a roster repo binds for the `gh-project-vocabulary` row * (mmnto-ai/totem#2791). Only the CURRENT repo's binding is derivable locally * (`orient.projectNumber`); a sibling's `totem.config.ts` is not a network * surface this checkout reads, so a sibling is honest-absent by construction. */ export type ProjectBinding = { kind: 'bound'; owner: string; number: number; } | { kind: 'unbound'; } | { kind: 'sibling'; }; /** * One repo's pre-fetched network snapshot. `repoSlug` is the `owner/repo` * addressed on the API; `repoId` is the cohort id used for `consumers` * applicability (the repo segment / {@link deriveCohortRepoId} result). §14 * clause 3: one verdict LINE is emitted per repo, never one blended verdict. * `project` is present only when the vocabulary row is in scope for the repo. */ export interface NetworkProbeRepoSnapshot { repoSlug: string; repoId: string; surfaces: NetworkRepoSurfaces; project?: ProjectBinding; } /** * The rows the network-read-only family senses (routing key = the contract * id): the three Prop 296 §14 posture rows plus the two 472-charter * orientation rows (mmnto-ai/totem#2791). */ export type NetworkPostureRow = 'repo-merge-posture' | 'repo-required-checks-posture' | 'repo-branch-protection-posture' | 'gh-issue-label-canon' | 'gh-project-vocabulary'; /** Inputs + test seams for {@link detectNetworkPostureContract}. */ export interface DetectNetworkPostureContext { /** Which posture row to evaluate — selects the surface reads + verdict logic. */ row: NetworkPostureRow; /** * Pre-fetched per-repo snapshots (roster resolved + fetched at the CLI edge). * The detector NEVER networks — it only inspects these. The row's `consumers` * scope is applied PER-REPO against each snapshot's `repoId`. */ repos: NetworkProbeRepoSnapshot[]; /** * `repo-required-checks-posture` only: absolute path to the totem-side * canonical ruleset declaration (`.totem/rulesets/main.json`). Read via * {@link readFile}. Absent file → honest-absent `skip` ("declaration not yet * committed"), never an error. */ declarationPath?: string; /** * `gh-issue-label-canon` only: the label canon as a ROSTER-WIDE surface — the * TEXT of `mmnto-ai/totem:scripts/sync-labels.ps1` (`data`), resolved at the * CLI edge from the local checkout or the canonical fetch, with `detail` * naming which. Parsed here (Tenet 20). Absent or non-`ok` → cannot-verify * for every in-scope repo, never a conformance verdict. */ labelCanon?: NetworkSurfaceSnapshot; /** Test seam — override the declaration read. Production callers omit it (reads UTF-8 on disk). */ readFile?: (absPath: string) => string | undefined; } /** * Sense the network-read-only rows — the three Prop 296 §14 posture rows and the * two 472-charter orientation rows (mmnto-ai/totem#2791) — against pre-fetched * snapshots. Returns an ARRAY of per-repo verdict lines (the {@link LockContentLine} * pattern — the CLI's flatMap render + R2 contract-counting already support * multi-line rows). NEVER networks (the fetches ran at the CLI edge), NEVER * throws (every read/parse failure degrades to a verdict), NEVER emits `fail` * (the CLI edge owns `--strict` promotion) and NEVER a drift verdict on an * auth/transport failure (§14 clause 2). * * Applicability: the row's `consumers` scope is applied PER-REPO against each * snapshot's `repoId` (verbatim with {@link detectLockContentContract}). A row * scoped `consumers: [totem]` senses only the roster repos whose id is `totem`; * an empty in-scope set yields one honest-absent `skip`. */ export declare function detectNetworkPostureContract(contract: ParityContract, ctx: DetectNetworkPostureContext): LockContentLine[]; //# sourceMappingURL=parity-detect.d.ts.map