Integrate current task recovery and accepted planning contracts
Why: Preserve work merged by concurrent tasks while adopting versioned dependency workspaces. Evidence: SELF task receipts record conflict detection, all 96 task/recovery/cutover cases, all 33 evidence cases and 5 component identity cases. Documents links and requirements were replayed after preserving both planning entries. Impact: Keep upstream idempotent sync, local resume and source-external receipt rules; adapt fixed-guidelines readiness and ignored component identity validation. Remove only obsolete index links and retain their physical Git metadata. Refs: Authoritative main57bcb17ea212a531186b6e21617a7f3bea65a791; Documents4a26798000c39cc59ba0c2cc6328f5c8f10dcb32; dependency-workspace R15.
This commit is contained in:
+77
-6
@@ -249,19 +249,83 @@ def read_receipt(filename):
|
||||
return value
|
||||
|
||||
|
||||
def validate_precheck(receipt):
|
||||
def task_context(current=None):
|
||||
"""Verify an existing task checkout; this is not an approval identity."""
|
||||
current = snapshot() if current is None else current
|
||||
root = Path(git_text("rev-parse", "--show-toplevel")).resolve()
|
||||
branch = current["branch"]
|
||||
if not re.fullmatch(r"(?:feature|fix|hotfix|release)/[A-Za-z0-9._-]+", branch):
|
||||
raise EvidenceError("Local recovery requires a task branch")
|
||||
records = [row.split(b"\0") for row in git("worktree", "list", "--porcelain", "-z").split(b"\0\0") if row]
|
||||
matches = [row for row in records if row[0].startswith(b"worktree ")
|
||||
and Path(os.fsdecode(row[0][9:])).resolve() == root]
|
||||
if len(matches) != 1 or not records or matches[0] == records[0]:
|
||||
raise EvidenceError("Recovery requires a registered linked task worktree")
|
||||
entry = matches[0]
|
||||
if any(field.startswith((b"bare", b"detached", b"prunable")) for field in entry) or (
|
||||
b"branch refs/heads/" + branch.encode() not in entry):
|
||||
raise EvidenceError("Task worktree registration is not valid")
|
||||
if (root / ".task").is_symlink():
|
||||
raise EvidenceError("Task registration must belong to the current checkout")
|
||||
card = find_card()
|
||||
if card.is_symlink() or card.resolve().parent != root / ".task":
|
||||
raise EvidenceError("Task registration must belong to the current checkout")
|
||||
content = card.read_text()
|
||||
locations = re.findall(r"^- 工作区:(.+?)\s*[((]worktree[))];分支:([^\s]+)\s*$", content, re.M)
|
||||
bases = re.findall(r"^- 权威仓:.*;目标分支:([^;]+);R0(initial_base_sha)= ([0-9a-f]+)\s*$", content, re.M)
|
||||
if len(locations) != 1 or Path(locations[0][0]).resolve() != root or locations[0][1] != branch:
|
||||
raise EvidenceError("Task card belongs to a different worktree or branch")
|
||||
if len(bases) != 1 or bases[0][0] != current["target_branch"] or not GIT_SHA.fullmatch(bases[0][1]):
|
||||
raise EvidenceError("Task card has no valid target and initial baseline")
|
||||
r0 = bases[0][1]
|
||||
if not current["target_head"]:
|
||||
raise EvidenceError("Task target is unverified; fetch it first")
|
||||
for tip in (current["head"], current["target_head"]):
|
||||
git("merge-base", "--is-ancestor", r0, tip)
|
||||
check = subprocess.run([sys.executable, str(Path(__file__).with_name("check-worktree.py")), "before-sync"],
|
||||
capture_output=True, text=True)
|
||||
if check.returncode:
|
||||
raise EvidenceError("Task workspace is not ready: " + (check.stdout + check.stderr).strip())
|
||||
return {"root": root, "r0": r0}
|
||||
|
||||
|
||||
def validate_precheck(receipt, purpose="start"):
|
||||
if purpose not in ("start", "resume-delivery", "resume-local"):
|
||||
raise EvidenceError("Unknown precheck validation purpose")
|
||||
after = receipt["after"]
|
||||
current = snapshot()
|
||||
if receipt["command"] not in ("make precheck", "bash scripts/task.sh precheck"):
|
||||
raise EvidenceError("Task start requires a precheck receipt")
|
||||
if receipt["exit_code"] != 0 or not re.search(r"^\[verdict\] READY(?:\s|$)", receipt["output"], re.M):
|
||||
raise EvidenceError("Precheck receipt is not READY")
|
||||
expected = "LOCAL_READY" if purpose == "resume-local" else "READY"
|
||||
verdicts = re.findall(r"^\[verdict\] ([A-Z_]+)(?:\s|$)", receipt["output"], re.M)
|
||||
if receipt["exit_code"] != 0 or verdicts != [expected]:
|
||||
raise EvidenceError("Precheck receipt is not " + expected)
|
||||
if after is None or receipt["after_error"] is not None:
|
||||
raise EvidenceError("Precheck object capture is incomplete")
|
||||
if receipt["before"]["head"] != current["head"] or after["head"] != current["head"]:
|
||||
raise EvidenceError("Precheck source HEAD has changed")
|
||||
if after["target_branch"] != current["target_branch"] or not after["target_head"] or after["target_head"] != current["target_head"]:
|
||||
if current.get("components") is not None and any(
|
||||
obj.get("components") != current["components"] for obj in (receipt["before"], after)):
|
||||
raise EvidenceError("Precheck ordinary component objects have changed or are missing")
|
||||
if after["target_branch"] != current["target_branch"] or not after["target_head"]:
|
||||
raise EvidenceError("Precheck authoritative target has changed")
|
||||
if purpose != "resume-local" and after["target_head"] != current["target_head"]:
|
||||
raise EvidenceError("Precheck authoritative target has changed")
|
||||
if purpose.startswith("resume-"):
|
||||
context = task_context(current)
|
||||
try:
|
||||
Path(receipt["cwd"]).resolve().relative_to(context["root"])
|
||||
except ValueError:
|
||||
raise EvidenceError("Precheck belongs to a different task worktree")
|
||||
if any(obj["branch"] != current["branch"] or obj["target_branch"] != current["target_branch"]
|
||||
for obj in (receipt["before"], after)):
|
||||
raise EvidenceError("Precheck belongs to a different task branch")
|
||||
git("merge-base", "--is-ancestor", context["r0"], after["target_head"])
|
||||
if purpose == "resume-local":
|
||||
try:
|
||||
git("merge-base", "--is-ancestor", after["target_head"], current["target_head"])
|
||||
except EvidenceError:
|
||||
raise EvidenceError("Precheck target history has rewound or diverged") from None
|
||||
|
||||
|
||||
def find_card():
|
||||
@@ -313,6 +377,12 @@ def main():
|
||||
try:
|
||||
if os.environ.get("SRC", "SELF") not in ("", "SELF"):
|
||||
raise EvidenceError("Local evidence is SELF; GATE/REPLAY require external execution identity")
|
||||
if sys.argv[1:] == ["--local-context"]:
|
||||
context = task_context()
|
||||
print("[task-state] registered=" + str(context["root"]) + " initial_base=" + context["r0"])
|
||||
return 0
|
||||
if sys.argv[1:]:
|
||||
raise EvidenceError("Unknown evidence arguments")
|
||||
capture, imported, validated = (os.environ.get(key) for key in
|
||||
("RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT"))
|
||||
command = os.environ.get("CMD", "")
|
||||
@@ -321,7 +391,7 @@ def main():
|
||||
if imported or validated:
|
||||
receipt = read_receipt(imported or validated)
|
||||
if validated:
|
||||
validate_precheck(receipt)
|
||||
validate_precheck(receipt, os.environ.get("VALIDATE_RECEIPT_PURPOSE", "start"))
|
||||
report(receipt, "precheck validated")
|
||||
else:
|
||||
card = find_card()
|
||||
@@ -338,7 +408,8 @@ def main():
|
||||
source_cwd, source_common = str(Path.cwd().resolve()), common_dir()
|
||||
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
child_env = os.environ.copy()
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL"):
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL",
|
||||
"VALIDATE_RECEIPT_PURPOSE"):
|
||||
child_env.pop(key, None)
|
||||
result = subprocess.run(["bash", "-o", "pipefail", "-c", command],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=child_env)
|
||||
|
||||
Reference in New Issue
Block a user