feat(projection): add the fleet reset as a loop over the repository form
ADR-012 decision 7 requires the fleet form to share the per-repository implementation: the rarely-run wide operation must be the frequently-run narrow one, or the wide one is trusted on the strength of never having been exercised. Failure behaviour is the substance. A refusal does not stop the pass — aborting on the first refusal means one unresolved repository blocks reconstruction everywhere, which in practice means permanently. An error does not stop it either. Each repository gets its own session so one failure cannot roll back another's work, and only repositories that applied are committed. Refs STATE-WP-0083-T04 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
parent
76a7c7ed24
commit
532583ce17
3 changed files with 173 additions and 1 deletions
|
|
@ -618,3 +618,72 @@ async def reset_repository_projection(
|
|||
"canonical identifier (STATE-WP-0083-T06)."
|
||||
)
|
||||
return outcome
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fleet form (STATE-WP-0083-T04)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class FleetResetOutcome:
|
||||
results: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||
errors: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
by_status: dict[str, int] = {}
|
||||
for r in self.results.values():
|
||||
by_status[r["status"]] = by_status.get(r["status"], 0) + 1
|
||||
return {
|
||||
"schema": "state-hub.fleet-projection-reset.v1",
|
||||
"repositories": len(self.results) + len(self.errors),
|
||||
"by_status": by_status,
|
||||
"errored": len(self.errors),
|
||||
"totals": {
|
||||
k: sum(r["counts"][k] for r in self.results.values())
|
||||
for k in ("created", "updated", "retired", "refused")
|
||||
},
|
||||
"results": self.results,
|
||||
"errors": self.errors,
|
||||
}
|
||||
|
||||
|
||||
async def reset_fleet_projection(
|
||||
session_factory: Any,
|
||||
repo_slugs: list[str],
|
||||
*,
|
||||
acknowledge_retirements: bool = False,
|
||||
forge_base: str = DEFAULT_FORGE_BASE,
|
||||
) -> FleetResetOutcome:
|
||||
"""Reset every repository, one at a time, sharing the per-repository path.
|
||||
|
||||
The fleet form is a loop over the repository form and nothing else
|
||||
(`ADR-012` decision 7). The rarely-run wide operation must be the frequently
|
||||
run narrow one, or the wide one is trusted on the strength of never having
|
||||
been exercised.
|
||||
|
||||
A repository that refuses or errors is recorded and the pass continues.
|
||||
Aborting on the first refusal would mean one unresolved repository blocks
|
||||
reconstruction everywhere — which in practice means permanently.
|
||||
|
||||
Each repository gets its own session, so one failure cannot roll back
|
||||
another's work or leave a poisoned transaction behind.
|
||||
"""
|
||||
outcome = FleetResetOutcome()
|
||||
for slug in repo_slugs:
|
||||
try:
|
||||
async with session_factory() as session:
|
||||
result = await reset_repository_projection(
|
||||
session,
|
||||
slug,
|
||||
acknowledge_retirements=acknowledge_retirements,
|
||||
forge_base=forge_base,
|
||||
)
|
||||
if result.status == "applied":
|
||||
await session.commit()
|
||||
else:
|
||||
await session.rollback()
|
||||
outcome.results[slug] = result.to_dict()
|
||||
except Exception as exc: # noqa: BLE001 - one repo must not end the pass
|
||||
outcome.errors[slug] = f"{type(exc).__name__}: {exc}"[:300]
|
||||
return outcome
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue