Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
638 lines
24 KiB
Python
638 lines
24 KiB
Python
"""Scoped on-demand identifier registrar for the ADR-007 interim.
|
|
|
|
The production fleet sweep is intentionally disabled. This command provides
|
|
one explicit, repository-scoped way to run the existing State Hub registration
|
|
adapter without teaching ordinary agent sessions to set ``STATEHUB_REGISTRAR``
|
|
themselves.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import fcntl
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import uuid
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from repo_manager.gitops import GitError, commit_paths, push_ff
|
|
from repo_manager.parse.record import iter_record_files, parse_record_file
|
|
from repo_manager.parse.workplan import parse_workplan_file
|
|
from repo_manager.record_identity import scan_record_identities
|
|
|
|
LOCK_PATH = Path("/tmp/repo-manager-identifier-registrar.lock")
|
|
|
|
|
|
@dataclass
|
|
class RegistrarResult:
|
|
status: str
|
|
evidence: dict[str, Any]
|
|
error: dict[str, Any] | None = None
|
|
correlation_id: str = ""
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
result: dict[str, Any] = {
|
|
"command": "repo.identifiers.registrar_reconcile",
|
|
"status": self.status,
|
|
"correlation_id": self.correlation_id,
|
|
"evidence": self.evidence,
|
|
}
|
|
if self.error:
|
|
result["error"] = self.error
|
|
return result
|
|
|
|
|
|
def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["git", *args],
|
|
cwd=repo,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def _missing_identifiers(repo: Path) -> dict[str, list[str]]:
|
|
workplans: list[str] = []
|
|
tasks: list[str] = []
|
|
intakes: list[str] = []
|
|
decisions: list[str] = []
|
|
workplans_dir = repo / "workplans"
|
|
if workplans_dir.is_dir():
|
|
# Closed archives are frozen under ADR-007 and are not registration work.
|
|
for path in sorted(workplans_dir.glob("*.md")):
|
|
parsed = parse_workplan_file(path, repo_root=repo)
|
|
if parsed.frontmatter.get("type") != "workplan" or not parsed.id:
|
|
continue
|
|
if not parsed.state_hub_workstream_id:
|
|
workplans.append(parsed.id)
|
|
# C-06 creates a newly discovered workplan and its tasks as one
|
|
# registration unit, including closed file-backed provenance.
|
|
# Include those task gaps so post-verification cannot report a
|
|
# false success after creating only the parent.
|
|
for task in parsed.tasks:
|
|
if task.id and not task.state_hub_task_id:
|
|
tasks.append(task.id)
|
|
continue
|
|
# Once a closed workplan is linked, missing task identifiers are
|
|
# frozen historical artefacts. State Hub's C-11 fixer deliberately
|
|
# refuses to create them.
|
|
if (parsed.status or "").strip().lower() in {"finished", "archived"}:
|
|
continue
|
|
for task in parsed.tasks:
|
|
if task.id and not task.state_hub_task_id:
|
|
tasks.append(task.id)
|
|
|
|
for path in iter_record_files(repo):
|
|
for record in parse_record_file(path, repo_root=repo):
|
|
if record.uuid:
|
|
continue
|
|
target = intakes if record.kind == "intake" else decisions
|
|
target.append(record.id)
|
|
return {
|
|
"workplans": workplans,
|
|
"tasks": tasks,
|
|
"intakes": intakes,
|
|
"decisions": decisions,
|
|
}
|
|
|
|
|
|
def _check_git(repo: Path) -> tuple[dict[str, Any], str | None]:
|
|
status = _git(repo, "status", "--porcelain")
|
|
if status.returncode != 0:
|
|
return {}, status.stderr.strip() or "not a Git repository"
|
|
if status.stdout.strip():
|
|
return {}, "worktree must be clean before registrar reconciliation"
|
|
|
|
upstream = _git(repo, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
|
|
if upstream.returncode != 0:
|
|
return {}, "current branch must have an upstream"
|
|
counts = _git(repo, "rev-list", "--left-right", "--count", "@{u}...HEAD")
|
|
if counts.returncode != 0:
|
|
return {}, counts.stderr.strip() or "could not compare HEAD with upstream"
|
|
try:
|
|
behind, ahead = (int(value) for value in counts.stdout.split())
|
|
except (TypeError, ValueError):
|
|
return {}, "could not parse Git ahead/behind state"
|
|
if behind or ahead:
|
|
return (
|
|
{"upstream": upstream.stdout.strip(), "behind": behind, "ahead": ahead},
|
|
"branch must match its upstream; pull or push before registrar reconciliation",
|
|
)
|
|
|
|
remote = _git(repo, "remote", "get-url", "origin")
|
|
if remote.returncode != 0:
|
|
return {}, "origin remote is required"
|
|
remote_url = remote.stdout.strip()
|
|
stale_markers = ("gitea-remote", "gitea.coulomb.social", "92.205.130.254")
|
|
if any(marker in remote_url for marker in stale_markers):
|
|
return {"origin": remote_url}, "origin still targets the retired Gitea lineage"
|
|
return {
|
|
"upstream": upstream.stdout.strip(),
|
|
"behind": behind,
|
|
"ahead": ahead,
|
|
"origin": remote_url,
|
|
}, None
|
|
|
|
|
|
def _check_primary(api_base: str) -> tuple[dict[str, Any], str | None]:
|
|
try:
|
|
response = httpx.get(f"{api_base.rstrip('/')}/state/health", timeout=10.0)
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
except (httpx.HTTPError, ValueError) as exc:
|
|
return {}, f"primary State Hub health check failed: {exc}"
|
|
if payload.get("status") != "ok" or payload.get("db") != "connected":
|
|
return payload, "State Hub is not healthy and database-connected"
|
|
return payload, None
|
|
|
|
|
|
def _workplan_projection_id(repo: Path, canonical_id: str) -> str | None:
|
|
"""Return the authoritative projection UUID for one open workplan."""
|
|
workplans_dir = repo / "workplans"
|
|
if not workplans_dir.is_dir():
|
|
return None
|
|
for path in sorted(workplans_dir.glob("*.md")):
|
|
parsed = parse_workplan_file(path, repo_root=repo)
|
|
if parsed.id != canonical_id:
|
|
continue
|
|
return parsed.state_hub_workstream_id or None
|
|
return None
|
|
|
|
|
|
def _projection_exists(api_base: str, projection_id: str) -> tuple[dict[str, Any], str | None]:
|
|
try:
|
|
response = httpx.get(
|
|
f"{api_base.rstrip('/')}/workplans/{projection_id}",
|
|
timeout=10.0,
|
|
)
|
|
response.raise_for_status()
|
|
payload = response.json()
|
|
except (httpx.HTTPError, ValueError) as exc:
|
|
return {}, f"workplan projection verification failed: {exc}"
|
|
if str(payload.get("id")) != projection_id:
|
|
return payload, "workplan projection returned an unexpected identity"
|
|
return payload, None
|
|
|
|
|
|
def _check_empty_repo_projection(
|
|
api_base: str, repo_slug: str
|
|
) -> tuple[dict[str, Any], str | None]:
|
|
try:
|
|
repo_response = httpx.get(f"{api_base.rstrip('/')}/repos/{repo_slug}", timeout=10.0)
|
|
repo_response.raise_for_status()
|
|
repo_payload = repo_response.json()
|
|
response = httpx.get(
|
|
f"{api_base.rstrip('/')}/workplans/",
|
|
params={"repo_id": repo_payload["id"]},
|
|
timeout=10.0,
|
|
)
|
|
response.raise_for_status()
|
|
rows = response.json()
|
|
except (httpx.HTTPError, KeyError, ValueError) as exc:
|
|
return {}, f"empty projection preflight failed: {exc}"
|
|
if not isinstance(rows, list):
|
|
return {}, "empty projection preflight returned an invalid workplan collection"
|
|
evidence = {"repo_id": repo_payload["id"], "workplan_count": len(rows)}
|
|
if rows:
|
|
return evidence, "target repository projection is not empty"
|
|
return evidence, None
|
|
|
|
|
|
def _authoritative_projection_ids(
|
|
repo: Path,
|
|
) -> tuple[set[str], set[str], dict[str, set[str]], str | None]:
|
|
workplan_ids: set[str] = set()
|
|
task_ids: set[str] = set()
|
|
record_ids: dict[str, set[str]] = {"intake": set(), "decision": set()}
|
|
for path in sorted((repo / "workplans").glob("*.md")):
|
|
parsed = parse_workplan_file(path, repo_root=repo)
|
|
if parsed.frontmatter.get("type") != "workplan" or not parsed.id:
|
|
continue
|
|
if not parsed.state_hub_workstream_id:
|
|
return set(), set(), record_ids, f"{parsed.id} has no authoritative projection UUID"
|
|
workplan_ids.add(parsed.state_hub_workstream_id)
|
|
for task in parsed.tasks:
|
|
if task.id and not task.state_hub_task_id:
|
|
return set(), set(), record_ids, f"{task.id} has no authoritative projection UUID"
|
|
if task.state_hub_task_id:
|
|
task_ids.add(task.state_hub_task_id)
|
|
if not workplan_ids:
|
|
return set(), set(), record_ids, "repository has no root workplans to rebuild"
|
|
for path in iter_record_files(repo):
|
|
for record in parse_record_file(path, repo_root=repo):
|
|
if record.kind not in record_ids:
|
|
continue
|
|
if not record.uuid:
|
|
return (
|
|
set(),
|
|
set(),
|
|
record_ids,
|
|
f"{record.id} has no authoritative projection UUID",
|
|
)
|
|
record_ids[record.kind].add(record.uuid)
|
|
return workplan_ids, task_ids, record_ids, None
|
|
|
|
|
|
def _verify_full_projection(
|
|
api_base: str,
|
|
workplan_ids: set[str],
|
|
task_ids: set[str],
|
|
record_ids: dict[str, set[str]],
|
|
) -> tuple[dict[str, Any], str | None]:
|
|
missing_workplans: list[str] = []
|
|
missing_tasks: list[str] = []
|
|
missing_records: dict[str, list[str]] = {"intake": [], "decision": []}
|
|
try:
|
|
for projection_id in sorted(workplan_ids):
|
|
response = httpx.get(
|
|
f"{api_base.rstrip('/')}/workplans/{projection_id}", timeout=10.0
|
|
)
|
|
if response.status_code == 404:
|
|
missing_workplans.append(projection_id)
|
|
else:
|
|
response.raise_for_status()
|
|
for projection_id in sorted(task_ids):
|
|
response = httpx.get(f"{api_base.rstrip('/')}/tasks/{projection_id}", timeout=10.0)
|
|
if response.status_code == 404:
|
|
missing_tasks.append(projection_id)
|
|
else:
|
|
response.raise_for_status()
|
|
for kind, endpoint in (("intake", "intakes"), ("decision", "decisions")):
|
|
for projection_id in sorted(record_ids[kind]):
|
|
response = httpx.get(
|
|
f"{api_base.rstrip('/')}/{endpoint}/{projection_id}", timeout=10.0
|
|
)
|
|
if response.status_code == 404:
|
|
missing_records[kind].append(projection_id)
|
|
else:
|
|
response.raise_for_status()
|
|
except httpx.HTTPError as exc:
|
|
return {}, f"full projection verification failed: {exc}"
|
|
evidence = {
|
|
"expected_workplans": len(workplan_ids),
|
|
"expected_tasks": len(task_ids),
|
|
"missing_workplans": missing_workplans,
|
|
"missing_tasks": missing_tasks,
|
|
"expected_intakes": len(record_ids["intake"]),
|
|
"expected_decisions": len(record_ids["decision"]),
|
|
"missing_intakes": missing_records["intake"],
|
|
"missing_decisions": missing_records["decision"],
|
|
}
|
|
if missing_workplans or missing_tasks or any(missing_records.values()):
|
|
return evidence, "full projection rebuild is incomplete"
|
|
return evidence, None
|
|
|
|
|
|
def _requested_projection_ids(
|
|
repo: Path,
|
|
requested: dict[str, list[str]],
|
|
) -> tuple[set[str], set[str], dict[str, set[str]], str | None]:
|
|
"""Resolve only the identifiers requested by this registrar invocation."""
|
|
wanted_workplans = set(requested["workplans"])
|
|
wanted_tasks = set(requested["tasks"])
|
|
wanted_records = {
|
|
"intake": set(requested["intakes"]),
|
|
"decision": set(requested["decisions"]),
|
|
}
|
|
resolved_workplans: dict[str, str] = {}
|
|
resolved_tasks: dict[str, str] = {}
|
|
resolved_records: dict[str, dict[str, str]] = {"intake": {}, "decision": {}}
|
|
|
|
for path in sorted((repo / "workplans").glob("*.md")):
|
|
parsed = parse_workplan_file(path, repo_root=repo)
|
|
if parsed.id in wanted_workplans and parsed.state_hub_workstream_id:
|
|
resolved_workplans[parsed.id] = parsed.state_hub_workstream_id
|
|
for task in parsed.tasks:
|
|
if task.id in wanted_tasks and task.state_hub_task_id:
|
|
resolved_tasks[task.id] = task.state_hub_task_id
|
|
|
|
for path in iter_record_files(repo):
|
|
for record in parse_record_file(path, repo_root=repo):
|
|
if record.kind in wanted_records and record.id in wanted_records[record.kind] and record.uuid:
|
|
resolved_records[record.kind][record.id] = record.uuid
|
|
|
|
unresolved = sorted(
|
|
(wanted_workplans - resolved_workplans.keys())
|
|
| (wanted_tasks - resolved_tasks.keys())
|
|
| (wanted_records["intake"] - resolved_records["intake"].keys())
|
|
| (wanted_records["decision"] - resolved_records["decision"].keys())
|
|
)
|
|
record_ids = {
|
|
kind: set(records.values()) for kind, records in resolved_records.items()
|
|
}
|
|
if unresolved:
|
|
return (
|
|
set(resolved_workplans.values()),
|
|
set(resolved_tasks.values()),
|
|
record_ids,
|
|
f"requested records have no assigned projection UUID: {', '.join(unresolved)}",
|
|
)
|
|
return (
|
|
set(resolved_workplans.values()),
|
|
set(resolved_tasks.values()),
|
|
record_ids,
|
|
None,
|
|
)
|
|
|
|
|
|
def _run_statehub(command: list[str], *, env: dict[str, str]) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
command,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
timeout=300,
|
|
env=env,
|
|
)
|
|
|
|
|
|
def _restore_generated_brief(repo: Path, paths: list[str]) -> tuple[list[str], str | None]:
|
|
"""Restore the generated brief after a projection-only repair child."""
|
|
if not paths:
|
|
return [], None
|
|
if paths != [".custodian-brief.md"]:
|
|
return [], "projection repair changed files other than the generated Custodian brief"
|
|
original = _git(repo, "show", "HEAD:.custodian-brief.md")
|
|
if original.returncode != 0:
|
|
return [], original.stderr.strip() or "could not read the committed Custodian brief"
|
|
(repo / ".custodian-brief.md").write_text(original.stdout, encoding="utf-8")
|
|
remaining = _git(repo, "status", "--porcelain")
|
|
if remaining.returncode != 0 or remaining.stdout.strip():
|
|
return [], remaining.stderr.strip() or "generated brief restoration left a dirty worktree"
|
|
return paths, None
|
|
|
|
|
|
def registrar_reconcile(
|
|
path: Path,
|
|
*,
|
|
api_base: str = "http://127.0.0.1:8000",
|
|
statehub_bin: str | None = None,
|
|
confirm_primary: bool = False,
|
|
push: bool = False,
|
|
repair_workplan: str | None = None,
|
|
bootstrap_empty_projection: bool = False,
|
|
) -> RegistrarResult:
|
|
"""Register missing workplan/task UUIDs through one scoped child process."""
|
|
cid = str(uuid.uuid4())
|
|
repo = path.expanduser().resolve()
|
|
identity = scan_record_identities(repo)
|
|
before = _missing_identifiers(repo)
|
|
evidence: dict[str, Any] = {
|
|
"repo_path": str(repo),
|
|
"repo_slug": repo.name,
|
|
"api_base": api_base.rstrip("/"),
|
|
"missing_before": before,
|
|
"record_identity": identity,
|
|
}
|
|
|
|
if identity["identity_collisions"]:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{
|
|
"code": "record_identity_collision",
|
|
"message": "same canonical work-record id has conflicting or incomplete UUID assignments",
|
|
},
|
|
cid,
|
|
)
|
|
|
|
repair_projection_id = None
|
|
if repair_workplan and bootstrap_empty_projection:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{
|
|
"code": "registrar_mode_conflict",
|
|
"message": "repair-workplan and bootstrap-empty-projection are mutually exclusive",
|
|
},
|
|
cid,
|
|
)
|
|
if repair_workplan:
|
|
repair_projection_id = _workplan_projection_id(repo, repair_workplan)
|
|
evidence["repair_workplan"] = repair_workplan
|
|
evidence["repair_projection_id"] = repair_projection_id
|
|
if not repair_projection_id:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{
|
|
"code": "repair_target_invalid",
|
|
"message": "repair workplan is absent or has no authoritative projection UUID",
|
|
},
|
|
cid,
|
|
)
|
|
|
|
if not confirm_primary:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{
|
|
"code": "confirmation_required",
|
|
"message": "pass --confirm-primary after verifying this is the authoritative hub",
|
|
},
|
|
cid,
|
|
)
|
|
if not any(before.values()) and not repair_projection_id and not bootstrap_empty_projection:
|
|
evidence["missing_after"] = before
|
|
return RegistrarResult("noop", evidence, None, cid)
|
|
|
|
git_evidence, git_error = _check_git(repo)
|
|
evidence["git"] = git_evidence
|
|
if git_error:
|
|
return RegistrarResult(
|
|
"rejected", evidence, {"code": "git_precondition_failed", "message": git_error}, cid
|
|
)
|
|
|
|
health, health_error = _check_primary(api_base)
|
|
evidence["state_hub_health"] = health
|
|
if health_error:
|
|
return RegistrarResult(
|
|
"rejected", evidence, {"code": "primary_unavailable", "message": health_error}, cid
|
|
)
|
|
|
|
executable = statehub_bin or shutil.which("statehub")
|
|
if not executable:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{"code": "statehub_missing", "message": "statehub CLI is not installed"},
|
|
cid,
|
|
)
|
|
|
|
bootstrap_workplans: set[str] = set()
|
|
bootstrap_tasks: set[str] = set()
|
|
bootstrap_records: dict[str, set[str]] = {"intake": set(), "decision": set()}
|
|
if bootstrap_empty_projection:
|
|
bootstrap_before, bootstrap_error = _check_empty_repo_projection(api_base, repo.name)
|
|
evidence["bootstrap_projection_before"] = bootstrap_before
|
|
if bootstrap_error:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{"code": "bootstrap_precondition_failed", "message": bootstrap_error},
|
|
cid,
|
|
)
|
|
(
|
|
bootstrap_workplans,
|
|
bootstrap_tasks,
|
|
bootstrap_records,
|
|
source_error,
|
|
) = _authoritative_projection_ids(repo)
|
|
evidence["bootstrap_source"] = {
|
|
"workplans": len(bootstrap_workplans),
|
|
"tasks": len(bootstrap_tasks),
|
|
"intakes": len(bootstrap_records["intake"]),
|
|
"decisions": len(bootstrap_records["decision"]),
|
|
}
|
|
if source_error:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{"code": "bootstrap_source_invalid", "message": source_error},
|
|
cid,
|
|
)
|
|
|
|
LOCK_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
with LOCK_PATH.open("a+", encoding="utf-8") as lock:
|
|
try:
|
|
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
except BlockingIOError:
|
|
return RegistrarResult(
|
|
"rejected",
|
|
evidence,
|
|
{"code": "registrar_busy", "message": "another registrar reconciliation is active"},
|
|
cid,
|
|
)
|
|
|
|
child_env = {**os.environ, "STATEHUB_REGISTRAR": "1"}
|
|
command = [
|
|
executable,
|
|
"fix-consistency",
|
|
"--path",
|
|
str(repo),
|
|
"--api-base",
|
|
api_base.rstrip("/"),
|
|
]
|
|
if bootstrap_empty_projection:
|
|
command.append("--bootstrap-empty-projection")
|
|
completed = _run_statehub(command, env=child_env)
|
|
|
|
evidence["statehub_exit_code"] = completed.returncode
|
|
evidence["statehub_stdout_tail"] = completed.stdout[-4000:]
|
|
evidence["statehub_stderr_tail"] = completed.stderr[-2000:]
|
|
after = _missing_identifiers(repo)
|
|
evidence["missing_after"] = after
|
|
repair_verified = False
|
|
if repair_projection_id:
|
|
projection, projection_error = _projection_exists(api_base, repair_projection_id)
|
|
evidence["repair_projection"] = projection
|
|
evidence["repair_projection_verified"] = projection_error is None
|
|
if projection_error:
|
|
evidence["repair_projection_error"] = projection_error
|
|
repair_verified = projection_error is None
|
|
|
|
bootstrap_verified = False
|
|
if bootstrap_empty_projection:
|
|
projection, projection_error = _verify_full_projection(
|
|
api_base,
|
|
bootstrap_workplans,
|
|
bootstrap_tasks,
|
|
bootstrap_records,
|
|
)
|
|
evidence["bootstrap_projection"] = projection
|
|
evidence["bootstrap_projection_verified"] = projection_error is None
|
|
if projection_error:
|
|
evidence["bootstrap_projection_error"] = projection_error
|
|
bootstrap_verified = projection_error is None
|
|
|
|
requested_verified = False
|
|
if completed.returncode == 1 and any(before.values()) and not any(after.values()):
|
|
(
|
|
requested_workplans,
|
|
requested_tasks,
|
|
requested_records,
|
|
requested_source_error,
|
|
) = _requested_projection_ids(repo, before)
|
|
if requested_source_error:
|
|
evidence["requested_projection_error"] = requested_source_error
|
|
else:
|
|
projection, projection_error = _verify_full_projection(
|
|
api_base,
|
|
requested_workplans,
|
|
requested_tasks,
|
|
requested_records,
|
|
)
|
|
evidence["requested_projection"] = projection
|
|
evidence["requested_projection_verified"] = projection_error is None
|
|
if projection_error:
|
|
evidence["requested_projection_error"] = projection_error
|
|
requested_verified = projection_error is None
|
|
|
|
accepted_exit_codes = {0, 2}
|
|
if repair_verified or bootstrap_verified or requested_verified:
|
|
# A repository-scoped projection repair may coexist with legacy stale
|
|
# references that correctly keep the broader consistency report red.
|
|
accepted_exit_codes.add(1)
|
|
mode_verification_failed = bool(repair_projection_id and not repair_verified) or (
|
|
bootstrap_empty_projection and not bootstrap_verified
|
|
)
|
|
if (
|
|
completed.returncode not in accepted_exit_codes
|
|
or any(after.values())
|
|
or mode_verification_failed
|
|
):
|
|
return RegistrarResult(
|
|
"failed",
|
|
evidence,
|
|
{
|
|
"code": "registration_incomplete",
|
|
"message": "State Hub reconciliation did not assign every requested identifier",
|
|
},
|
|
cid,
|
|
)
|
|
|
|
changed = _git(repo, "status", "--porcelain")
|
|
paths = [line[3:] for line in changed.stdout.splitlines() if len(line) > 3]
|
|
if (repair_verified or bootstrap_verified) and paths:
|
|
restored, restore_error = _restore_generated_brief(repo, paths)
|
|
evidence["restored_generated_paths"] = restored
|
|
if restore_error:
|
|
return RegistrarResult(
|
|
"failed",
|
|
evidence,
|
|
{"code": "projection_repair_file_change", "message": restore_error},
|
|
cid,
|
|
)
|
|
paths = []
|
|
if paths:
|
|
try:
|
|
evidence["git_sha"] = commit_paths(
|
|
repo,
|
|
paths,
|
|
"chore(registrar): assign State Hub identifiers",
|
|
)
|
|
except GitError as exc:
|
|
return RegistrarResult(
|
|
"failed",
|
|
evidence,
|
|
{"code": "commit_failed", "message": str(exc)},
|
|
cid,
|
|
)
|
|
|
|
if push:
|
|
pushed, message = push_ff(repo)
|
|
evidence["push"] = {"ok": pushed, "message": message}
|
|
if not pushed:
|
|
return RegistrarResult(
|
|
"failed",
|
|
evidence,
|
|
{"code": "push_failed", "message": message},
|
|
cid,
|
|
)
|
|
|
|
return RegistrarResult("applied", evidence, None, cid)
|