From 4235ce5d9033944cc3d1aff3b8d05d02cdd2e21c Mon Sep 17 00:00:00 2001 From: shrutik pradip balwan Date: Mon, 12 Oct 2026 04:05:47 +0530 Subject: [PATCH 1/2] Add cloud-only begin transition preflight --- .github/workflows/control-begin-preflight.yml | 34 +++++++++ tools/cloud_begin_preflight.py | 74 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 .github/workflows/control-begin-preflight.yml create mode 100644 tools/cloud_begin_preflight.py diff --git a/.github/workflows/control-begin-preflight.yml b/.github/workflows/control-begin-preflight.yml new file mode 100644 index 0000000..f5e14a5 --- /dev/null +++ b/.github/workflows/control-begin-preflight.yml @@ -0,0 +1,34 @@ +name: Cloud begin transition preflight +on: + pull_request: + paths: + - '.github/workflows/control-begin-preflight.yml' + - 'tools/cloud_begin_preflight.py' +permissions: + contents: read +jobs: + preflight: + runs-on: ubuntu-latest + timeout-minutes: 8 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Validate control state and manifest + run: | + python tools/validate_control_state.py + python tools/validate_execution_manifest.py + python scripts/select_batch.py > /tmp/selection.json + python -c 'import json; s=json.load(open("/tmp/selection.json")); assert [p["id"] for p in s["batch"]] == list(range(21,41)); assert s["batch_count"] == 20' + - name: Derive lifecycle transition and protected CAS plan + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + NOW_IST: '2026-10-12T04:02:04+05:30' + EXPIRES_IST: '2026-10-12T12:00:00+05:30' + LEASE_OWNER: 'cloud-20261012-040204-a21f9c' + REQUEST_ID: 'begin-20261012-040204-a21f9c' + RUN_REFERENCE: 'OMP-021-040-20261012' + run: python tools/cloud_begin_preflight.py diff --git a/tools/cloud_begin_preflight.py b/tools/cloud_begin_preflight.py new file mode 100644 index 0000000..e77b70c --- /dev/null +++ b/tools/cloud_begin_preflight.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +import base64,json,os,subprocess,sys +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] +sys.path.insert(0,str(ROOT)) +from tools.batch_lifecycle import transition,digest +from tools.control_persistence import prepare,READ_PATHS,LEASE,blob_sha + +base=os.environ["BASE_SHA"] +def git(*args): + return subprocess.check_output(["git",*args],text=True).strip() +def read_at(path): + return subprocess.check_output(["git","show",f"{base}:{path}"]).decode("utf-8") +def parsed(path): + import csv,io + content=read_at(path) + return list(csv.DictReader(io.StringIO(content),strict=True)) if path.endswith(".csv") else json.loads(content) + +state=parsed(LEASE) +roadmap=parsed("roadmap-projects.json") +manifest=parsed("state/execution-manifest-20261011-ids021-040.json") +context={ + "roadmap":roadmap, + "historicalCompletions":parsed("state/completion-verifications.json"), + "manifest":manifest, + "preflight":parsed("state/preflight-20261010-ids021-040.json"), + "plans":manifest["projects"], + "durableReports":[parsed("state/run-20261010-batch001-020.json")], + "authoritativeEvidence":[] +} +selected=list(range(21,41)) +request={ + "action":"begin", + "expectedStateDigest":digest(state), + "expectedContextDigest":digest(context), + "expectedRevision":0, + "nowIST":os.environ["NOW_IST"], + "expiresAtIST":os.environ["EXPIRES_IST"], + "owner":os.environ["LEASE_OWNER"], + "requestId":os.environ["REQUEST_ID"], + "runReference":os.environ["RUN_REFERENCE"], + "selectedIDs":selected, + "selectorOutput":selected +} +result=transition(state,context,request) +content=json.dumps(result["state"],indent=2,ensure_ascii=False)+"\n" +old_content=read_at(LEASE) +old_sha=git("rev-parse",f"{base}:{LEASE}") +change={ + "path":LEASE, + "expectedBlobSHA":old_sha, + "expectedDigest":digest(state), + "expectedRevision":0, + "content":content +} +operation={ + "schemaVersion":1,"owner":"OpenMakerProjects","repo":"roadmap-cloud-automation","branch":"main", + "expectedMainSHA":base, + "message":"Begin cloud batch IDs 021-040 for 2026-10-12", + "transition":{"state":state,"context":context,"request":request}, + "changes":[change] +} +paths=set(READ_PATHS)|{LEASE} +files={} +for path in paths: + raw=read_at(path) + entry=git("ls-tree",base,"--",path).split() + files[path]={"sha":entry[2],"mode":entry[0],"content":raw} +snapshot={"owner":"OpenMakerProjects","repo":"roadmap-cloud-automation","branch":"main", + "refSHA":base,"treeSHA":git("show","-s","--format=%T",base),"files":files} +plan=prepare(operation,snapshot) +payload={"result":result,"operation":operation,"plan":plan} +encoded=base64.b64encode(json.dumps(payload,separators=(",",":"),ensure_ascii=False).encode()).decode() +print("BEGIN_RESULT_B64="+encoded) From e7986348e774aff5fbcf43ae4b07518042c1ac18 Mon Sep 17 00:00:00 2001 From: shrutik pradip balwan Date: Mon, 12 Oct 2026 04:12:22 +0530 Subject: [PATCH 2/2] Bound cloud preflight output --- tools/cloud_begin_preflight.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/cloud_begin_preflight.py b/tools/cloud_begin_preflight.py index e77b70c..a90b063 100644 --- a/tools/cloud_begin_preflight.py +++ b/tools/cloud_begin_preflight.py @@ -68,7 +68,10 @@ def parsed(path): files[path]={"sha":entry[2],"mode":entry[0],"content":raw} snapshot={"owner":"OpenMakerProjects","repo":"roadmap-cloud-automation","branch":"main", "refSHA":base,"treeSHA":git("show","-s","--format=%T",base),"files":files} +print("TRANSITION_OK stateDigest="+result["stateDigest"],flush=True) plan=prepare(operation,snapshot) -payload={"result":result,"operation":operation,"plan":plan} +print("PERSISTENCE_PLAN_OK expectedMainSHA="+plan["expectedMainSHA"]+" newBlobSHA="+plan["changes"][0]["blobSHA"],flush=True) +payload={"result":result,"expectedMainSHA":base,"expectedBlobSHA":old_sha, + "newBlobSHA":plan["changes"][0]["blobSHA"],"treeSHA":snapshot["treeSHA"]} encoded=base64.b64encode(json.dumps(payload,separators=(",",":"),ensure_ascii=False).encode()).decode() -print("BEGIN_RESULT_B64="+encoded) +print("BEGIN_RESULT_B64="+encoded,flush=True)