Advance dependency roots with guarded source implementations
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a10391-6355-7b41-b0fa-3e14e874f091
This commit is contained in:
parent
caef8df3aa
commit
57f2751f7b
10 changed files with 996 additions and 14 deletions
|
|
@ -154,6 +154,7 @@ class DerivedWorkplan:
|
|||
flavor: str | None = None
|
||||
flavor_promotion_reason: str | None = None
|
||||
flavor_promoted_from: str | None = None
|
||||
historical_preservation: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -205,6 +206,7 @@ class DerivedProjection:
|
|||
"flavor_promoted_from": w.flavor_promoted_from,
|
||||
"relative_path": w.relative_path,
|
||||
"archived": w.archived,
|
||||
"historical_preservation": w.historical_preservation,
|
||||
"tasks": [
|
||||
{
|
||||
"record_id": t.record_id,
|
||||
|
|
@ -444,7 +446,40 @@ def derive_from_checkout(repo_root: Path, repo_slug: str, commit: str) -> Derive
|
|||
if not wp_dir.is_dir():
|
||||
proj.records_source_present = False
|
||||
return proj
|
||||
# Exact immutable histories are preserved by existing UUID, never allocated.
|
||||
from api.services.historical_projection import bound_sources
|
||||
try:
|
||||
historical = bound_sources(repo_root, repo_slug)
|
||||
except (ValueError, OSError, UnicodeError) as exc:
|
||||
raise ForgeDeriveError(str(exc)) from exc
|
||||
consumed = {entry["path"] for entry, _text in historical}
|
||||
groups = {}
|
||||
for entry, text in historical:
|
||||
meta, body = _split_frontmatter(text)
|
||||
parent_id = entry["parent_record_id"]
|
||||
# Split views carry the parent's existing UUID and repeat its task list.
|
||||
# Only the reviewed archived parent supplies the projected row.
|
||||
if entry["record_id"] != parent_id:
|
||||
continue
|
||||
if repo_slug == "activity-core":
|
||||
tasks = [DerivedTask(record_id=item["id"], uuid=item["state_hub_task_id"],
|
||||
title=item.get("title"), status=item.get("status"),
|
||||
priority=item.get("priority")) for item in meta["tasks"]]
|
||||
else:
|
||||
tasks = _parse_tasks(body, parent_id)
|
||||
bindings = [yaml.safe_load(m.group(1)) for m in _TASK_BLOCK_RE.finditer(body)]
|
||||
for task, binding in zip(tasks, bindings, strict=True):
|
||||
task.uuid = binding["state_hub_task_id"]
|
||||
groups[parent_id] = DerivedWorkplan(
|
||||
record_id=parent_id, uuid=entry["uuid"], title=meta.get("title"),
|
||||
status=meta.get("status"), relative_path=entry["path"],
|
||||
archived="/archived/" in entry["path"], tasks=tasks,
|
||||
historical_preservation=True,
|
||||
)
|
||||
proj.workplans.extend(groups.values())
|
||||
for path in sorted(wp_dir.rglob("*.md")):
|
||||
if path.relative_to(repo_root).as_posix() in consumed:
|
||||
continue
|
||||
if path.name.startswith("."):
|
||||
continue
|
||||
try:
|
||||
|
|
@ -646,7 +681,11 @@ def diff_against_hub(
|
|||
want_wp = {w.record_id.strip().lower(): w for w in derived.workplans}
|
||||
have_wp: dict[str, dict[str, Any]] = {}
|
||||
want_paths = {_path_key(w.relative_path): k for k, w in want_wp.items()}
|
||||
preserved_by_uuid = {w.uuid: key for key, w in want_wp.items() if w.historical_preservation}
|
||||
for w in hub_workplans:
|
||||
if str(w["id"]) in preserved_by_uuid:
|
||||
have_wp[preserved_by_uuid[str(w["id"])]] = w
|
||||
continue
|
||||
slug = str(w.get("slug") or "")
|
||||
key = slug.strip().lower()
|
||||
if key not in want_wp:
|
||||
|
|
@ -686,6 +725,11 @@ def diff_against_hub(
|
|||
uid = str(cur["id"]) if cur else w.uuid
|
||||
if not cur:
|
||||
continue
|
||||
if w.historical_preservation:
|
||||
if cur.get("status") not in {"done", "finished", "archived", "completed"}:
|
||||
d.differing.append({"kind": "workplan", "record_id": w.record_id, "uuid": uid,
|
||||
"changed": {"historical_terminal_status": cur.get("status")}})
|
||||
continue
|
||||
changed = {}
|
||||
if (cur.get("status") or None) != (w.status or None):
|
||||
changed["status"] = {"hub": cur.get("status"), "forge": w.status}
|
||||
|
|
@ -707,6 +751,18 @@ def diff_against_hub(
|
|||
cur = have_wp.get(w.record_id.strip().lower())
|
||||
if cur:
|
||||
hub_rows = hub_tasks_by_workplan.get(str(cur["id"]), [])
|
||||
if w.historical_preservation:
|
||||
by_uuid = {str(task["id"]): task for task in hub_rows}
|
||||
for task in w.tasks:
|
||||
current = by_uuid.get(task.uuid)
|
||||
if current is None:
|
||||
d.missing.append({"kind": "task", "record_id": task.record_id,
|
||||
"uuid": task.uuid, "workplan": w.record_id})
|
||||
elif current.get("status") not in {"done", "cancel"}:
|
||||
d.differing.append({"kind": "task", "record_id": task.record_id,
|
||||
"uuid": task.uuid, "changed": {
|
||||
"historical_terminal_status": current.get("status")}})
|
||||
continue
|
||||
# Match on the canonical record id where the hub has one. Rows created
|
||||
# before STATE-WP-0083-T06 fall back to title, which is why those are
|
||||
# reported rather than acted on: a renamed heading is indistinguishable
|
||||
|
|
@ -1312,6 +1368,27 @@ async def reset_repository_projection(
|
|||
for task_row in loaded:
|
||||
tasks_by_wp.setdefault(task_row.workplan_id, []).append(task_row)
|
||||
|
||||
# Bound histories are receiving support for already existing records only.
|
||||
# Verify every parent and task before any mutation; preserve labels, status,
|
||||
# retirement stamps and all UUIDs. Missing rows cannot authorize recreation.
|
||||
for key, workplan in want.items():
|
||||
if not workplan.historical_preservation:
|
||||
continue
|
||||
row = matched.get(key)
|
||||
local = {str(task.id): task for task in tasks_by_wp.get(row.id, [])} if row else {}
|
||||
missing = [task.uuid for task in workplan.tasks if task.uuid not in local]
|
||||
nonterminal = [task.uuid for task in workplan.tasks
|
||||
if task.uuid in local and local[task.uuid].status not in {"done", "cancel"}]
|
||||
if (row is None or str(row.id) != workplan.uuid or missing or nonterminal
|
||||
or row.status not in {"done", "finished", "archived", "completed"}):
|
||||
outcome.status = "refused"
|
||||
outcome.refused.append({
|
||||
"reason": "historical binding requires existing parent and task UUIDs",
|
||||
"workplan": workplan.record_id, "uuid": workplan.uuid,
|
||||
"missing_task_uuids": missing, "nonterminal_task_uuids": nonterminal,
|
||||
})
|
||||
return outcome
|
||||
|
||||
# Canonical task identity is scoped to the matched current workplan while
|
||||
# reconciling. Duplicate rows attached to a retired/displaced workplan are
|
||||
# historical evidence and are deliberately not loaded above. Two rows with
|
||||
|
|
@ -1374,22 +1451,11 @@ async def reset_repository_projection(
|
|||
creating_tasks[task.uuid] = (workplan, task)
|
||||
|
||||
if creating_tasks:
|
||||
current_holder_filters = [Workplan.projection_retired_at.is_(None)]
|
||||
if stale:
|
||||
# The caller has already acknowledged these retirements (the
|
||||
# unacknowledged path returned above). Their tasks are preserved as
|
||||
# history, but must not prevent the replacement projection from
|
||||
# claiming its deterministic identities in the same transaction.
|
||||
current_holder_filters.append(
|
||||
Workplan.id.not_in([workplan.id for workplan in stale])
|
||||
)
|
||||
task_holders = list(
|
||||
(
|
||||
await session.execute(
|
||||
select(Task)
|
||||
.join(Workplan, Workplan.id == Task.workplan_id)
|
||||
.where(
|
||||
*current_holder_filters,
|
||||
Task.id.in_(
|
||||
[uuid.UUID(task_id) for task_id in creating_tasks]
|
||||
),
|
||||
|
|
@ -1403,7 +1469,7 @@ async def reset_repository_projection(
|
|||
workplan, task = creating_tasks[str(holder.id)]
|
||||
outcome.refused.append(
|
||||
{
|
||||
"reason": "derived task identifier already belongs to another current workplan",
|
||||
"reason": "derived task identifier already belongs to another workplan",
|
||||
"kind": "task",
|
||||
"record_id": task.record_id,
|
||||
"uuid": task.uuid,
|
||||
|
|
@ -1463,6 +1529,10 @@ async def reset_repository_projection(
|
|||
outcome.created.append(w.record_id)
|
||||
continue
|
||||
|
||||
if w.historical_preservation:
|
||||
outcome.notes.append("Preserved bound historical row and task UUIDs: " + w.record_id)
|
||||
continue
|
||||
|
||||
changed = False
|
||||
# The title is derived like every other field, and not syncing it left
|
||||
# `cust-wp-0010` reading "Domain and Repository Goals" while its file
|
||||
|
|
|
|||
90
api/services/historical_projection.py
Normal file
90
api/services/historical_projection.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Exact closed source bindings predating canonical Forge identity.
|
||||
|
||||
These are frozen historical source views, not aliases or UUID allocation rules.
|
||||
An altered/missing source requires explicit owner review; generic source UUIDs
|
||||
remain untrusted. Shared Activity views represent the one archived parent.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from hashlib import sha256
|
||||
import json
|
||||
|
||||
BINDINGS = json.loads(r'''[
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/archived/260707-custodian-WP-0003-event-bridge.md",
|
||||
"sha256": "83ea8bcb151e003419b73c59dbd028f4a1ce3d1120a82e4a128a772503ff3b76",
|
||||
"record_id": "custodian-WP-0003",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0001-temporal-backbone.md",
|
||||
"sha256": "020e2eac973f2daa41c1687f8e867da9d521f945547e6aa82c87993b3ff6fc64",
|
||||
"record_id": "custodian-WP-0001",
|
||||
"uuid": "e22516cc-4fdf-4dad-a928-a9e04a9fa7c7",
|
||||
"parent_record_id": "custodian-WP-0001"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0002-triggers-ops.md",
|
||||
"sha256": "d8cbc61631c01b6f11a03a63f2fb6275fc853ff7f7459ce65973ed1a233daa01",
|
||||
"record_id": "custodian-WP-0002",
|
||||
"uuid": "3a4f47d9-8bc1-434e-acb4-bed5d4dacda0",
|
||||
"parent_record_id": "custodian-WP-0002"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md",
|
||||
"sha256": "ffa6682e9ba3aaf559bd44203d561c90dea69bcf1b1e778209a7b209e6e5cdd3",
|
||||
"record_id": "custodian-WP-0003a",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md",
|
||||
"sha256": "4fc34e0f821e2df51e5c08c7ab34da35b65690381d01f0cfdf55d00c970b414c",
|
||||
"record_id": "custodian-WP-0003b",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0003c-event-bridge-context-integration.md",
|
||||
"sha256": "b6cd62b26515e509670ca39736b6bcec9633beedd46c9bd8f7598c06bac7276b",
|
||||
"record_id": "custodian-WP-0003c",
|
||||
"uuid": "b4eb45a9-69e3-4ab0-b00c-67a53c3117c5",
|
||||
"parent_record_id": "custodian-WP-0003"
|
||||
},
|
||||
{
|
||||
"repo": "activity-core",
|
||||
"path": "workplans/custodian-WP-0004-railiance-ops.md",
|
||||
"sha256": "20df02eea740e109c937130e7f8801a636fc0990ed8c6d2ced55659f8957adb1",
|
||||
"record_id": "custodian-WP-0004",
|
||||
"uuid": "759b1255-aa78-42b5-8fab-c5fcf168f5f4",
|
||||
"parent_record_id": "custodian-WP-0004"
|
||||
},
|
||||
{
|
||||
"repo": "the-custodian",
|
||||
"path": "workplans/ADHOC-2026-08-25-retired.md",
|
||||
"sha256": "344a1293dbb3a67a944eb34ca4a0337487d8f047226027a2107922346fbab2dd",
|
||||
"record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED",
|
||||
"uuid": "1c53d3db-6633-5e2d-987f-10706d73be9e",
|
||||
"parent_record_id": "CUST-WP-ADHOC-2026-08-25-RETIRED"
|
||||
}
|
||||
]''')
|
||||
|
||||
|
||||
def bound_sources(repo_root: Path, repo_slug: str):
|
||||
entries = [entry for entry in BINDINGS if entry["repo"] == repo_slug]
|
||||
result = []
|
||||
for entry in entries:
|
||||
path = repo_root / entry["path"]
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise ValueError("Historical source binding missing or symlinked: " + entry["path"])
|
||||
data = path.read_bytes()
|
||||
if sha256(data).hexdigest() != entry["sha256"]:
|
||||
raise ValueError("Historical source binding changed: " + entry["path"])
|
||||
result.append((entry, data.decode("utf-8")))
|
||||
return result
|
||||
Loading…
Add table
Add a link
Reference in a new issue