Skip to content

Ubiquitous Language

88 terms across 3 bounded contexts.

See ADR-0053 for the governing pattern.

Actors

Kind: policy · Context: shared-kernel · Anchor: src/driver_contracts.py:WorkerRole · Confidence: accepted

The third layer of the PAAA governance model (ADR-0143): who or what is authorized to act on a repository, and with what delegated authority. It answers "who may change what?". The agents/ tree — role contracts and chamber charters — is the Actors declaration per the 2026-08-25 house standard (#11741); a governing declaration may point at that directory but must never re-declare roles in YAML. Adjacent surfaces bound what an authorized actor may do rather than naming who it is: WorkerRole fixes the closed set of roles a director may request, RepoRecord.data_class fixes the data-governance class enforced at every model spawn, and the merge-policy autonomy classes (act / ask) fix where an agent may proceed alone and where it must ask.

Invariants: - Actors are declared by the agents/ directory layout; a manifest may point at it and must not restate roles — two declarations of who may act is one too many. - A role outside the declared vocabulary cannot be invented at runtime; unknown authority values fail closed. - Delegated authority is bounded: an actor classified ask cannot self-promote to act.

Actuator

Kind: control_role · Context: shared-kernel · Anchor: src/base_runner.py:BaseRunner · Confidence: accepted

The component that applies a Controller's action to the Plant: dispatches an agent runner, opens a PR, swaps a pipeline label. BaseRunner is the canonical actuator; PRManager realizes the PR-creation and label-swap actions.

Invariants: - Every Actuator action is subject to the Governor's saturation and safety limits.

ADRIndex

Kind: service · Context: shared-kernel · Anchor: src/adr_index.py:ADRIndex · Confidence: accepted Aliases: adr cache, adr catalog, architecture decision index

Mtime-based runtime cache over the ADR directory that parses docs/adr/*.md on first access and re-scans only when the directory mtime changes. Exposes parsed ADR records — including normalized status, context summary, cited source files, and symbol-level citations — to caretaker loops and agent prompts. Acts as the authoritative in-process view of architecture decisions, enabling callers such as adr_citation_resolve.unresolved_citations (the ADR-0136 citation gate) and AdrConformanceLoop to check what live ADRs cite without re-reading the filesystem on every access. The module docstring frames it explicitly as load-bearing: agents must know what has already been decided before they plan.

Invariants: - Re-scans the ADR directory only when its mtime changes; returns the cached ADR list on a stable directory - Status values are normalized to one of Accepted, Proposed, Superseded, Deprecated, or Unknown — no raw status strings escape the parser

ADRPreValidator

Kind: service · Context: caretaker · Anchor: src/adr_pre_validator.py:ADRPreValidator · Confidence: accepted Aliases: adr pre-validator, adr structural validator

A service that validates ADR structure before submission to the ADRReviewPanel, catching structural defects early in the review pipeline. Checks include: status field presence and validity, required section presence and non-emptiness (Context, Decision, Consequences), ADR number collisions, supersession integrity, volatile line citations, stale 'requires amending' notes, bare ADR references lacking title annotations, source-symbol references against the live repo, and cross-reference title accuracy. Returns an ADRValidationResult that distinguishes auto-fixable issues from blocking ones, allowing the panel to skip reviews for trivially malformed drafts.

Invariants: - Runs all structural checks in a single validate() call and returns an ADRValidationResult — never raises on malformed input. - Issues are classified as fixable or non-fixable; has_fixable_only lets callers auto-repair before escalating to the review panel. - Cross-ADR checks (number collision, supersession, cross-reference titles) are skipped when all_adrs is not supplied, so single-ADR validation is always safe.

ADRReviewerLoop

Kind: loop · Context: caretaker · Anchor: src/adr_reviewer_loop.py:ADRReviewerLoop · Confidence: accepted Aliases: ADR reviewer loop, adr council review loop, adr review loop

Caretaker loop that polls for ADRs in Proposed status and runs panel reviews via ADRReviewPanel. The loop is intentionally thin: all review logic and output formatting live in ADRReviewPanel, keeping tick scheduling and business logic separately testable. Review interval is config.adr_review_interval.

Invariants: - The loop delegates entirely to ADRReviewPanel.review_proposed_adrs(); no review logic lives in the loop itself. - Kill-switch is via enabled_cb("adr_reviewer") and config.adr_reviewer_loop_enabled (ADR-0049).

ADRReviewPanel

Kind: service · Context: caretaker · Anchor: src/adr_reviewer.py:ADRReviewPanel · Confidence: accepted Aliases: adr review panel, review panel

ADRReviewPanel is the domain service that runs multi-agent review-panel sessions on proposed Architecture Decision Records. It scans the ADR directory for files marked Status: Proposed, gates each candidate through ADRPreValidator, detects near-duplicate ADRs via similarity scoring, orchestrates multi-round panel voting, and routes each outcome to acceptance, rejection, escalation, or duplicate-flagging. ADRReviewerLoop delegates all review logic to this service on every polling cycle.

Invariants: - CreditExhaustedError and AuthenticationError propagate out of the review batch rather than being swallowed per-item, so BaseBackgroundLoop can pause on a fatal billing signal. - Every ADR that reaches Accepted status is guaranteed to carry an Enforced by: line (injected as '(none)' if absent) before it is written back. - Pre-validation must pass before a review-panel session is started; a failing ADR is routed and counted separately without blocking the rest of the batch.

AgentPort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:AgentPort · Confidence: accepted Aliases: agent port, agent runner port

Hexagonal port for agent runner operations used by infrastructure modules. Implemented by agent.AgentRunner via base_runner.BaseRunner. The port was introduced so that infrastructure modules like merge_conflict_resolver can accept the agent runner via dependency injection without importing from the runner layer, keeping the four-layer boundary clean and making those modules independently testable with a mock.

Invariants: - Pure Protocol — no implementation, no state. - Three methods: build_command constructs the CLI invocation; execute runs the subprocess and returns the full transcript; verify_result checks that the agent produced valid commits and that make quality passes. - Parameter names and types are kept identical to the concrete implementations to satisfy structural subtype checks in tests/test_ports.py.

AgentRunner

Kind: runner · Context: builder · Anchor: src/agent/_runner.py:AgentRunner · Confidence: accepted Aliases: agent runner, implement runner, claude agent runner

Subprocess runner for the implement phase: launches a claude -p process inside an isolated git worktree to implement a GitHub issue. Builds the agent's self-check checklist (extended by recent review escalations), spec-match guidance, and requirements-gap context, then commits the agent's changes locally. Pushing the branch and creating the PR are deliberately left to other phases.

Invariants: - Phase name is fixed: _phase_name == 'implement'. - The runner commits inside the worktree but never pushes or opens a PR — that work belongs to downstream phases. - Self-check checklist is dynamically extended with checklist items from recurring review escalations.

Articles

Kind: invariant · Context: shared-kernel · Anchor: src/charter_model.py:Articles · Confidence: accepted

The second layer of the PAAA governance model (ADR-0143): what must remain true of a repository — standards, architectural constraints, security and compliance rules, and local policy. It answers "what rules apply to it?". Articles are carried today by docs/standards/, by ADRs that declare an **Enforced by:** block, by control/principles.yaml, by docs/standards/factory_autonomy/policy.yaml, by docs/standards/branch_protection/gates.toml, and by the per-repo charter of ADR-0121 as amended by #11748 (charter.yaml, whose articles: block is charter.Articles) — the surface a repository uses to declare which of them apply to it. Enforcement of Articles splits three ways: the declaration declares, a decision layer classifies normalized facts as compliant / violated / exempt / grandfathered, and HydraFlow acts on the verdict. blocking is not a fifth verdict but an orthogonal flag on the decision: the four classify what is TRUE of a subject, blocking decides what to DO about it, and they vary independently — "violated but not gating" is a real and common state (ADR-0143 Ruling 4 as amended 2026-08-31; policy.models.DecisionStatus has exactly four members and carries blocking as a separate field on StandardDecision).

Invariants: - Building standards are one class of Articles, not the whole of Articles — security, compliance, architecture, and local policy are Articles too. - The declaration is reviewable in git; a decision layer never runs tests, reads git, or writes to the repository. - Changing an Article is an enactment reserved to the operator (ENACT, not RATIFY); nothing automates an edit to the articles of a declaration.

Artifacts

Kind: aggregate · Context: shared-kernel · Anchor: src/jsonl_ledger.py:AppendOnlyJsonlLedger · Confidence: accepted

The fourth layer of the PAAA governance model (ADR-0143): everything a repository has produced and kept — the software itself, plus ADRs, tests, evidence, manifests, ledgers, and recorded decisions. It answers "what evidence and memory already exist?". This is by a wide margin the richest layer in HydraFlow: docs/arch/generated/ is regenerated every pull request, docs/wiki/ (including these term files) carries the repo wiki, .hydraflow/metrics/ carries the measurement streams, the append-only ledgers carry decisions and escapes, and a stamped repository carries a kernel lock. Artifacts are what the evidence collectors read to produce the normalized facts a decision layer classifies; read as input they are evidence, but evidence is not a fifth PAAA layer.

Invariants: - Evidence is Artifacts read as input, never a fifth layer — the four layers stay four. - Ledger records are append-only: an artifact is added or superseded, never silently rewritten. - A conformance claim over Artifacts must be reproducible offline from a clean checkout — no claim may depend on an external service being up.

Authority

Kind: policy · Context: shared-kernel · Anchor: src/models.py:HitlEscalation · Confidence: accepted

Two distinct notions the word must not conflate (ADR-0122). In the control register, authority is actuation permission — what a loop may do to the plant this cycle, bounded by the Governor (saturation limits, kill switch, credit holds); a Controller cannot override it. In the legal/constitutional register, authority is jurisdiction — who holds the decision: who may change what, by what procedure, reviewed by whom, and the escalation boundary where a decision leaves the factory's authority and passes to a human (models.py:HitlEscalation, human-signed envelopes). Qualify as "actuation authority" (control) or "decision authority / jurisdiction" (legal); the bare word defaults to the legal sense (jurisdiction).

Invariants: - Actuation authority (control) is bounded by the Governor and cannot be self-granted by a Controller. - Decision authority (legal) transfers to a human only across an explicit escalation boundary.

BaseBackgroundLoop

Kind: loop · Context: shared-kernel · Anchor: src/base_background_loop.py:BaseBackgroundLoop · Confidence: accepted Aliases: base background loop, loop base class

Abstract base class for every concurrent worker loop in the HydraFlow orchestrator (ADR-0001, ADR-0029). Owns the run-loop skeleton — enabled-check, interval management, status callbacks, BACKGROUND_WORKER_STATUS event publishing, error reporting, and trigger-based early wake-up — leaving subclasses to implement only the domain-specific _do_work and _get_default_interval hooks.

Invariants: - Subclasses must implement abstract methods _do_work and _get_default_interval. - AuthenticationError, AuthenticationRetryError, and CreditExhaustedError propagate; all other exceptions are logged and the loop retries on the next cycle. - Shared dependencies (event_bus, stop_event, status_cb, enabled_cb, sleep_fn, interval_cb) are bundled into a LoopDeps record passed to init.

BotPRPort

Kind: port · Context: shared-kernel · Anchor: src/term_proposer_loop.py:BotPRPort · Confidence: accepted Aliases: bot pr port

Hexagonal port used by caretaker loops (TermProposerLoop, others) to open auto-merging bot PRs without coupling to the GitHub-specific PR adapter. Production wiring composes push_branch + create_pr + add_pr_labels behind this Protocol.

Invariants: - Pure Protocol — no implementation; tests use a fake; production uses a thin adapter. - open_bot_pr is the only method; one PR per call; success returns the PR number.

Charter

Kind: value_object · Context: shared-kernel · Anchor: src/charter_model.py:Charter · Confidence: accepted

The governing declaration a HydraFlow-governed repository carries at its root, in the file named by charter.CHARTER_FILENAME (charter.yaml). It states the repository's Purpose, its Articles (adopted standards by id, an assurance class, and local articles), a pointer to where its Actors are declared, and the Artifacts it commits to carrying — the four layers of ADR-0143 — plus a rails: block holding the ADR-0121 template-conformance fields with their semantics unchanged. It supersedes rails.yaml, which loads for one cycle as a rails-only charter with a non-fatal legacy-rails-manifest finding. It is HydraFlow's implementation surface for the PAAA ontology, never a schema anyone outside HydraFlow is asked to conform to.

Invariants: - actors is a path pointer and never a role list; a list or mapping is rejected at load, because the agents/ tree is the Actors declaration (#11741) and a second copy rots. - articles.assurance reuses the RepoRecord.data_class vocabulary and fails closed on anything outside it — there is no second assurance scale. - Unknown standard ids and unknown template-layer names are tolerated and reported, never fatal (the ADR-0121 forward-compat rule). - A charter that declares nothing checkable is fatal rather than clean: a drift check with an empty subject list reads as coverage. - Editing purpose or articles is an ENACT reserved to the operator, not something the factory automates (ADR-0143 Ruling 6, guard 4).

CharterDriftCaretakerLoop

Kind: loop · Context: caretaker · Anchor: src/charter_drift_caretaker_loop.py:CharterDriftCaretakerLoop · Confidence: accepted Aliases: charter drift caretaker loop, charter drift caretaker, charter drift loop

Caretaker loop (ADR-0121 as amended by #11748, ADR-0143) that audits each managed repo's live state against its charter (charter.yaml) and files deduped hydraflow-find drift issues. Mirrors the ADR-drift (ADR-0056) and branch-protection-drift (ADR-0082) caretakers: periodic, contract-diffing, one deduped issue per finding class. Per tick it loads the repo's charter, observes live state, and computes drift — a declared standard whose docs/standards/<id>/ directory is gone, a declared required artifact that is absent, a missing template layer, a coverage-floor breach, or a missing declared domain gate script. Unknown layer names and unknown standard ids are reported but never file an issue (tolerated, forward-compat), and so is a legacy rails.yaml read through the one-cycle fallback. Dedup key is charter_drift_caretaker:<repo>:<finding_class>; when a finding class resolves, its open issue is closed and the key cleared so a recurrence re-files.

Invariants: - One deduped drift issue per (repo, finding class); never one issue per individual failing check. - A declared standard or artifact that is absent is drift; an undeclared extra of either is fine; an unknown standard id or layer name is reported but never fatal. - A charter that declares nothing checkable, or whose standard ids cannot be resolved against any registry, is FATAL rather than silently clean — a drift check with an empty subject list reads as coverage. - The coverage floor is evaluated only when observed coverage is known (fail-open: no drift on an unmeasured value). - Kill-switch is via enabled_cb("charter_drift_caretaker") (ADR-0049), then the static charter_drift_caretaker_loop_enabled config gate (default OFF). - Cadence is config-driven via charter_drift_caretaker_interval (default 1 day).

CIMonitorLoop

Kind: loop · Context: caretaker · Anchor: src/ci_monitor_loop.py:CIMonitorLoop · Confidence: accepted Aliases: CI monitor loop, ci monitor loop, continuous integration monitor loop

Caretaker loop that watches CI status on the main branch and files a hydraflow-ci-failure issue when CI goes red (ADR-0029, ADR-0065). The loop auto-closes the issue when CI recovers to green. Duplicate issue creation is prevented by tracking the open CI-failure issue number in memory, with rehydration from GitHub labels on first tick to survive restarts cleanly.

Invariants: - At most one open hydraflow-ci-failure issue exists at any time; the loop tracks _open_issue to enforce this. - On startup the loop rehydrates from existing hydraflow-ci-failure issues before its first check — a clean restart never duplicates a pre-existing issue. - Kill-switch is via enabled_cb("ci_monitor") and config.ci_monitor_loop_enabled (ADR-0049).

CircuitBreaker

Kind: control_role · Context: shared-kernel · Anchor: src/circuit_breaker.py:CircuitBreaker · Confidence: accepted Aliases: circuit breaker, breaker

Three-state (CLOSED → OPEN → HALF_OPEN → CLOSED) resilience primitive that protects against cascading failures by opening after max_failures consecutive failures and probing HALF_OPEN after reset_timeout seconds. Re-exported from signal_control.controllers alongside PidController, AimdController, and RetryController as a peer in the control vocabulary, and used by subprocess execution to gate calls that may raise CreditExhaustedError.

Invariants: - After max_failures consecutive failures, state transitions to OPEN and allow_request() returns False. - State transitions OPEN → HALF_OPEN only after reset_timeout seconds have elapsed since the last failure. - A success recorded in HALF_OPEN or CLOSED zeroes the failure count and returns state to CLOSED.

ContractRefreshLoop

Kind: loop · Context: caretaker · Anchor: src/contract_refresh_loop.py:ContractRefreshLoop · Confidence: accepted Aliases: contract refresh loop, cassette refresh loop, fake contract refresh loop

Trust-fleet loop that refreshes cassettes for fake contract tests on a weekly cadence (ADR-0045, ADR-0047, spec §4.2). Each tick: records cassettes against live gh/git/docker/claude into a tmp directory, diffs them against committed cassettes, short-circuits on hash-matching repeat drift (dedup via DedupStore), stages drifted cassettes, and opens a contract-refresh: YYYY-MM-DD (<adapters>) PR labeled contract-refresh + auto-merge. A post-refresh replay gate (make trust-contracts) runs after staging; failure opens a companion hydraflow-find + fake-drift issue so the factory dispatches a fake-repair implementer — PR auto-merge is not blocked by the replay gate. Per-loop telemetry spans (trace_collector.emit_loop_subprocess_trace) cover each recorder subprocess and the replay gate.

Invariants: - Dedup is keyed on the drift-report hash; identical drift on consecutive ticks does not refile the same PR. - Dedup is recorded only after the PR is opened, never before — transient failures do not silently block the next tick. - The replay gate failure opens a companion issue but does not block the auto-merge PR. - Kill-switch is via enabled_cb("contract_refresh") (ADR-0049); no config field.

Controller

Kind: control_role · Context: shared-kernel · Anchor: src/issue_store.py:IssueStore · Confidence: accepted

The component that converts Error into a control action — which unit to act on next and how hard. HydraFlow has a supervisory controller (which issue to admit/route, today FIFO in IssueStore) and an inner controller (the per-issue gate decision, e.g. the review_advisor PostVerifyResult APPROVE/VETO).

Invariants: - A Controller decides; it does not itself touch the Plant (that is the Actuator).

CorpusLearningLoop

Kind: loop · Context: caretaker · Anchor: src/corpus_learning_loop.py:CorpusLearningLoop · Confidence: accepted Aliases: corpus learning loop, adversarial corpus loop, escape signal ingestion loop

Trust-fleet loop that autonomously grows the adversarial test corpus from escape signals (ADR-0045, spec §4.1 v2). Each tick: reads open issues tagged with the escape label from the last DEFAULT_LOOKBACK_DAYS days, synthesizes each into a SynthesizedCase, runs three self-validation gates (harness acceptance, expected catcher trips, unambiguity across all catchers), materializes passing cases to tests/trust/adversarial/cases/<slug>/, and opens auto-merge PRs. A DedupStore keyed on corpus_learning:<issue_number>:<slug> prevents re-filing the same case on subsequent ticks.

Invariants: - All three validation gates must pass before a case reaches disk: harness accepts it, expected catcher trips, no other catcher also trips. - Cases that trip more than one catcher are rejected as ambiguous before they can corrupt the corpus. - No corpus_learning_enabled config field exists — kill-switch is purely via enabled_cb("corpus_learning") (spec §12.2, ADR-0049).

Credentials

Kind: value_object · Context: shared-kernel · Anchor: src/config.py:Credentials · Confidence: accepted Aliases: infrastructure credentials, secrets bundle

A frozen value object that bundles raw infrastructure secrets — GitHub token, the exception sensor's DSN, and the Bugsink API token — needed by runners and loops to authenticate with external services. Explicitly separated from HydraFlowConfig to ensure secrets never appear in domain-model serialization. Built from environment variables at startup via build_credentials() and injected as a constructor parameter into every loop or runner that calls an authenticated external API.

Invariants: - Immutable once constructed (frozen=True); no field may be mutated after build. - Never serialized as part of domain state — kept separate from HydraFlowConfig by design.

CreditExhaustedError

Kind: domain_event · Context: shared-kernel · Anchor: src/subprocess_util.py:CreditExhaustedError · Confidence: accepted

CreditExhaustedError signals that a gh/git/claude subprocess call failed because the underlying API billing account (Anthropic, or a one-shot OpenAI-compatible backend such as openrouter/zai/kimi) has run out of credits. It carries the billing-provider identity and an optional resume_at UTC reset time so the orchestrator can scope the resulting pause to only the loops routed to that provider — a Claude cap must not halt z.ai/kimi background workers and vice-versa. Every subprocess-spawning runner's broad except block must route this exception through reraise_on_credit_or_bug so it halts attempt-budget consumption instead of being silently swallowed and retried against an exhausted billing signal.

Invariants: - Subprocess-spawning runners MUST call reraise_on_credit_or_bug(exc) in their broad except block, or CreditExhaustedError is silently eaten and the loop burns attempt budget against an exhausted billing signal. - Carries provider (defaulting to "anthropic") so the orchestrator scopes the credit pause to only loops routed to the same provider, never cross-halting other providers. - resume_at, when parseable from subprocess error output, tells the orchestrator when credits are expected to reset.

DedupStore

Kind: service · Context: shared-kernel · Anchor: src/dedup_store.py:DedupStore · Confidence: accepted Aliases: dedup tracking set, dedup set store

DedupStore is a file-backed dedup tracking set, persisted as a sorted JSON list via atomic writes. It is the canonical shared-kernel mechanism the caretaker fleet uses to avoid re-filing or re-processing the same finding, case, or issue across ticks: a loop hashes or keys a piece of work, checks the DedupStore to see whether that key has already been handled, and records it once acted upon. It underlies idempotency for nearly every autonomous caretaker loop (ADR review, contract refresh, corpus learning, dependabot merge, diagnostics, entry evidence, fake-coverage audit, flake tracking, live corpus replay, merge-state watching, RC budget, sentry ingestion, skill-prompt eval, term proposal, wiki-rot detection).

Invariants: - get() returns an empty set rather than raising when the backing file is missing, unreadable, or contains malformed JSON - add/discard/set_all persist via atomic_write so a crash mid-write cannot corrupt the stored set - discard() is a silent no-op (no write) when the value is not present

DependabotMergeLoop

Kind: loop · Context: caretaker · Anchor: src/dependabot_merge_loop.py:DependabotMergeLoop · Confidence: accepted Aliases: dependabot merge loop, bot pr merge loop, auto-merge bot PRs loop

Caretaker loop that polls open PRs via GitHubDataCache and auto-merges those authored by Dependabot and other configured bot accounts after CI passes (ADR-0054, ADR-0057, ADR-0058). The list of bot authors is configurable via config; the loop compares pr.author.lower() against the set. Only PRs with a passing ReviewVerdict are merged — CI must be green before the loop touches a PR.

Invariants: - Author matching is case-insensitive. - CI must pass (ReviewVerdict green) before any merge is attempted; the loop never force-merges. - Kill-switch is via enabled_cb("dependabot_merge") and config.dependabot_merge_loop_enabled (ADR-0049).

DiagnosticLoop

Kind: loop · Context: caretaker · Anchor: src/diagnostic_loop.py:DiagnosticLoop · Confidence: accepted Aliases: diagnostic loop, diagnostic self-healing loop, escalation diagnostic loop

Caretaker loop that picks up escalated HITL issues and attempts autonomous self-healing via DiagnosticRunner (ADR-0050). For each escalated issue the loop runs a diagnosis that identifies severity, root cause, affected files, and a fix plan, then posts a structured diagnostic comment with human-guidance section. Attempts are tracked via AttemptRecord; the loop respects the attempt budget before escalating further. CreditExhaustedError is re-raised via reraise_on_credit_or_bug so attempt budgets are not silently burned against an exhausted billing signal.

Invariants: - reraise_on_credit_or_bug(exc) is called in the broad except block — credit exhaustion is never silently swallowed. - Severity is structured (P0_SECURITY through P4_HOUSEKEEPING) and appears in the diagnostic comment; reviewers do not need to parse free text to triage. - Kill-switch is via enabled_cb("diagnostic") (ADR-0049).

DiagramLoop

Kind: loop · Context: caretaker · Anchor: src/diagram_loop.py:DiagramLoop · Confidence: accepted Aliases: diagram loop, arch regen loop, architecture regen loop, L24

Caretaker loop (L24) that keeps docs/arch/generated/ in sync with src/ by running the arch-regen runner on each tick and opening a single idempotent bot PR (arch-regen-auto branch) when drift is detected (ADR-0029, ADR-0049). If no drift is found the tick exits silently. A secondary functional-area coverage check fires after regen; failures open a separate chore(arch): unassigned functional area issue via PRPort.find_existing_issue + create_issue, distinct from the regen PR.

Invariants: - The regen PR always targets the fixed branch arch-regen-auto; a force-push updates any existing open PR rather than opening duplicates. - The functional-area coverage issue is separate from the regen PR — one concern per artifact. - Kill-switch is HYDRAFLOW_DISABLE_DIAGRAM_LOOP=1 (ADR-0049 convention; no config field).

DimensionBaseline

Kind: control_role · Context: shared-kernel · Anchor: src/disturbance/registry.py:DimensionSpec · Confidence: accepted

The Set-point (ADR-0094) for one disturbance dimension in the Disturbance Dampener (ADR-0095): a version-controlled, count-per-signature YAML snapshot (disturbance/baselines/.yaml) of a dimension's known violations at the point it was last accepted. DimensionSpec is the registry entry binding a dimension's name, its ViolationDetector, its baseline path, and its fix prompt. The feedforward ratchet gate (src/disturbance/gate.py:run_gate) diffs a fresh detector pass against this baseline: any signature exceeding its baselined count is new and blocks the PR; a signature below its baselined count is burn-down progress. DisturbanceDampenerLoop's fix agents are instructed to prune resolved signatures from this baseline as part of each fix.

Invariants: - A baseline only blocks growth past its recorded per-signature count; it never requires the pre-existing backlog to be cleared before the gate can be enabled for a dimension. - Pruning a baseline entry without actually fixing the underlying violation is self-correcting: the next gate run re-diffs the detector's live findings against the pruned baseline and reports the signature as new, blocking the PR.

DisturbanceDampenerLoop

Kind: loop · Context: shared-kernel · Anchor: src/disturbance_dampener_loop.py:DisturbanceDampenerLoop · Confidence: accepted

The burn-down actuator half of the Disturbance Dampener (ADR-0095). Each tick it runs every registered dimension's ViolationDetector, loads that dimension's baseline, and selects a capped, smallest-first, deduped batch of BurndownUnits (one per dimension+file). For each unit it dispatches a coding agent via generate_and_open_pr_async to fix the violations in that file and prune the corresponding baseline entries, opening one PR per file (Pattern A). It follows SandboxFailureFixerLoop's caretaker shape: LoopDeps wiring, a kill-switch, max-PRs-per-tick saturation, per-unit attempt caps, and dedup so an already-opened unit is not redispatched.

Invariants: - Every per-unit exception handler calls reraise_on_credit_or_bug before recording a failure, so a credit-exhaustion signal is never absorbed as a per-file crash. - A unit is only marked opened (and deduped) after generate_and_open_pr_async reports status == 'opened'; a crashed or skipped unit leaves the unit eligible for retry up to auto_agent_max_attempts.

EdgeProposerLoop

Kind: loop · Context: caretaker · Anchor: src/edge_proposer_loop.py:EdgeProposerLoop · Confidence: accepted Aliases: edge proposer loop, UL edge proposer loop, term edge loop

Caretaker loop that densifies the ubiquitous-language term context map by proposing depends_on and implements edges between existing terms via static import graph analysis (ADR-0058, ADR-0060, ADR-0062). Each tick walks the import graph produced by build_import_graph, infers structural relationships between terms, and opens auto-merge bot PRs labeled hydraflow-ul-edges with updated term files. Unlike EntryEvidenceLoop, edge inference is static-analysis-driven, not LLM-driven. The hydraflow-ul-edges label causes review_phase to skip agent pipeline routing.

Invariants: - Edge inference is based on the import graph (build_import_graph), not LLM judgment — results are deterministic for a given codebase state. - The hydraflow-ul-edges label causes review_phase to skip the agent pipeline; the structural inference IS the work (ADR-0058). - Kill-switch is via enabled_cb("edge_proposer") (ADR-0049).

EntryEvidenceLoop

Kind: loop · Context: caretaker · Anchor: src/entry_evidence_loop.py:EntryEvidenceLoop · Confidence: accepted Aliases: entry evidence loop, wiki entry evidence loop, UL evidence loop

Caretaker loop that backfills Term.evidence links by matching wiki entries to ubiquitous-language terms via LLM (ADR-0062). Each tick processes up to entry_evidence_max_entries_per_tick unmatched entries, calls the LLM once per entry to identify genuinely related terms (not superficial name-fragment matches), and opens auto-merge bot PRs labeled hydraflow-ul-evidence with the updated term files. A DedupStore prevents re-processing entries that already have evidence on subsequent ticks. Mirrors EdgeProposerLoop (ADR-0058) and TermProposerLoop (ADR-0054) in structure but is LLM-driven rather than static-analysis-driven.

Invariants: - One LLM call per wiki entry per tick — no batching across entries within a single call. - The hydraflow-ul-evidence label causes review_phase to skip agent pipeline routing; the LLM-driven matching IS the work (ADR-0062). - Kill-switch is via enabled_cb("entry_evidence") (ADR-0049); no config field. - entry_evidence_max_entries_per_tick bounds the LLM spend per cycle.

Erosion

Kind: value_object · Context: shared-kernel · Anchor: src/erosion_metrics_loop.py:ErosionMetricsLoop · Confidence: accepted

Slow drift of a measured quantity away from where it should be — a control-register signal (erosion_metrics_loop.py:ErosionMetricsLoop, the erosion trends). Two sides must be named separately (ADR-0122). Plant-side erosion is decay in the code itself: rising duplication, scatter, god-module concentration — the process variable degrading. Reference-side erosion is setpoint erosion (#10829): the target drifting — a floor quietly lowered, a bound relaxed — so the regulator holds an eroded reference and reports health while the standard slips. Bare "erosion" means plant-side; always write "setpoint erosion" for the reference-side case, because the two have opposite fixes (tighten the plant vs restore the setpoint). Plant-side sensors in src/erosion/: change-spread, concept-scatter, duplication, concentration (fan-in), mass (god files / god classes by size) and suite hygiene (parametrize candidates, cross-file duplicate tests); the last two file one standing class issue each and are ratcheted at PR time against disturbance/baselines/mass.yaml / suite_hygiene.yaml.

Invariants: - Bare 'erosion' is plant-side (code); reference-side decay must be written 'setpoint erosion' (#10829). - Plant-side and setpoint erosion have opposite remedies — tighten the plant vs restore the setpoint.

Error

Kind: control_role · Context: shared-kernel · Anchor: src/harness_insights.py:FailureRecord · Confidence: accepted

The gap between Set-point and measured state that a Controller acts to reduce: unresolved review concerns, a REQUEST_CHANGES verdict, route-backs, or a recorded FailureRecord. On main the signal is largely binary; a continuous per-issue error is a known-open surface.

Invariants: - Error is derived (Set-point minus measured state), never authored directly.

EscalationReconciler

Kind: service · Context: caretaker · Anchor: src/escalation_reconcile.py:EscalationReconciler · Confidence: accepted Aliases: escalation lifecycle reconciler, hitl escalation reconciler

EscalationReconciler is the shared reconciliation service that closes the loop on hitl-escalation lifecycle state for trust/caretaker loops. It resolves two lifecycle paths every adopting loop needs: reconcile_closed drops the dedup key and attempt counter when a human/external actor closes an escalation issue (re-arming the detector), while reconcile_open auto-closes an open escalation whose subject is no longer present in the loop's currently-detected set (the gap was fixed or was a false positive), clearing its dedup/attempt state so a genuine recurrence escalates fresh. It encodes the bot-vs-human close distinction via the shared BOT_CLOSE_MARKER_LABEL/is_bot_close predicate so a programmatic close never prematurely re-arms a still-active subject.

Invariants: - reconcile_open only proceeds when the tick's detection completed (active_subjects is not None) — closing on incomplete/partial scan data would kill real escalations and reset their attempt budgets. - A bot/programmatic close (marked with BOT_CLOSE_MARKER_LABEL before closing) retains the dedup key so a still-detected subject does not immediately refile a duplicate; only a human/external close resets dedup state. - Unparseable escalation titles (operator-created issues carrying the stuck label) are left untouched by subject_from_title returning None.

EventBus

Kind: service · Context: shared-kernel · Anchor: src/events.py:EventBus · Confidence: accepted Aliases: pub/sub bus, hydraflow event bus

Async pub/sub bus that fans HydraFlowEvent objects out to subscriber asyncio.Queues, retains a bounded in-memory history for replay, and optionally persists every event through an EventLog. Auto-injects the active session_id and repo slug onto outbound events so downstream consumers always see a fully tagged event stream.

Invariants: - History length is capped at max_history (default 5000); oldest entries are evicted when full. - Slow subscribers do not block the publisher: a full subscriber queue drops its oldest entry before the new event is enqueued. - History mutation is serialized through an asyncio.Lock.

EventType

Kind: value_object · Context: shared-kernel · Anchor: src/events.py:EventType · Confidence: accepted Aliases: event category, event kind

Closed enumeration of the event categories the orchestrator publishes through the EventBus. Each value names a distinct kind of state change or observable occurrence — phase transitions, worker updates, PR lifecycle events, HITL escalations, CI checks, fitness updates, ADR conformance changes, and adversarial-pipeline stages — that subscribers (dashboard, loops, persistence) react to. Engineers add a new member whenever a new class of happening enters the system.

Invariants: - Members are append-only; existing string values are never renamed because persisted JSONL event logs depend on stable representations. - A designated subset (EPHEMERAL_EVENT_TYPES, e.g. PIPELINE_SNAPSHOT) is live-only — fanned out to connected subscribers but never retained in in-memory history nor persisted to the on-disk log.

FakeCoverageAuditorLoop

Kind: loop · Context: caretaker · Anchor: src/fake_coverage_auditor_loop.py:FakeCoverageAuditorLoop · Confidence: accepted Aliases: fake coverage auditor loop, fake coverage gap detector, uncassetted method detector

Trust-fleet loop that detects uncovered methods on fake adapters under src/mockworld/fakes/ via ast.parse (ADR-0045, ADR-0056, ADR-0057, spec §4.7). Compares two method sets: adapter-surface (public methods, covered by cassettes under tests/trust/contracts/cassettes/<adapter>/) and test-helper (scenario drivers like script_*, fail_service, heal_service, covered by scenario tests). Files one rollup issue per (fake_class, gap_kind) labeled hydraflow-find + fake_coverage_gap. Subsequent ticks update the body via PRPort.update_issue_body — appending newly-uncovered methods and striking through methods that gained coverage. Escalates after 3 attempts to hitl_escalation + fake_coverage_stuck.

Invariants: - One rollup issue per (fake_class, gap_kind) — never one issue per missing method. - Issue bodies are updated in-place on repeat ticks, not replaced. - Maximum 3 repair attempts before HITL escalation. - Kill-switch is via enabled_cb("fake_coverage_auditor") (ADR-0049).

FitnessContext

Kind: value_object · Context: caretaker · Anchor: src/loop_fitness.py:FitnessContext · Confidence: accepted Aliases: fitness context, loop fitness context

Frozen, data-only Pydantic model that is the sole input to any loop_fitness() call. Carries the evaluation window (window_start, window_end), this loop's BACKGROUND_WORKER_STATUS events pre-filtered to the window, a snapshot list of IssueRecords relevant to the loop, and optional per-loop cost. Contains no live GitHub client. The purity constraint (ADR-0093 §2) requires that loop_fitness() reads only from ctx — no network, no clock, no mutable self state — which lets the same function score live history now and replayed history in the future optimizer.

Invariants: - Carries no live client or callable; the model is frozen (model_config = {"frozen": True}). - issues is a snapshot list of IssueRecord rows; each loop attributes its own artifacts by querying this list for its label. - The same FitnessContext instance that powers the live scorecard can power an offline optimizer replay — that equivalence is the design invariant this type enforces.

FitnessScorecardLoop

Kind: loop · Context: caretaker · Anchor: src/fitness_scorecard_loop.py:FitnessScorecardLoop · Confidence: accepted Aliases: fitness scorecard, fitness scorecard loop, loop fitness scorecard

Read-only caretaker loop (ADR-0029) that produces the per-loop fitness scorecard on a configurable cadence (default 86400 s). Each tick it builds one FitnessContext per registered loop, calls every loop's loop_fitness(ctx), persists results to fitness.jsonl, regenerates docs/arch/generated/loop-fitness.md, and emits a LOOP_FITNESS_UPDATE event. Mutates no loop state, so it sits off the ADR-0046 recursion ladder. Kill-switch via enabled_cb("fitness_scorecard") per ADR-0049. (ADR-0093)

Invariants: - Kill-switch is via enabled_cb("fitness_scorecard") at the top of _do_work() (ADR-0049). - The loop is read-only: it calls loop_fitness() on peer loops but changes no loop config or state. - Declares its own fitness as HOUSEKEEPING — it produces no GitHub proposals or artifacts that have an acceptance lifecycle.

FlakeTrackerLoop

Kind: loop · Context: caretaker · Anchor: src/flake_tracker_loop.py:FlakeTrackerLoop · Confidence: accepted Aliases: flake tracker loop, flaky test detector, flake detector loop

Trust-fleet loop that detects persistently flaky tests by parsing JUnit XML from the last 20 RC runs and counting mixed pass/fail occurrences per test (spec §4.5, ADR-0065). When a test's flake count reaches flake_threshold (default 3, comparison >=), the loop files a hydraflow-find + flaky-test issue. After 3 repair attempts on the same test_name the loop escalates to a second issue labeled hitl-escalation + flaky-test-stuck. The dedup key for the escalation issue clears when the escalation issue is closed.

Invariants: - The rolling window is fixed at the last 20 RC runs; earlier history is not scanned. - Flake detection requires at least one pass AND one fail within the window — pure-fail tests are not flakes. - Maximum 3 repair attempts per test before HITL escalation; the dedup key for the hydraflow-find issue does not reset until the escalation is resolved. - Kill-switch is via enabled_cb("flake_tracker") (ADR-0049).

Gate

Kind: service · Context: shared-kernel · Anchor: src/convergence_gate.py:Gate · Confidence: accepted

Two things the word collapses (ADR-0122). A gate (mechanism) is the runtime component that evaluates a condition and returns a verdict on a transition (convergence_gate.py:Gate and its evaluate, the convergence gate, precondition gates) — the control/kernel register. A gate (entrenched rule) is the standing, hard-to-change rule the mechanism enforces — gate immutability in the legal/constitutional register (who may alter the gate, under what allowlist). Bare "gate" means the mechanism; use "gate policy" or "entrenched gate rule" for the rule it enforces. The split matters because changing a gate's code is a control act, while changing what a gate is allowed to permit is a constitutional one.

Invariants: - Bare 'gate' is the mechanism; the standing rule it enforces is a 'gate policy' (legal register). - Changing gate code is a control act; changing what a gate may permit is a constitutional act under an allowlist.

GitHubCacheLoop

Kind: loop · Context: caretaker · Anchor: src/github_cache_loop.py:GitHubCacheLoop · Confidence: accepted Aliases: github cache loop, github data cache loop, github poller

Centralized GitHub data poller that replaces the pattern where every dashboard endpoint and background worker makes its own gh api calls (ADR-0041). A single GitHubCacheLoop polls GitHub on a fixed interval and stores results in GitHubDataCache — in memory and on disk. Dashboard endpoints and background workers read from the cache instantly rather than hitting the API. Write operations (create PR, merge, comment, label swap) still call gh directly because they need immediate confirmation.

Invariants: - Only one instance per repo runtime; all read consumers share the same cache snapshot. - Write operations bypass the cache and call gh directly. - Cache staleness is observable: each CacheSnapshot carries a fetched_at timestamp; age_seconds is infinite until the first poll completes.

GitHubDataCache

Kind: service · Context: shared-kernel · Anchor: src/github_cache_loop.py:GitHubDataCache · Confidence: accepted Aliases: github data cache, shared github snapshot, gh api cache

GitHubDataCache is a repo-scoped, in-memory and disk-persisted cache for GitHub API read data. A single GitHubCacheLoop poller fetches data on a fixed interval and stores it here; dashboard endpoints and background workers such as DependabotMergeLoop, FlakeTrackerLoop, and RCBudgetLoop read from it via get_* methods instead of each issuing their own gh api calls. High-frequency datasets (open PRs, HITL items, label counts, collaborators) are refreshed by the poll cycle, while low-frequency datasets (RC-promotion workflow runs, xdist-audit runs, per-label issue lists) are demand-refreshed with an explicit staleness bound, single-flight locking to coalesce concurrent refreshes, and a stale-serve fallback before returning empty.

Invariants: - get_* read methods never hit the network — only poll() and the demand-refresh paths call the GitHub API - Demand-refreshed datasets serve a stale snapshot while younger than a multiple (default 3x) of the caller's staleness bound; beyond that, callers get an empty result rather than acting on ancient data - The cache is repo-scoped: each RepoRuntime gets its own instance with its own disk file

GoalSupervisorLoop

Kind: loop · Context: caretaker · Anchor: src/goal_supervisor_loop.py:GoalSupervisorLoop · Confidence: accepted Aliases: goal supervisor loop, tier-2 goal supervisor, goal supervisor, mini-me supervisor

Tier-2 (meta-observability) caretaker loop that formalizes the by-hand "keep the factory alive & healthy" monitor (ADR-0124, #10733). Ticks on a cadence, assembles a read-only HealthSnapshot from the existing Tier-1 signals (per-loop heartbeats, credit-failover state, boot-SHA staleness, the event-loop watchdog marker, the second-order vitals verdict), hands it to a Fable agent (claude-fable-5) under the standing goal, and records a SupervisorObservation (assessment · insights · nudges-taken · escalations · deferred) to the append-only supervisor_thread.jsonl + the event bus. Authority is watch + surface + NUDGE only — a small reversible allowlist; everything with blast radius is surfaced, never self-done. The load-bearing classify / known-incident / nudge-vs-escalate / give-up-window logic is pure and unit-tested in supervisor_observation. Ships default OFF.

Invariants: - Reuses Tier-1 signals; the snapshot never re-detects and never mutates. - Nudge allowlist is small and explicit (NUDGE_ALLOWLIST); everything else escalates (surface, never self-do) — mirrors docs/standards/factory_autonomy. - A nudge carries a one-line root-cause diagnosis; a cause-less action is dropped as noise. - Bounded retries then escalate: an incident is nudged ≤ GIVEUP_CAP times (give-up window, attempt ledger persisted across ticks), then escalates — never infinite-retry. - Verify + re-arm: a nudge is pending until a later tick confirms its condition cleared (reconcile_ledger); a still-present incident counts toward the give-up window. - Healthy snapshot → no-op without consulting the Fable agent (cost control). - Kill-switch is via enabled_cb("goal_supervisor") and the goal_supervisor_loop_enabled deploy-time gate (ADR-0049). - Tier separation: registered standalone (not folded into HealthMonitorLoop) so no LLM sits in the deterministic Tier-1 kernel (ADR-0124).

Governor

Kind: control_role · Context: shared-kernel · Anchor: src/base_background_loop.py:LoopDeps · Confidence: accepted

The saturation limiter and safety interlock that bounds every Actuator regardless of Controller intent. LoopDeps carries a loop's per-cycle safety controls — the kill switch (enabled_cb) and the watchdog timeout bound (timeout_cb); the wider Governor role (concurrency caps, credit holds) is realized elsewhere, by the max_workers/max_planners semaphores and the credit-exhaustion signal. The v2 Governor generalizes these into an explicit capacity-and-safety authority.

Invariants: - The Governor can veto or throttle any actuation; a Controller cannot override it.

HITLItem

Kind: entity · Context: caretaker · Anchor: src/models.py:HITLItem · Confidence: accepted Aliases: hitl issue, hitl queue item, escalation item

HITLItem is the entity representing a single Human-In-The-Loop escalation: an issue (and, if one exists, its associated PR) that has stalled and requires human review or intervention. It carries identity (issue number), the escalation cause, a lifecycle status (HITLItemStatus: pending, processing, resolved), and pointers to the underlying issue/PR/branch. PRManager assembles HITLItems from raw GitHub issues, GitHubDataCache serves them via GitHubCacheLoop.get_hitl_items(), PRPort exposes list_hitl_items() as the formal port method for fetching them, and PRUnstickerLoop (ADR-0077) consumes them to drive autonomous resolution of stuck HITL-labeled PRs before falling back to a human.

Invariants: - status defaults to HITLItemStatus.PENDING and transitions through PROCESSING to RESOLVED - issue is the required identity field; pr/pr_url/branch are populated only when a PR is associated - cause records the escalation reason that routed the issue into the HITL queue

HumanSteeringLoop

Kind: control_role · Context: shared-kernel · Anchor: src/human_steering_loop.py:HumanSteeringLoop · Confidence: accepted

The Sensor half of the SteeringChannel (ADR-0099 §6 surface #4, closed by ADR-0103): a BaseBackgroundLoop that, each tick, fetches GitHub comments for every active issue and calls the pure parser human_steering.parse_directives to derive the latest SteeringState, then persists it via state.set_human_steering. Purely a sensor: it never mutates issue phase, labels, or the pipeline directly — the orchestrator's actuator half reads the persisted state and enacts it at the next phase boundary. Gated by human_steering_enabled (default True since 2026-07-05, env-controllable via HYDRAFLOW_HUMAN_STEERING_ENABLED) and a kill-switch (enabled_cb), per ADR-0049. Default-on is safe because parse_directives honors nobody when human_steering_authorized_users is empty.

Invariants: - _do_work never applies a decision to the plant; it only fetches comments, parses, and writes SteeringState — enactment is the orchestrator's job, keeping the sensor/actuator split total. - On a comment-fetch failure for one issue, the loop logs and continues to the next issue rather than aborting the whole tick, so one flaky issue cannot starve steering for the rest of the fleet.

HydraFlowConfig

Kind: aggregate · Context: shared-kernel · Anchor: src/config.py:HydraFlowConfig · Confidence: accepted Aliases: hydraflow config, config aggregate, orchestrator config

Pydantic-validated runtime configuration aggregate for the HydraFlow orchestrator. Bundles issue selection (ready labels, batch size, repo), per-phase concurrency caps (max_workers, max_planners, max_reviewers, max_triagers, max_hitl_workers), required-plugin manifests, language plugins, and per-phase skill whitelists into a single object passed to every loop and runner. Edited via the dashboard or config JSON file, not environment variables.

Invariants: - Worker concurrency fields default to 1 and are bounded by ge=1, le=10 (max_hitl_workers le=5). - batch_size is bounded ge=1, le=50. - repo is auto-detected from the git remote when left empty.

HydraFlowEvent

Kind: domain_event · Context: shared-kernel · Anchor: src/events.py:HydraFlowEvent · Confidence: accepted Aliases: bus event, published event

A single event published on the in-process EventBus. Carries a monotonic id (for frontend dedup), an EventType discriminator, an ISO timestamp, a typed data payload, and optional session/repo context. HydraFlowEvents are fanned out live to subscribers, retained in in-memory history, and persisted to an append-only JSONL log for replay; persisted IDs are advanced past historical maxima so live events never collide with replayed ones.

Invariants: - Event IDs are monotonic and advanced past the maximum persisted ID after history load, so live events are never silently dropped by frontend dedup. - Every HydraFlowEvent carries an EventType discriminator and an ISO-8601 timestamp; data is a plain mapping (TypedDict or model_dump). - PIPELINE_SNAPSHOT and other EPHEMERAL_EVENT_TYPES are fanned out live-only — never retained in history nor persisted to disk.

Independence

Kind: policy · Context: shared-kernel · Anchor: src/judge_independence.py:IndependenceDisposition · Confidence: accepted

Non-correlation of judgment, in two registers that must not be conflated (ADR-0122). In the evidence/formal register, independence is model-family diversity — a verdict from a model family outside the implementing agent's roster, so author and reviewer are not "siblings" (#10371/#10832, judge_independence.py:IndependenceDisposition); it buys decorrelated error, not org-chart separation. In the legal register, independence is institutional — a reviewer structurally separate from the authoring authority (separation of powers). Qualify as "model-family independence" or "institutional independence"; the bare word in HydraFlow code defaults to model-family independence.

Invariants: - In HydraFlow code, unqualified 'independence' means model-family independence (decorrelated error), not institutional independence.

Invariant

Kind: invariant · Context: shared-kernel · Anchor: src/arch/integrity.py:IntegrityInvariant · Confidence: accepted

A property asserted to hold — but the three assurance disciplines mean three different strengths of claim, so the bare word must be qualified (ADR-0122). A formal invariant is a proven property, established for every interleaving by the kernel proof (#10833). A control invariant is a monitored series — a signal a regulator holds within bounds and is observed (never proven) to stay there. A legal invariant is a rule asserted in prose — a constraint declared in an ADR or an architecture check (arch/integrity.py:IntegrityInvariant) and enforced by convention or CI, not by proof. Because unqualified "invariant" reads as proven and thereby overclaims, always name the register: "proven invariant", "monitored invariant", or "asserted invariant".

Invariants: - 'Invariant' unqualified overclaims (it reads as proven); name the register — proven (formal), monitored (control), or asserted (legal).

IssueFetcherPort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:IssueFetcherPort · Confidence: accepted Aliases: issue fetcher port, github issue fetching port

Hexagonal port for fetching GitHub issues from the upstream source of truth. Exposes two methods consumed by domain code (phases and background loops): fetch_issue_by_number for single-issue lookups and fetch_issues_by_labels for label-scoped batch fetches. Implemented by issue_fetcher.IssueFetcher, which shells out to the gh CLI and applies internal caching and rate-limit back-off.

Invariants: - Pure Protocol — no implementation, no state. - Only the two methods domain code actually calls are declared here; the concrete IssueFetcher carries additional infrastructure methods (PR cache, collaborator cache) that stay off the port. - fetch_issues_by_labels accepts an optional exclude_labels list and a require_complete flag so callers can narrow results without extra filtering passes.

IssueStorePort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:IssueStorePort · Confidence: accepted Aliases: issue store port, issue queue port, work queue port

Hexagonal port for the in-memory issue work-queue — exposes only the queue accessors that domain code (phases, background loops, phase utilities) actually uses (get_triageable, get_plannable, get_implementable, get_reviewable, ...). Implemented by issue_store.IssueStore; orchestrator-only and dashboard-only methods stay on the concrete class to keep the domain surface narrow.

Invariants: - Pure Protocol — no implementation, no state. - Only domain-consumed methods are declared; orchestrator and dashboard methods deliberately stay off the port.

Lineage

Kind: value_object · Context: shared-kernel · Anchor: src/adr_index.py:ADR · Confidence: accepted

The named engineering tradition a control-plane ADR inherits (its Precedent) together with the forced break it takes from that tradition (its Divergence, which must cite a receipt). Lineage makes the ADR corpus separate inherited engineering from genuine novelty: unforced invention is a defect, forced invention has a named forcing condition and a receipt. The two optional single-line fields are defined by ADR-0113, parsed by the pure functions in scripts/hydraflow_audit/lineage.py, and enforced on the control-plane ADR set by the P1.17 audit check. Anchored on the parsed ADR record the fields annotate.

Invariants: - A control-plane ADR carries at least one lineage line — a Precedent or a Divergence (P1.17, ADR-0113). - Every Divergence cites a receipt (an ADR, incident, #issue, or docs path); a receipt-less Divergence is a defect — unforced invention. - A Precedent names a real, citable tradition; retrofitted branding fails review.

LiveCorpusReplayLoop

Kind: loop · Context: caretaker · Anchor: src/live_corpus_replay_loop.py:LiveCorpusReplayLoop · Confidence: accepted Aliases: live corpus replay loop, shadow corpus replay, shadow drift replay

Trust-fleet loop that replays live shadow-corpus samples against registered fake-adapter or shape validators. LiveCorpusReplayLoop files hydraflow-find / shadow-drift issues when live adapter output diverges from the current fake or schema contract, and escalates to HITL only after the configured drift retry budget is exhausted.

Invariants: - Empty shadow corpus is an idle tick, not an error. - Drift issues are auto-agent routed before any human escalation. - Dispatcher registration is keyed by (adapter, command) so cassette

LoopFitness

Kind: value_object · Context: caretaker · Anchor: src/loop_fitness.py:LoopFitness · Confidence: accepted Aliases: loop fitness, loop fitness score, fitness result

The result of calling a background loop's loop_fitness(ctx) method for one evaluation window. Carries kind (SCORED or HOUSEKEEPING), an optional normalized score in [0, 1] valid only for intra-loop trend comparison, raw components for diagnosis, sample_count, and a Confidence signal (OK or INSUFFICIENT_DATA) keyed off sample_count vs a per-loop threshold. Produced by every BaseBackgroundLoop subclass; consumed by FitnessScorecardLoop and persisted to fitness.jsonl. (ADR-0093)

Invariants: - score is normalized 0–1 and valid only for intra-loop use (trend over time, or intra-loop config ranking). Cross-loop comparison of score is architecturally invalid. - When kind is HOUSEKEEPING, score is always None; components carries raw counters. - confidence is INSUFFICIENT_DATA when sample_count is below the loop's min_samples threshold; score is None in that case regardless of kind.

MergeStateWatcherLoop

Kind: loop · Context: caretaker · Anchor: src/merge_state_watcher_loop.py:MergeStateWatcherLoop · Confidence: accepted Aliases: merge state watcher loop, merge state watcher, conflict rebase loop

Caretaker loop that periodically scans all open PRs for merge conflicts and auto-rebases or escalates them (ADR-0029). The filter is intentionally broad: RC promotion PRs, Dependabot bumps, agent PRs, and manual PRs all benefit from auto-rebase when they go DIRTY against main. PRs already labeled hydraflow-hitl (being handled by PRUnsticker) or hydraflow-review (active reviewer worktree) are skipped to avoid stepping on in-progress work. Delegates the actual conflict-detection and rebase logic to MergeStateWatcher.

Invariants: - Default tick interval is 600 seconds (10 minutes). - PRs labeled hydraflow-hitl or hydraflow-review are skipped. - Kill-switch is via enabled_cb("merge_state_watcher") and config.merge_state_watcher_loop_enabled.

ObservabilityPort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:ObservabilityPort · Confidence: accepted Aliases: observability port, sentry port, error capture port

Hexagonal port for the observability boundary (ADR-0044 P7.7). Exposes five methods: capture_exception, capture_message, breadcrumb, set_measurement, and flush. Sentry was removed by ADR-0118 (observability moves to a dedicated SRE agent targeting New Relic), so the production adapter is now NoOpObservabilityAdapter in src/observability/noop_adapter.py — a null object whose methods return silently — and the SRE agent will supply the real adapter for this seam. The port is intentionally minimal — rich APIs drag every backend into the union.

Invariants: - Pure Protocol — no implementation, no state. - The production adapter (NoOpObservabilityAdapter) is a null object; every method returns silently so callers never need a try/except around port calls. - Domain code never imports an observability SDK directly; all observability routes through the injected ObservabilityPort so the SRE agent's future adapter (New Relic, OTLP, structured-log, or sidecar) can replace the no-op without touching call sites.

Plant

Kind: control_role · Context: shared-kernel · Anchor: src/models.py:StateData · Confidence: accepted

The process an orchestration loop drives and observes: the repository plus an issue's lifecycle. Its durable, observable state is captured in StateData (and, in v2, the ConvergenceLedger). Controllers act on the Plant through Actuators; Sensors read it.

Invariants: - The Plant is only mutated through an Actuator, never by a Controller directly.

PricingRefreshLoop

Kind: loop · Context: caretaker · Anchor: src/pricing_refresh_loop.py:PricingRefreshLoop · Confidence: accepted Aliases: pricing refresh loop, litellm pricing poller, model pricing caretaker

Daily caretaker loop that fetches LiteLLM's model_prices_and_context_window.json via stdlib urllib, filters to Anthropic-provider entries (normalizing Bedrock keys), diffs against src/assets/model_pricing.json, and opens or updates a pricing-refresh-auto PR when drift is detected (ADR-0029, ADR-0049). A bounds guard rejects suspicious price moves. Bounds violations, parse errors, and schema errors open a single deduplicated [pricing-refresh] hydraflow-find issue. Network errors log-and-retry on the next tick without filing issues.

Invariants: - Kill-switch is the HYDRAFLOW_DISABLE_PRICING_REFRESH=1 env var. - PR is always on the fixed branch pricing-refresh-auto; no-op ticks do not open a PR. - Bounds violations are separate from network errors; each has a distinct response path.

PRManager

Kind: adapter · Context: shared-kernel · Anchor: src/pr_manager.py:PRManager · Confidence: accepted Aliases: pr manager, github adapter, pull request manager

PRManager is the gh-CLI-backed adapter that manages the full pull-request and issue lifecycle for HydraFlow: pushing branches, creating and merging PRs, creating/listing/closing GitHub issues, swapping pipeline labels, and posting size-bounded PR/issue comments. It is the single concrete surface that caretaker loops, reviewers, and builders across the system call to talk to GitHub — the label-swap operations it exposes are what drive the label-state-machine transitions (ADR-0002) that move issues through the pipeline, and its cost-alert hooks and pipeline-label listener wire it into cross-cutting dashboard and budgeting concerns.

Invariants: - Label-count queries are served from an in-memory cache with a 30s TTL (_LABEL_CACHE_TTL) to bound gh API pressure. - Successful pipeline-label swaps notify an optional registered listener (_pipeline_label_listener) so dashboard state updates within seconds instead of waiting for the periodic label poll. - Comment bodies are chunked to GitHub's comment size limit with truncation markers via CommentFormatter before posting.

PRPort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:PRPort · Confidence: accepted Aliases: pr port, pull request port, github pr port

Hexagonal port for GitHub PR, label, and CI operations — branch push, PR creation/merge, RC-branch creation, and the related label manipulations consumed by domain phases and background loops. Implemented by pr_manager.PRManager; signatures are kept identical to the concrete class to enable structural subtype checks.

Invariants: - Pure Protocol — no implementation, no state. - Method signatures must match pr_manager.PRManager exactly so structural subtype checks in tests/test_ports.py pass.

PRUnstickerLoop

Kind: loop · Context: caretaker · Anchor: src/pr_unsticker_loop.py:PRUnstickerLoop · Confidence: accepted Aliases: pr unsticker loop, pr unsticker, hitl unsticker

Caretaker loop that polls HITL items and delegates to PRUnsticker to resolve all HITL causes — merge conflicts, CI failures, and generic stuck states. Operates only on HITL issues that currently have an open PR. The loop wraps the unsticker worker in the standard BaseBackgroundLoop tick-and-interval skeleton, keeping the HITL resolution logic in PRUnsticker separate from the polling infrastructure.

Invariants: - Only processes HITL issues with an associated open PR (item.pr > 0); issues without PRs are skipped. - Kill-switch is via enabled_cb("pr_unsticker") and config.pr_unsticker_loop_enabled (ADR-0049). - Interval is driven by config.pr_unstick_interval.

Purpose

Kind: policy · Context: shared-kernel · Anchor: src/onboarding/kernel_writer.py:KernelSpec · Confidence: accepted

The first layer of the PAAA governance model (ADR-0143): what a repository is for — its direction, goals, set-points, and the intent the work serves. It answers "what is this thing trying to do?" for a system arriving at the repository cold, with no institutional memory. Purpose is the one PAAA layer nothing checks. It now has a declaration surface — #11748 landed the purpose: block in charter.yaml, parsed into Purpose (src/charter_model.py) — but no drift check reads it, and none should be added without a ruling saying what checking intent would even mean. Outside the charter it still lives implicitly in README.md prose, in the one-line description the onboarding kernel stamps into a new repository (KernelSpec.description), and in milestone and epic text; nothing reads any of those as a statement of intent either.

Invariants: - Purpose is declarative intent, never an executable check — nothing today decides anything against it. - Changing Purpose is an enactment reserved to the operator (ENACT, not RATIFY); the system cannot enlarge its own mandate. - Purpose is not Articles: a goal the repository is aiming at is not a rule that must remain true.

RCBudgetLoop

Kind: loop · Context: caretaker · Anchor: src/rc_budget_loop.py:RCBudgetLoop · Confidence: accepted Aliases: rc budget loop, rc ci wall-clock detector, rc duration regression detector

4-hour RC CI wall-clock regression detector (ADR-0045 §4.8). Reads the last 30 days of rc-promotion-scenario.yml runs via gh run list, extracts per-run wall-clock duration, and emits a hydraflow-find + rc-duration-regression issue when the newest run trips either a gradual-bloat signal (current run ≥ 1.5× rolling median) or a sudden-spike signal (current run ≥ 2.0× recent-five maximum). Both signals are independent; both may fire on the same tick with distinct dedup keys. After 3 unresolved attempts per signal the loop escalates to hitl-escalation + rc-duration-stuck.

Invariants: - Kill-switch is via enabled_cb("rc_budget") only — no rc_budget_enabled config field (ADR-0049 §12.2). - Requires at least 5 historical data points before emitting any signal. - Dedup keys clear on escalation-close.

ReportIssueLoop

Kind: loop · Context: builder · Anchor: src/report_issue_loop.py:ReportIssueLoop · Confidence: accepted Aliases: report issue loop, bug report loop, dashboard report processor

Background loop that dequeues pending bug reports from state, saves any attached screenshots to temp files, and invokes the Claude CLI with /hf.issue so the agent can see the image, research the codebase, and file a well-structured GitHub issue (ADR-0028). This is the same issue-filing flow triggered by dashboard bug reports. Supports base64-encoded screenshot payloads and scans them for secrets before saving. Caps retries at 5 attempts per report.

Invariants: - Screenshot payloads are scanned for secrets before being written to disk. - Reports cap at _MAX_REPORT_ATTEMPTS (5) before being abandoned. - Kill-switch is via enabled_cb("report_issue") (ADR-0049).

RepoWikiStore

Kind: service · Context: shared-kernel · Anchor: src/repo_wiki.py:RepoWikiStore · Confidence: accepted Aliases: repo wiki store, wiki store, per-repo wiki

File-based per-repo wiki manager (ADR-0032). Owns the on-disk layout for both the self-repo wiki (flattened directly under wiki_root so it can live at docs/wiki/ alongside code) and managed-repo wikis (nested under wiki_root/owner/repo). Provides ingest, lookup, indexing, lint, and append-only operation logging across the topic pages and structured WikiIndex.

Invariants: - Self-repo pages live directly under wiki_root; every other slug is nested under wiki_root/owner/repo. - ingest() updates topic pages, refreshes index.json/index.md, and appends to log.jsonl in a single operation. - When a tracked_root with per-entry layout is configured, reads prefer it and fall back to the legacy topic-page layout.

ReviewInsightStorePort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:ReviewInsightStorePort · Confidence: accepted Aliases: review insight store port, review insight port

Hexagonal port for persisting and querying recurring reviewer-feedback patterns. Implemented by review_insights.ReviewInsightStore. ReviewPhase injects this port to record each review outcome and to query which feedback categories have been seen often enough to inject mandatory guidance blocks into the next agent prompt. The port decouples ReviewPhase from the JSONL file-storage backend.

Invariants: - Pure Protocol — no implementation, no state. - Methods cover the full lifecycle: append_review writes a new record, load_recent reads recent history, get_proposed_categories and mark_category_proposed gate category escalation, and record_proposal, load_proposal_metadata, and update_proposal_verified track whether a proposed mandatory block reduced the pattern.

ReviewVerdict

Kind: value_object · Context: builder · Anchor: src/models.py:ReviewVerdict · Confidence: accepted Aliases: review verdict, review decision, review outcome

The closed-set outcome of a reviewer agent's evaluation of a pull request — APPROVE, REQUEST_CHANGES, or COMMENT. It is the terminal decision produced by the review phase (src/reviewer/), submitted as a formal GitHub PR review via PRManager.submit_review, and consumed downstream to gate auto-merge eligibility (DependabotMergeLoop's human/bot shepherd path only merges on ReviewVerdict.APPROVE) and to drive review-insight analytics (review_insights.py filters records by verdict != APPROVE).

Invariants: - Exactly one of APPROVE, REQUEST_CHANGES, or COMMENT — no other values are valid - PRManager.submit_review maps each verdict to its corresponding gh CLI review flag (--approve / --request-changes / --comment) - Only ReviewVerdict.APPROVE authorizes DependabotMergeLoop's shepherd path to proceed to merge

RouteBackCounterPort

Kind: port · Context: shared-kernel · Anchor: src/route_back.py:RouteBackCounterPort · Confidence: accepted Aliases: route-back counter port, route back counter, precondition retry counter port

Hexagonal port for the per-issue route-back counter. Lives in src/route_back.py alongside RouteBackCoordinator. The coordinator depends on this port rather than StateTracker directly, so unit tests can wire a tiny in-memory dict implementation without pulling in the full state layer. Production wiring connects StateTracker as the concrete adapter.

Invariants: - Pure Protocol — no implementation, no state. - Three methods: get_route_back_count reads the current count; increment_route_back_count returns the new count after incrementing; decrement_route_back_count rolls back an increment when a subsequent label swap fails, preventing transient network blips from burning route-back budget without any actual route-back occurring. - decrement_route_back_count must be a no-op (returning 0) when the counter is already at zero.

Sensor

Kind: control_role · Context: shared-kernel · Anchor: src/models.py:MetricsSnapshot · Confidence: accepted

Any component that measures the current state of the Plant and emits a signal a Controller can read — deterministic (drift detectors, lint) or LLM-based (spec/review judges). MetricsSnapshot is the canonical aggregate reading.

Invariants: - A Sensor observes; it does not mutate the Plant.

Set-point

Kind: control_role · Context: shared-kernel · Anchor: src/issue_store.py:IssueStoreStage · Confidence: accepted

The desired state an orchestration loop drives toward — an issue reaching its terminal pipeline stage (the MERGED value of the IssueStoreStage state space), or a regulator holding a quantity at zero. A first-class converged flag arrives with the v2 ConvergenceLedger.

Invariants: - The Set-point is the loop's target, not its current state (that is the Sensor reading).

Setpoint

Kind: value_object · Context: shared-kernel · Anchor: src/signal_control/controllers.py:PidController · Confidence: accepted

The reference value a regulator drives its process variable toward — the target term in error = PV - setpoint that a control loop holds (signal_control/controllers.py:PidController, the setpoint regulators of ADR-0120). The bare word is scoped to the control register (ADR-0122). Adjacent registers use different words and must not borrow "setpoint": a written requirement (an acceptance criterion) is a specification, and an ADR decision is a ruling — neither is a setpoint, because a setpoint is a live, human-signed target a regulator reads each cycle whereas a requirement or ruling is prose. Where a requirement is operationalized into a regulator's target (e.g. the 70% coverage floor), that number is the setpoint and the ADR is its authority — name the two separately.

Invariants: - 'Setpoint' is control-register only: a live, human-signed target a regulator reads each cycle; a requirement or ADR ruling is not a setpoint. - When a requirement is operationalized into a target, the number is the setpoint and the ADR is its authority — name them separately.

SkillPromptEvalLoop

Kind: loop · Context: caretaker · Anchor: src/skill_prompt_eval_loop.py:SkillPromptEvalLoop · Confidence: accepted Aliases: skill prompt eval loop, skill prompt drift detector, corpus health auditor

Weekly background loop with two responsibilities. First, it runs the full adversarial skill-prompt corpus on a fixed schedule, catching regressions the RC-gate subset sampling misses (ADR-0045 §4.6). Second, it samples 10% of provenance: learning-loop corpus cases, flagging any that the expected_catcher skill passes — a weak-case signal the §4.1 learner uses for corpus quality improvement. Files skill-prompt-drift issues on PASS→FAIL transitions and corpus-case-weak issues for human triage.

Invariants: - Both subsystems share a single loop tick; neither runs independently. - Issues are deduplicated before filing; the same regression does not produce duplicate bead spam. - Subclasses BaseBackgroundLoop; kill-switch is via enabled_cb("skill_prompt_eval") (ADR-0049).

StaleIssueGCLoop

Kind: loop · Context: caretaker · Anchor: src/stale_issue_gc_loop.py:StaleIssueGCLoop · Confidence: accepted Aliases: stale issue gc loop, stale hitl gc loop, hitl stale closer

Caretaker loop that auto-closes stale HITL escalation issues (ADR-0029). Scope is specifically open issues carrying the configured HITL label that have been inactive beyond stale_issue_threshold_days. Posts a farewell comment, then closes. Caps at 10 closures per cycle to avoid GitHub rate-limiting. Distinct from StaleIssueLoop, which handles stale general issues with no HydraFlow lifecycle label — the two loops share only the BaseBackgroundLoop framework and have zero business-logic overlap.

Invariants: - Maximum 10 issues closed per tick to respect GitHub rate limits. - Only closes issues carrying the HITL label (config.hitl_label), not general issues. - StaleIssueGCLoop and StaleIssueLoop are fully separate; do not conflate them.

StateTracker

Kind: service · Context: shared-kernel · Anchor: src/state/__init__.py:StateTracker · Confidence: accepted Aliases: state tracker, state facade, state mixin facade

JSON-file backed state service for crash recovery. Composes ~30 domain mixins (issue, workspace, HITL, review, route-back, epic, session, worker, principles audit, sentry, trust fleet, ...) into a single facade that writes /.hydraflow/state.json after every mutation and rotates timestamped backups so a corrupt primary file can be restored from .bak.

Invariants: - Every mutating method persists state to disk before returning. - Issue/PR/epic numbers are stored as string keys; helpers convert to int on read. - On corrupt primary file, load() falls back to the most recent .bak before defaulting to an empty StateData.

SteeringChannel

Kind: control_role · Context: shared-kernel · Anchor: src/human_steering_loop.py:HumanSteeringLoop · Confidence: accepted

The continuous Human reference-input path (ADR-0099 §6 surface #4, closed by ADR-0103): a live, per-issue channel from an operator's GitHub comments into the running pipeline, replacing the discrete single-shot pending_correction + suspend/wake mechanism. The channel has a sensor half (HumanSteeringLoop parses /steer, /pause, /resume, /redo, /abort comment directives into a persisted SteeringState each tick) and an actuator half (the orchestrator's _apply_human_steering enacts the latest state at the next phase boundary — skip, park, redo, or fold guidance into the next prompt). The two halves never share a process step: the sensor only senses, the actuator only enacts, so the orchestrator stays thin.

Invariants: - The channel applies at phase boundaries only; it never interrupts a running phase mid-flight (the only mid-phase stop is the fleet-wide SIGKILL). - Declarative directives (/steer, /pause, /resume) are recomputed latest-wins from the full comment history every tick; imperative directives (/redo, /abort) fire at most once, gated by a per-issue created_at high-water-mark so a re-tick cannot replay them.

SteeringState

Kind: control_role · Context: shared-kernel · Anchor: src/models.py:SteeringState · Confidence: accepted

The persisted Error/reference-state register for one issue's SteeringChannel (ADR-0099 §6 surface #4, closed by ADR-0103): a guidance string, a flow (running | paused | abort), a pending redo_phase, a redo_count, and a last_applied_ts high-water-mark gating imperative directives. HumanSteeringLoop (Sensor) writes it each tick from parsed comments; the orchestrator's apply_steering (Controller) reads it to compute a SteeringDecision that the orchestrator (Actuator) enacts at the next phase boundary. Keyed str(issue_number) in StateData.human_steering, matching the per-issue-map convention.

Invariants: - Precedence within one poll is fixed: abort beats pause beats redo beats steer — apply_steering checks flow == abort first, then paused, before considering redo_phase. - redo_phase is only honored while redo_count < human_steering_max_redos and the phase name is a known internal stage; otherwise it is silently dropped rather than retried, so a stale or bogus /redo cannot stall an issue forever.

SubprocessRunner

Kind: port · Context: shared-kernel · Anchor: src/execution.py:SubprocessRunner · Confidence: accepted Aliases: subprocess execution port, host/docker execution abstraction

SubprocessRunner is the Protocol that abstracts how a command gets executed — on the host via asyncio.create_subprocess_exec, or inside a Docker container — behind a single interface (create_streaming_process, run_simple, cleanup). Runners and loops that need to spawn a Claude Code process or shell out to git/gh depend on this seam rather than the concrete HostRunner/DockerRunner implementation, so the same call sites work unchanged whether HydraFlow is running bare-metal or containerized.

Invariants: - Two implementations select the execution environment: HostRunner (asyncio.create_subprocess_exec on the host) and DockerRunner (inside a container) - run_simple's cancel_check is polled every cancel_poll_interval seconds; a True verdict tears down the whole process group and raises SubprocessCancelledError rather than a plain timeout (#9577) - cleanup() must release any held resources (containers, connections), not just terminate processes

Task

Kind: entity · Context: builder · Anchor: src/models.py:Task · Confidence: accepted Aliases: work item, ticket

A source-agnostic work item abstraction representing tasks from any source (GitHub issues, Linear tickets, etc.) that flow through HydraFlow's pipeline. Tasks carry metadata, support typed relationships via TaskLink, and serve as the unified representation for all work regardless of origin. Relationship extraction follows first-match precedence across compiled regex patterns.

Invariants: - TaskLink relationships extracted via regex patterns with first-match precedence per target_id - URLs validated as empty or http(s):// via AfterValidator - Timestamps validated as empty or ISO 8601 format

Term

Kind: entity · Context: shared-kernel · Anchor: src/ubiquitous_language.py:Term · Confidence: accepted Aliases: ul term, glossary term

Term is the first-class domain entity representing a single ubiquitous-language concept in HydraFlow: a canonical name, kind (aggregate/entity/service/loop/etc.), bounded context, one-paragraph definition, code anchor, and typed relations to other terms. Each Term is persisted as one markdown file under docs/wiki/terms/, carries a proposed → accepted → deprecated confidence lifecycle, and is grown and groomed by the UL caretaker loops (TermProposerLoop, EdgeProposerLoop, EntryEvidenceLoop, TermPrunerLoop) that keep the glossary a living artifact per ADR-0053.

Invariants: - id defaults to a freshly generated ULID, giving each Term a stable identity independent of its name - confidence moves through a closed lifecycle: proposed → accepted → deprecated - code_anchor must resolve to a module:symbol pair so the term stays traceable to real code

TermPrunerLoop

Kind: loop · Context: caretaker · Anchor: src/term_pruner_loop.py:TermPrunerLoop · Confidence: accepted Aliases: term pruner loop, UL pruner, glossary pruner

Caretaker background loop that autonomously prunes stale terms from the ubiquitous-language glossary (ADR-0057). On each tick it scans every confidence == "accepted" term in docs/wiki/terms/; for any term whose code_anchor no longer resolves in the live symbol index (built by ubiquitous_language.build_symbol_index), it opens an auto-merging bot PR that flips confidence to deprecated and records a superseded_reason with the broken anchor. The loop makes no LLM calls — detection is purely structural.

Invariants: - Kill-switch: enabled_cb("term_pruner") AND config.term_pruner_enabled — both must be true for work to proceed. - Opens at most one PR per tick, bundling all eligible terms into a single hydraflow-ul-deprecated-labelled PR. - ReviewPhase skips routing for PRs carrying TERM_PRUNER_PR_LABEL so the deprecation PR is not sent through the agent pipeline. - Companion to TermProposerLoop: together they implement the two-tick grow/prune cycle that keeps make lint-ul anchor-resolution green without human intervention.

TermStore

Kind: service · Context: shared-kernel · Anchor: src/ubiquitous_language.py:TermStore · Confidence: accepted Aliases: term repository

TermStore is the persistence service for the ubiquitous-language glossary — it reads and lists Term entities from their one-file-per-term markdown representation under docs/wiki/terms/, giving the UL caretaker loops (TermProposerLoop, EdgeProposerLoop, EntryEvidenceLoop, TermPrunerLoop) a single canonical way to load and enumerate the term corpus rather than each loop parsing term files itself.

Invariants: - Each Term corresponds to exactly one markdown file under docs/wiki/terms/, following the frontmatter + prose format written by dump_term_file

TribalWikiStore

Kind: service · Context: shared-kernel · Anchor: src/tribal_wiki.py:TribalWikiStore · Confidence: accepted Aliases: tribal wiki, global wiki, cross-repo wiki

Cross-repo knowledge store that mirrors the per-repo wiki layout (index.json + topic.md pages) but is not namespaced by repo. All entries carry source_repo='global' and are written only by the generalization pass (src/wiki_compiler/_judge.py) when the same principle is observed in two or more per-repo wikis. Loaded at every plan/implement/review phase alongside the target repo's wiki so tribal rules apply regardless of which repo is being worked on. Routes reads, writes, staleness filtering, and contradiction marking through the underlying RepoWikiStore to keep on-disk format consistent with per-repo wikis.

Invariants: - All entries carry source_repo='global'; the store is pinned to a single 'global' slug. - Entries are written only by the generalization pass when the same principle is observed in ≥2 per-repo wikis; direct modification from agent code is not a supported use case. - On-disk layout, staleness filtering, contradiction marking, and supersession are delegated to the underlying RepoWikiStore so per-repo and tribal formats stay consistent.

Verdict

Kind: value_object · Context: shared-kernel · Anchor: src/convergence_gate.py:JudgeVerdict · Confidence: accepted

A closed-set adjudication emitted by a decision mechanism. The bare word is scoped to the control/kernel register: a gate's pass/fail outcome (convergence_gate.py:JudgeVerdict, convergence_gate.py:GateDecision, models.py:ReviewVerdict) — the terminal decision the pipeline routes on. Two other registers qualify the word rather than own it: a formal-methods verdict (ADR-0122) is a model-checker's result and, on failure, a counterexample trace witnessing a violated property (#10833); a legal-sense verdict is an adjudication under the ADR corpus — a human-signed ruling on a proposal. Qualify with the register ("gate verdict", "model-checker verdict", "adjudication") whenever the three could be confused; unqualified "verdict" means the gate outcome.

Invariants: - Unqualified 'verdict' denotes a gate's pass/fail outcome; the formal (counterexample) and legal (adjudication) senses must be qualified.

ViolationDetector

Kind: control_role · Context: shared-kernel · Anchor: src/disturbance/detectors/base.py:ViolationDetector · Confidence: accepted

The Sensor role (ADR-0094) in the Disturbance Dampener (ADR-0095): a pluggable protocol with a single pure method, detect(repo_root) -> list[Finding], that reads files only and produces no side effects. Each dimension in the registry (mock_spec, suppressions) binds one concrete ViolationDetector implementation. Findings carry a stable per-occurrence signature so the ratchet gate and the burn-down loop can count and diff violations per signature rather than as an undifferentiated total.

Invariants: - detect() must be pure: it reads repository files and returns Findings, and must not mutate the repository or any baseline. - Every Finding's signature must be stable across repeated detect() calls against unchanged source, since the ratchet gate diffs signatures against a persisted baseline.

WikiRotDetectorLoop

Kind: loop · Context: caretaker · Anchor: src/wiki_rot_detector_loop.py:WikiRotDetectorLoop · Confidence: accepted Aliases: wiki rot detector loop, wiki rot detector, cite freshness loop

Trust-fleet caretaker loop (ADR-0045 §4.9) that detects broken code citations in per-repo wikis. On each tick it walks every RepoWikiStore-registered repo's wiki entries, extracts code references via three patterns (path.py:symbol, dotted src.module.Class, and bare identifiers inside fenced code blocks as hints only), then verifies each hard cite. HydraFlow-self cites are checked via AST introspection; managed-repo cites are verified via grep over wiki markdown mirrors. For each broken cite the loop files a hydraflow-find + wiki-rot issue via PRManager, with a fuzzy-match suggestion from difflib.get_close_matches when the containing module still exists. After three unresolved attempts for a given slug+cite pair the loop escalates to hitl-escalation + wiki-rot-stuck.

Invariants: - Kill-switch: enabled_cb("wiki_rot_detector") only — no config field (ADR-0049 trust-fleet convention). - Calls reraise_on_credit_or_bug(exc) in its broad except block to prevent CreditExhaustedError from being silently swallowed. - Does not run on startup (run_on_startup=False) — the first tick is deferred to the normal interval.

WorkspaceGCLoop

Kind: loop · Context: caretaker · Anchor: src/workspace_gc_loop.py:WorkspaceGCLoop · Confidence: accepted Aliases: workspace gc loop, workspace garbage collector, worktree gc loop

Background caretaker loop that periodically garbage-collects stale worktrees and orphaned branches. Handles three leak classes: worktrees tracked in StateTracker whose PR has been merged or closed, orphaned worktree directories on disk with no StateTracker entry, and orphaned remote branches with no open PR. Catches worktrees that leak when PRs are merged manually, via HITL resolution, or when implementations fail or crash mid-cleanup.

Invariants: - Kill-switch: enabled_cb("workspace_gc") AND config.workspace_gc_loop_enabled — both must be true to run. - Caps at _MAX_GC_PER_CYCLE = 20 collections per tick to avoid long-running passes. - State removal happens before WorkspacePort.destroy() so a crash between the two steps leaves the entry gone; destroy() is idempotent. - An optional is_in_pipeline_cb guard prevents GC of issues still being actively processed by a phase.

WorkspacePort

Kind: port · Context: shared-kernel · Anchor: src/ports.py:WorkspacePort · Confidence: accepted Aliases: workspace port, worktree port, git workspace port

Hexagonal port for git workspace lifecycle operations — create and destroy isolated worktrees per issue, merge main into a worktree, list conflicting files, hard-reset to origin/main, abort an in-progress merge, and run post-work cleanup. Implemented by workspace.WorkspaceManager; the abstraction is what lets phases stay agnostic to the concrete worktree machinery.

Invariants: - Pure Protocol — no implementation, no state. - Each managed worktree is keyed by issue_number; create() returns the worktree path used for subsequent calls.