-
Notifications
You must be signed in to change notification settings - Fork 0
feat(automation): bounded-automation candidate layer (Arc D phase 1) #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| """Bounded-automation eligibility + candidate selection (Arc D, phase 1). | ||
|
|
||
| This module is strictly advisory and read-only. It answers a single question | ||
| for each repo: *does it clear the automation trust bar?* A repo is eligible | ||
| only when ALL of the following hold: | ||
|
|
||
| * the portfolio's ``decision_quality_status`` is ``"trusted"`` (a portfolio-wide | ||
| gate — when calibration is noisy/mixed/insufficient, nothing is eligible); | ||
| * the repo's ``path_confidence`` is ``"high"`` (its operating path is settled); | ||
| * the repo's ``registry_status`` is active or candidate (not archived/parked); | ||
| * the repo's ``context_quality`` is non-trivial (not boilerplate/none/unknown). | ||
|
|
||
| Selecting candidates does NOT propose or apply any change — proposal creation | ||
| (phase 2) and gated execution (phase 3) build on top of this signal layer. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
| from typing import Any | ||
|
|
||
| CONTRACT_VERSION = "automation_candidates_v1" | ||
|
|
||
| # Trust-bar thresholds. Kept as module constants so later phases (proposal | ||
| # creation, execution re-checks) reuse the exact same gate. | ||
| TRUSTED_DECISION_QUALITY = "trusted" | ||
| ELIGIBLE_PATH_CONFIDENCE = frozenset({"high"}) | ||
| ELIGIBLE_REGISTRY_STATUS = frozenset({"active", "candidate"}) | ||
| ELIGIBLE_CONTEXT_QUALITY = frozenset({"minimum-viable", "standard", "full"}) | ||
|
|
||
| MAX_AUTOMATION_CANDIDATES = 25 | ||
|
|
||
|
|
||
| def _mapping(value: Any) -> dict[str, Any]: | ||
| return value if isinstance(value, dict) else {} | ||
|
|
||
|
|
||
| def _text(value: Any) -> str: | ||
| return value.strip() if isinstance(value, str) else "" | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class AutomationEligibility: | ||
| """Whether a repo clears the automation trust bar, with reasons if not.""" | ||
|
|
||
| eligible: bool | ||
| blockers: tuple[str, ...] | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class AutomationCandidate: | ||
| """An eligible repo surfaced as a bounded-automation candidate.""" | ||
|
|
||
| display_name: str | ||
| repo_full_name: str | ||
| registry_status: str | ||
| path_confidence: str | ||
| context_quality: str | ||
|
|
||
| def to_dict(self) -> dict[str, str]: | ||
| return { | ||
| "repo": self.display_name, | ||
| "repo_full_name": self.repo_full_name, | ||
| "registry_status": self.registry_status, | ||
| "path_confidence": self.path_confidence, | ||
| "context_quality": self.context_quality, | ||
| } | ||
|
|
||
|
|
||
| def evaluate_automation_eligibility( | ||
| project: dict[str, Any], *, decision_quality_status: str | ||
| ) -> AutomationEligibility: | ||
| """Evaluate a single truth project against the bounded-automation trust bar. | ||
|
|
||
| ``decision_quality_status`` is the portfolio-level value (the same for every | ||
| repo in a run); the remaining checks are per-repo. Blockers accumulate so | ||
| the operator sees every reason a repo is held back, not just the first. | ||
| """ | ||
|
Comment on lines
+74
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This marks a repo eligible using only derived path/context/status fields, but the existing automation trust bar in Useful? React with 👍 / 👎. |
||
| derived = _mapping(project.get("derived")) | ||
| blockers: list[str] = [] | ||
| if _text(decision_quality_status) != TRUSTED_DECISION_QUALITY: | ||
| blockers.append("decision-quality-not-trusted") | ||
| if _text(derived.get("registry_status")) not in ELIGIBLE_REGISTRY_STATUS: | ||
| blockers.append("registry-status-not-eligible") | ||
| if _text(derived.get("path_confidence")) not in ELIGIBLE_PATH_CONFIDENCE: | ||
| blockers.append("path-confidence-not-high") | ||
| if _text(derived.get("context_quality")) not in ELIGIBLE_CONTEXT_QUALITY: | ||
| blockers.append("context-quality-too-weak") | ||
| return AutomationEligibility(eligible=not blockers, blockers=tuple(blockers)) | ||
|
|
||
|
|
||
| def select_automation_candidates( | ||
| portfolio_truth: dict[str, Any], *, decision_quality_status: str | ||
| ) -> list[AutomationCandidate]: | ||
| """Return the eligible repos (sorted by display name, capped) as candidates.""" | ||
| projects = portfolio_truth.get("projects") or [] | ||
| candidates: list[AutomationCandidate] = [] | ||
| for project in projects: | ||
| if not isinstance(project, dict): | ||
| continue | ||
| eligibility = evaluate_automation_eligibility( | ||
| project, decision_quality_status=decision_quality_status | ||
| ) | ||
| if not eligibility.eligible: | ||
| continue | ||
| identity = _mapping(project.get("identity")) | ||
| derived = _mapping(project.get("derived")) | ||
| candidates.append( | ||
| AutomationCandidate( | ||
| display_name=_text(identity.get("display_name")) or "Repo", | ||
| repo_full_name=_text(identity.get("repo_full_name")), | ||
| registry_status=_text(derived.get("registry_status")), | ||
| path_confidence=_text(derived.get("path_confidence")), | ||
| context_quality=_text(derived.get("context_quality")), | ||
| ) | ||
| ) | ||
| candidates.sort(key=lambda candidate: candidate.display_name.lower()) | ||
| return candidates[:MAX_AUTOMATION_CANDIDATES] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For snapshots produced by this repo,
derived.registry_statusis validated against{"active", "recent", "parked", "archived"}insrc/portfolio_truth_types.pyand_registry_status_for()returns the activity status except mapping stale to parked, so no valid portfolio-truth project can have"candidate". As written, a recent repo that otherwise clears decision quality, high path confidence, and usable context is always blocked asregistry-status-not-eligible, so the digest silently omits eligible recent projects.Useful? React with 👍 / 👎.