Compose immutable dependency sources and preserve branch transitions
Why: Replace repeated submodule source pointers with consumer-owned language versions and fixed application sources, keeping independent repository edits and existing branches intact. Evidence: SELF task receipts contain startup/sync evidence, real Go and npm consumption, Native overlay/unlink, safe transition/ref preservation, credential boundary and historical build regressions. Exact committed full composition and RDM checks follow this checkpoint; formal acceptance and GATE CI remain pending. Impact: Add isolated build/test composition, source provenance and safe owner refresh, adapt governance and service build callers, remove replaced index links while retaining original Git metadata, and adopt reusable dependency standards. Refs: Documents approved dependency-workspace r1/R1-R15 and U1-U7 implementation plan; feature/dependency-workspace task card.
This commit is contained in:
@@ -65,7 +65,43 @@ class Workspace:
|
||||
self.state.data["components"][component["id"]] = record
|
||||
return record
|
||||
|
||||
def prepare_one(self, component):
|
||||
def refresh_versioned(self, component, path, record, commit, source):
|
||||
old_commit = record["baseline"]
|
||||
old_source = record["source"]
|
||||
reference = "refs/workspace/fixed/" + old_commit
|
||||
try:
|
||||
self.git.call(path, "fetch", "--no-tags", "origin", commit)
|
||||
# Fetch can take time. Recheck ownership and files before changing
|
||||
# this registered checkout, without forcing any owner's changes.
|
||||
actual = self.git.safe(path, component["repo"])
|
||||
if actual["branch"] or actual["head"] != old_commit:
|
||||
raise WorkspaceError("fixed checkout changed during fetch; owner must inspect it")
|
||||
saved = self.git.text(path, "rev-parse", "--verify", "--quiet", reference, check=False)
|
||||
if saved and saved != old_commit:
|
||||
raise WorkspaceError("preserved fixed reference has another value; owner must inspect it")
|
||||
if not saved:
|
||||
self.git.call(path, "update-ref", reference, old_commit, "0" * 40)
|
||||
self.git.call(path, "checkout", "--no-overwrite-ignore", "--detach", commit)
|
||||
actual = self.git.safe(path, component["repo"])
|
||||
if actual["head"] != commit or actual["branch"]:
|
||||
raise WorkspaceError("refreshed checkout differs from the declared fixed source")
|
||||
history = list(record.get("previous_sources", []))
|
||||
previous = {"commit": old_commit, "source": old_source, "ref": reference}
|
||||
if previous not in history:
|
||||
history.append(previous)
|
||||
record.update({"baseline": commit, "source": source, "branch": None,
|
||||
"mode": "versioned", "previous_sources": history})
|
||||
record.pop("error", None)
|
||||
self.state.save()
|
||||
return record
|
||||
except Exception as error:
|
||||
# In particular, a failed fetch retains the old baseline and mode,
|
||||
# so retrying the explicit refresh cannot mistake it for a partial clone.
|
||||
record["error"] = str(error)
|
||||
self.state.save()
|
||||
raise
|
||||
|
||||
def prepare_one(self, component, refresh_fixed=False):
|
||||
name = component["id"]
|
||||
path = safe_path(self.root, component["path"])
|
||||
commit, source = self.baseline(component)
|
||||
@@ -75,7 +111,12 @@ class Workspace:
|
||||
if path.exists():
|
||||
editable = record and record.get("mode") in ("editable", "linked")
|
||||
partial = record and record.get("mode") == "preparing"
|
||||
actual = self.git.safe(path, component["repo"], clean=not (editable or partial))
|
||||
actual = self.git.safe(path, component["repo"], clean=refresh_fixed or not (editable or partial))
|
||||
if refresh_fixed:
|
||||
if not record or record.get("mode") != "versioned" or actual["branch"] or actual["head"] != record["baseline"]:
|
||||
raise WorkspaceError(f"fixed refresh requires a registered detached versioned checkout at its baseline: {name}")
|
||||
if actual["head"] != commit:
|
||||
return self.refresh_versioned(component, path, record, commit, source)
|
||||
if editable:
|
||||
if record["baseline"] != commit:
|
||||
raise WorkspaceError(f"version changed while {name} is editable; owner must synchronize explicitly")
|
||||
@@ -91,6 +132,7 @@ class Workspace:
|
||||
if record is None:
|
||||
record = self.record(component, commit, source)
|
||||
record.update({"mode": "versioned", "baseline": commit, "source": source})
|
||||
record.pop("error", None)
|
||||
self.state.save()
|
||||
return record
|
||||
elif record and record.get("mode") != "preparing":
|
||||
@@ -197,6 +239,7 @@ def main(argv=None):
|
||||
parser.add_argument("--name")
|
||||
parser.add_argument("--branch")
|
||||
parser.add_argument("--apply", action="store_true")
|
||||
parser.add_argument("--refresh-fixed", action="store_true", help="explicitly refresh registered clean detached fixed sources")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
if args.branch:
|
||||
@@ -205,6 +248,8 @@ def main(argv=None):
|
||||
raise WorkspaceError("--name is required for component editing/unlinking")
|
||||
if args.apply and args.command != "transition":
|
||||
raise WorkspaceError("--apply is only valid for transition")
|
||||
if args.refresh_fixed and args.command != "prepare":
|
||||
raise WorkspaceError("--refresh-fixed is only valid for prepare")
|
||||
workspace = Workspace(args.root)
|
||||
components = workspace.manifest.select(args.name, fixed=args.command in ("prepare", "verify"))
|
||||
if args.command == "status":
|
||||
@@ -223,7 +268,7 @@ def main(argv=None):
|
||||
workspace.transition.apply_all(components, args.branch)
|
||||
for component in components:
|
||||
if args.command == "prepare":
|
||||
workspace.prepare_one(component)
|
||||
workspace.prepare_one(component, refresh_fixed=args.refresh_fixed)
|
||||
elif args.command == "edit":
|
||||
workspace.edit(component, args.branch)
|
||||
elif args.command == "unlink":
|
||||
|
||||
Reference in New Issue
Block a user