Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
a998d66
phase7q: add model artifact identity preflight
oigorbrito Sep 30, 2026
437d17c
phase7q: open scaffold-model recovery decision stream
oigorbrito Sep 30, 2026
965309c
phase7q: define scaffold-model pair recovery boundary
oigorbrito Sep 30, 2026
e5a3a4e
phase7q: bind qualified L1 artifact and authorize Q1
oigorbrito Sep 30, 2026
f25156f
phase7q: parameterize frozen model identity
oigorbrito Sep 30, 2026
c9062cf
phase7q: complete model identity parameterization
oigorbrito Sep 30, 2026
6bff0c8
phase7q: add S4-L1-Q1 pair qualification cell
oigorbrito Sep 30, 2026
306d974
phase7q: point roadmap at S4-L1-Q1
oigorbrito Sep 30, 2026
731c7cd
phase7q: add Windows application-control diagnostic
oigorbrito Sep 30, 2026
8276a1a
phase7q: record Q1 application-control environment failure
oigorbrito Sep 30, 2026
80b01a6
phase7q: gate Q1 retry on Code Integrity diagnosis
oigorbrito Sep 30, 2026
3ddfb6a
phase7q: attribute Q1 block to Windows Code Integrity
oigorbrito Sep 30, 2026
e93e5c9
phase7q: add frozen runtime signature preflight
oigorbrito Sep 30, 2026
ec626e6
phase7q: gate Q1 on frozen runtime signature preflight
oigorbrito Sep 30, 2026
456f3aa
phase7q: record frozen runtime signature state
oigorbrito Sep 30, 2026
250f714
phase7q: add frozen runtime provenance preflight
oigorbrito Sep 30, 2026
3f12010
phase7q: gate Q1 on runtime provenance preflight
oigorbrito Sep 30, 2026
8e103a7
phase7q: record retained b11205 package provenance
oigorbrito Sep 30, 2026
91ff805
phase7q: add runtime archive member linkage check
oigorbrito Sep 30, 2026
d872e4c
phase7q: gate Q1 on archive member linkage
oigorbrito Sep 30, 2026
d5fc246
phase7q: establish retained runtime byte identity
oigorbrito Sep 30, 2026
392ea73
phase7q: add Windows application-control posture diagnostic
oigorbrito Sep 30, 2026
5172daf
phase7q: gate Q1 on policy-owner attribution
oigorbrito Sep 30, 2026
a874cfa
phase7q: constrain application-control posture output
oigorbrito Sep 30, 2026
3a32a07
phase7q: record posture diagnostic output defect
oigorbrito Sep 30, 2026
9241084
phase7q: attribute Q1 blocker to Smart App Control
oigorbrito Sep 30, 2026
71549d9
phase7q: add read-only CiTool policy inventory
oigorbrito Sep 30, 2026
0a167ab
phase7q: gate Q1 on CiTool policy inventory
oigorbrito Sep 30, 2026
8ddb4be
phase7q: classify CiTool access denied
oigorbrito Sep 30, 2026
0889bf4
phase7q: record CiTool privilege gate
oigorbrito Sep 30, 2026
511a94e
phase7q: record elevated CiTool access denial
oigorbrito Sep 30, 2026
c4b3a13
phase7q: add direct CiTool elevation diagnostic
oigorbrito Sep 30, 2026
523495c
phase7q: gate Q1 on direct CiTool diagnostic
oigorbrito Sep 30, 2026
7f1643d
phase7q: classify effective elevation state
oigorbrito Sep 30, 2026
50c8264
phase7q: record effective non-elevated token
oigorbrito Sep 30, 2026
fd31981
phase7q: gate Q1 on effective high-integrity CiTool rerun
oigorbrito Sep 30, 2026
ee9565b
phase7q: establish high-integrity Smart App Control inventory
oigorbrito Sep 30, 2026
bb9a822
phase7q: add Authenticode signing capability preflight
oigorbrito Sep 30, 2026
67bae37
phase7q: gate Q1 on signing capability preflight
oigorbrito Sep 30, 2026
b2bbf17
phase7q: record absent local signing capability
oigorbrito Sep 30, 2026
3210a5d
phase7q: block on environment remediation decision
oigorbrito Sep 30, 2026
8c04bb6
phase7q: authorize signed-runtime remediation treatment
oigorbrito Sep 30, 2026
9091c68
phase7q: freeze b11295 remediation candidate
oigorbrito Sep 30, 2026
61a7da1
phase7q: add b11295 remediation candidate preflight
oigorbrito Sep 30, 2026
e354953
phase7q: authorize b11295 remediation preflight
oigorbrito Sep 30, 2026
154790a
phase7q: record b11295 identity and signature preflight
oigorbrito Sep 30, 2026
8db86a9
phase7q: add b11295 isolated execution preflight
oigorbrito Sep 30, 2026
8c4cacc
phase7q: authorize b11295 isolated execution preflight
oigorbrito Sep 30, 2026
6579370
phase7q: record b11295 isolated execution failure
oigorbrito Sep 30, 2026
8668bbf
phase7q: add broad b11295 Code Integrity capture
oigorbrito Sep 30, 2026
55c2a4d
phase7q: gate R1 on broad Code Integrity attribution
oigorbrito Sep 30, 2026
14a3f59
phase7q: fix broad Code Integrity capture filter
oigorbrito Sep 30, 2026
0f26ca3
phase7q: record Code Integrity capture script defect
oigorbrito Sep 30, 2026
f6eb6fd
phase7q: add consolidated R1 closure collector
oigorbrito Sep 30, 2026
079129e
phase7q: define consolidated R1 closure evidence
oigorbrito Sep 30, 2026
09d93cf
phase7q: consolidate R1 closure gate
oigorbrito Sep 30, 2026
1f6a2c4
phase7q: close R1 as Smart App Control incompatible
oigorbrito Sep 30, 2026
95248a5
phase7q: close R1 remediation cell
oigorbrito Sep 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
964 changes: 964 additions & 0 deletions docs/decisions/0169-phase7q-scaffold-model-pair-recovery.md

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ PHASE 5 CodePro local plumbing COMPLETE
PHASE 6 Execution/verifier plumbing COMPLETE
PHASE 7 Scaffold compatibility COMPLETE
PHASE 7R S4 runtime requalification COMPLETE
PHASE 7Q Scaffold/model pair recovery IN_PROGRESS
PHASE 8 Scaffold screen NOT_STARTED
PHASE 9 Scaffold pruning NOT_STARTED
PHASE 10 Model screen NOT_STARTED
Expand All @@ -619,8 +620,21 @@ PHASE 19 Cloud tier DEFERRED
Current task:

```text
PHASE 7R = S4 RUNTIME REQUALIFICATION
ACTIVE_PR = #91
STATUS = COMPLETE / NO_COMPATIBLE_SURVIVOR
PHASE 7Q = SCAFFOLD/MODEL PAIR QUALIFICATION RECOVERY
STATUS = BLOCKED / R1_CLOSED_SMART_APP_CONTROL_INCOMPATIBLE
PHASE 8 = BLOCKED_BY_NO_COMPATIBLE_SCAFFOLD
NEXT_EXPERIMENTAL_CELL = NOT_AUTHORIZED
```


Phase7Q environment remediation R1 closure:

```text
R1_RESULT = INCOMPATIBLE
R1_PRIMARY = SMART_APP_CONTROL_BLOCKS_B11295_RUNTIME_DEPENDENCY_MTMD_DLL
R1_SECONDARY = OFFICIAL_B11295_WINDOWS_RUNTIME_IS_UNSIGNED_AND_NOT_AUTHORIZED_BY_CURRENT_VERIFIED_AND_REPUTABLE_POLICY
R1_ACCEPTANCE = FAILED
R1_PROMOTION = NOT_AUTHORIZED
NEXT_EXPERIMENTAL_CELL = NOT_AUTHORIZED
PHASE8 = BLOCKED_BY_NO_COMPATIBLE_SCAFFOLD_RUNTIME_PATH
```
37 changes: 22 additions & 15 deletions tools/run_phase7_s4_openhands.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ def sha256(path:Path)->str:
for chunk in iter(lambda:h.read(1024*1024),b""): d.update(chunk)
return d.hexdigest().upper()

def summary_base()->dict[str,Any]:
def summary_base(*, model_alias:str=MODEL_ALIAS)->dict[str,Any]:
return {"schema_version":1,"phase":7,"candidate":"S4","scaffold":"OpenHands Agent Canvas","version":VERSION,"commit":COMMIT,
"agent_server_version":AGENT_SERVER_VERSION,"automation_version":AUTOMATION_VERSION,"classification":"BLOCKED_UNCLASSIFIED",
"local_runtime":{"base_url":LLAMA_BASE,"model_alias":MODEL_ALIAS,"fallback":"DISABLED"},
"local_runtime":{"base_url":LLAMA_BASE,"model_alias":model_alias,"fallback":"DISABLED"},
"native_contract":"Agent Canvas request builder -> Agent Server 1.49.3 -> OpenHands agent tools -> isolated repository -> CodePro verifier",
"gates":{},"telemetry":{},"vertical":None,"adapter":None,"selected_scaffold":False,"executor_promotion":"NOT_AUTHORIZED"}

Expand Down Expand Up @@ -119,10 +119,17 @@ def ensure_install(evidence:Path,summary:dict[str,Any])->bool:
if not vite.is_file(): summary["blocker"]={"stage":"vite_node","detail":str(vite)}; return False
return True

def find_model()->Path:
def find_model(*, model_path:str|None=None, model_bytes:int=MODEL_BYTES, model_sha256:str=MODEL_SHA256)->Path:
expected_sha=model_sha256.upper()
if model_path is not None:
p=Path(model_path).expanduser().resolve()
if not p.is_file(): raise RuntimeError(f"frozen model artifact not found: {p}")
if p.stat().st_size!=model_bytes: raise RuntimeError(f"frozen model byte-size mismatch: {p}")
if sha256(p)!=expected_sha: raise RuntimeError(f"frozen model SHA256 mismatch: {p}")
return p
for p in (RUNTIME_ROOT/"models"/"phase4").rglob("*.gguf"):
if p.stat().st_size==MODEL_BYTES and sha256(p)==MODEL_SHA256: return p.resolve()
raise RuntimeError("frozen L3 artifact not found by SHA256")
if p.stat().st_size==model_bytes and sha256(p)==expected_sha: return p.resolve()
raise RuntimeError("frozen model artifact not found by SHA256")

def find_server()->Path:
matches=list((RUNTIME_ROOT/"downloads"/"llama-b11205-bin-win-cuda-13.4-x64").rglob("llama-server.exe"))
Expand All @@ -140,13 +147,13 @@ def get_json(url:str,headers:dict[str,str]|None=None,timeout:float=3)->Any:
req=Request(url,headers=headers or {})
with urlopen(req,timeout=timeout) as r: return json.loads(r.read().decode("utf-8"))

def wait_llama(process:subprocess.Popen[Any],timeout_seconds:int=120)->dict[str,Any]:
def wait_llama(process:subprocess.Popen[Any],model_alias:str=MODEL_ALIAS,timeout_seconds:int=120)->dict[str,Any]:
deadline=time.monotonic()+timeout_seconds; last=""
while time.monotonic()<deadline:
if process.poll() is not None: raise RuntimeError(f"llama-server exited early: {process.returncode}")
try:
health=get_json(f"{LLAMA_BASE}/health"); models=get_json(f"{LLAMA_BASE}/models"); ids=[x.get("id") for x in models.get("data",[]) if isinstance(x,dict)]
if health.get("status")=="ok" and MODEL_ALIAS in ids: return {"health":health,"models":models}
if health.get("status")=="ok" and model_alias in ids: return {"health":health,"models":models}
except Exception as exc: last=f"{type(exc).__name__}: {exc}"
time.sleep(.5)
raise RuntimeError(f"llama readiness timeout: {last}")
Expand Down Expand Up @@ -226,10 +233,10 @@ def classify(vertical:dict[str,Any],adapter:dict[str,Any],gates:dict[str,Any])->
return "INCOMPATIBLE"

def main(argv:list[str]|None=None)->int:
parser=argparse.ArgumentParser(description=__doc__); parser.add_argument("--evidence-dir",required=True); parser.add_argument("--llama-parallel",type=int,default=None); parser.add_argument("--llama-context",type=int,default=4096); parser.add_argument("--litellm-version",default=None); parser.add_argument("--completion-log-dir",default=None); parser.add_argument("--study-id",default="phase7"); parser.add_argument("--conversation-timeout-seconds",type=int,default=240); parser.add_argument("--executor-timeout-seconds",type=int,default=280); parser.add_argument("--vertical-wall-time-seconds",type=int,default=320); parser.add_argument("--platform-contract",choices=("windows-powershell-v1","windows-powershell-v2"),default=None); parser.add_argument("--system-prompt-profile",choices=("windows-minimal-v1","windows-embedded-v1"),default=None); parser.add_argument("--conversation-worktree",choices=("true","false"),default="true"); parser.add_argument("--cleanup-untracked-python-bytecode",choices=("true","false"),default="false"); parser.add_argument("--max-iterations",type=int,default=8); args=parser.parse_args(argv)
parser=argparse.ArgumentParser(description=__doc__); parser.add_argument("--evidence-dir",required=True); parser.add_argument("--llama-parallel",type=int,default=None); parser.add_argument("--llama-context",type=int,default=4096); parser.add_argument("--litellm-version",default=None); parser.add_argument("--completion-log-dir",default=None); parser.add_argument("--study-id",default="phase7"); parser.add_argument("--conversation-timeout-seconds",type=int,default=240); parser.add_argument("--executor-timeout-seconds",type=int,default=280); parser.add_argument("--vertical-wall-time-seconds",type=int,default=320); parser.add_argument("--platform-contract",choices=("windows-powershell-v1","windows-powershell-v2"),default=None); parser.add_argument("--system-prompt-profile",choices=("windows-minimal-v1","windows-embedded-v1"),default=None); parser.add_argument("--conversation-worktree",choices=("true","false"),default="true"); parser.add_argument("--cleanup-untracked-python-bytecode",choices=("true","false"),default="false"); parser.add_argument("--max-iterations",type=int,default=8); parser.add_argument("--model-path",default=None); parser.add_argument("--model-bytes",type=int,default=MODEL_BYTES); parser.add_argument("--model-sha256",default=MODEL_SHA256); parser.add_argument("--model-alias",default=MODEL_ALIAS); args=parser.parse_args(argv)
evidence=Path(args.evidence_dir).expanduser().resolve()
if evidence.exists() and any(evidence.iterdir()): raise SystemExit(f"refusing to overwrite non-empty evidence directory: {evidence}")
evidence.mkdir(parents=True,exist_ok=True); summary=summary_base(); summary["study_id"]=args.study_id
evidence.mkdir(parents=True,exist_ok=True); summary=summary_base(model_alias=args.model_alias); summary["study_id"]=args.study_id
llama_proc=None; canvas_proc=None; isolated=None
try:
if not ensure_upstream(evidence,summary): return block(evidence,summary,"BLOCKED_UPSTREAM_IDENTITY","upstream",summary.get("blocker"))
Expand All @@ -242,9 +249,9 @@ def main(argv:list[str]|None=None)->int:
if args.max_iterations < 1: raise ValueError("--max-iterations must be >= 1")
summary["timeouts"]={"conversation_poll_seconds":args.conversation_timeout_seconds,"executor_process_seconds":args.executor_timeout_seconds,"vertical_wall_seconds":args.vertical_wall_time_seconds}
summary["max_iterations_requested"]=args.max_iterations
assert_ports(); model=find_model(); server=find_server()
assert_ports(); model=find_model(model_path=args.model_path,model_bytes=args.model_bytes,model_sha256=args.model_sha256); server=find_server()
if args.llama_context < 1: raise ValueError("--llama-context must be >= 1")
llama_argv=[str(server),"-m",str(model),"--device","CUDA0","-ngl","99","-c",str(args.llama_context),"--host","127.0.0.1","--port",str(LLAMA_PORT),"--alias",MODEL_ALIAS]
llama_argv=[str(server),"-m",str(model),"--device","CUDA0","-ngl","99","-c",str(args.llama_context),"--host","127.0.0.1","--port",str(LLAMA_PORT),"--alias",args.model_alias]
if args.llama_parallel is not None:
if args.llama_parallel < 1: raise ValueError("--llama-parallel must be >= 1")
llama_argv += ["--parallel",str(args.llama_parallel)]
Expand All @@ -257,10 +264,10 @@ def main(argv:list[str]|None=None)->int:
if effective_context != args.llama_context:
raise RuntimeError(f"llama context binding mismatch: declared={args.llama_context} effective={effective_context}")
summary["local_runtime"]["context_size"]=effective_context
write_json(evidence/"runtime-binding.json",{"llama_cpp_build":LLAMA_BUILD,"llama_cpp_commit":LLAMA_COMMIT,"server_argv":llama_argv,"base_url":LLAMA_BASE,"model_alias":MODEL_ALIAS,"model_artifact":str(model),"model_bytes":model.stat().st_size,"model_sha256":sha256(model),"context_size":effective_context,"parallel":args.llama_parallel,"fallback":"DISABLED"})
write_json(evidence/"runtime-binding.json",{"llama_cpp_build":LLAMA_BUILD,"llama_cpp_commit":LLAMA_COMMIT,"server_argv":llama_argv,"base_url":LLAMA_BASE,"model_alias":args.model_alias,"model_artifact":str(model),"model_bytes":model.stat().st_size,"model_sha256":sha256(model),"context_size":effective_context,"parallel":args.llama_parallel,"fallback":"DISABLED"})
llama_out=(evidence/"llama-server-stdout.txt").open("w",encoding="utf-8",newline="\n"); llama_err=(evidence/"llama-server-stderr.txt").open("w",encoding="utf-8",newline="\n")
llama_proc=subprocess.Popen(llama_argv,cwd=server.parent,stdout=llama_out,stderr=llama_err,text=True,shell=False)
write_json(evidence/"llama-preflight.json",wait_llama(llama_proc)); summary["gates"]["local_runtime_ready"]=True
write_json(evidence/"llama-preflight.json",wait_llama(llama_proc,args.model_alias)); summary["gates"]["local_runtime_ready"]=True
with tempfile.TemporaryDirectory(prefix="codepro-phase7-s4-",dir=S4_ROOT,ignore_cleanup_errors=True) as tmp:
base=Path(tmp); source=base/"source"; workspaces=base/"workspaces"; state=base/"canvas-state"; profile=base/"profile"; source.mkdir(); state.mkdir(); profile.mkdir()
(profile/"AppData"/"Local").mkdir(parents=True); (profile/"AppData"/"Roaming").mkdir(parents=True); (profile/".cache").mkdir(parents=True)
Expand Down Expand Up @@ -297,7 +304,7 @@ def main(argv:list[str]|None=None)->int:
write_json(evidence/"canvas-preflight.json",wait_canvas(canvas_proc)); summary["gates"]["agent_canvas_ready"]=True
result_path=evidence/"openhands-result.json"
issue="Work only in the current repository. Inspect value.py and confirm it contains VALUE = \"before\". Change it to VALUE = \"after\". Then run a Python command that imports value and asserts value.VALUE == \"after\". Finish after the check succeeds."
executor=[sys.executable,str(ROOT/"tools"/"phase7_openhands_executor.py"),"--upstream",str(UPSTREAM),"--backend-url",CANVAS_BASE,"--api-key",SESSION_KEY,"--working-dir",str(isolated.workspace),"--llm-base-url",LLAMA_BASE,"--model-alias",MODEL_ALIAS,"--result",str(result_path),"--issue",issue,"--poll-timeout-seconds",str(args.conversation_timeout_seconds),"--process-timeout-seconds",str(args.executor_timeout_seconds),"--conversation-worktree",args.conversation_worktree,"--cleanup-untracked-python-bytecode",args.cleanup_untracked_python_bytecode,"--max-iterations",str(args.max_iterations)]
executor=[sys.executable,str(ROOT/"tools"/"phase7_openhands_executor.py"),"--upstream",str(UPSTREAM),"--backend-url",CANVAS_BASE,"--api-key",SESSION_KEY,"--working-dir",str(isolated.workspace),"--llm-base-url",LLAMA_BASE,"--model-alias",args.model_alias,"--result",str(result_path),"--issue",issue,"--poll-timeout-seconds",str(args.conversation_timeout_seconds),"--process-timeout-seconds",str(args.executor_timeout_seconds),"--conversation-worktree",args.conversation_worktree,"--cleanup-untracked-python-bytecode",args.cleanup_untracked_python_bytecode,"--max-iterations",str(args.max_iterations)]
summary["conversation_worktree"]={"openhands_nested":args.conversation_worktree=="true","codepro_isolated_workspace":True}
summary["workspace_ephemeral_cleanup_policy"]="untracked-python-bytecode-only-v1" if args.cleanup_untracked_python_bytecode=="true" else None
if args.platform_contract is not None:
Expand Down Expand Up @@ -341,7 +348,7 @@ def main(argv:list[str]|None=None)->int:
else:
system_prompt_profile_observed=False
max_iterations_observed=(adapter.get("conversation_info") or {}).get("max_iterations")==args.max_iterations if isinstance(adapter.get("conversation_info"),dict) else False
summary["gates"].update({"max_iterations_observed":max_iterations_observed,"explicit_local_binding":(adapter.get("llm_binding") or {}).get("base_url")==LLAMA_BASE and (adapter.get("llm_binding") or {}).get("model")==f"openai/{MODEL_ALIAS}","platform_contract_observed":platform_contract_observed,"system_prompt_profile_observed":system_prompt_profile_observed,"model_calls_observed":model_call_observed(adapter),"inspect_observed":obs.get("inspectObserved") is True,"command_observed":obs.get("commandObserved") is True,"event_edit_signal":obs.get("editObserved") is True,"edit_observed":len(vertical.get("changed_files") or [])>0,"termination_observed":str(adapter.get("execution_status") or "").lower() in {"finished","error","stuck","stopped"},"patch_captured":patch_ok,"independent_verifier":verifier_ok,"source_repository_unchanged":source_state.get("clean") is True,"workspace_cleanup":cleanup.removed})
summary["gates"].update({"max_iterations_observed":max_iterations_observed,"explicit_local_binding":(adapter.get("llm_binding") or {}).get("base_url")==LLAMA_BASE and (adapter.get("llm_binding") or {}).get("model")==f"openai/{args.model_alias}","platform_contract_observed":platform_contract_observed,"system_prompt_profile_observed":system_prompt_profile_observed,"model_calls_observed":model_call_observed(adapter),"inspect_observed":obs.get("inspectObserved") is True,"command_observed":obs.get("commandObserved") is True,"event_edit_signal":obs.get("editObserved") is True,"edit_observed":len(vertical.get("changed_files") or [])>0,"termination_observed":str(adapter.get("execution_status") or "").lower() in {"finished","error","stuck","stopped"},"patch_captured":patch_ok,"independent_verifier":verifier_ok,"source_repository_unchanged":source_state.get("clean") is True,"workspace_cleanup":cleanup.removed})
summary["repository_state"]={"initial_revision":revision,"final":final_state,"source":source_state}; summary["classification"]=classify(vertical,adapter,summary["gates"])
write_json(evidence/"s4-summary.json",summary); print(json.dumps(summary,ensure_ascii=False,indent=2,sort_keys=True)); return 0
except Exception as exc:
Expand Down
86 changes: 86 additions & 0 deletions tools/run_phase7q_authenticode_signing_capability_preflight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""Read-only preflight for local Authenticode signing capability."""

from __future__ import annotations

import json
import subprocess


PS = r"""
$ErrorActionPreference = 'SilentlyContinue'

function Test-Chain($cert) {
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$ok = $chain.Build($cert)
[pscustomobject]@{
ok = [bool]$ok
status = @($chain.ChainStatus | ForEach-Object { $_.Status.ToString() })
}
}

$stores = @('Cert:\CurrentUser\My', 'Cert:\LocalMachine\My')
$certs = foreach($store in $stores) {
if(Test-Path $store) {
Get-ChildItem $store | ForEach-Object {
$c = $_
$ekuOids = @($c.EnhancedKeyUsageList | ForEach-Object { $_.ObjectId.Value })
if($ekuOids -contains '1.3.6.1.5.5.7.3.3') {
$chain = Test-Chain $c
[pscustomobject]@{
store = $store
thumbprint = $c.Thumbprint
has_private_key = [bool]$c.HasPrivateKey
not_before = $c.NotBefore.ToUniversalTime().ToString('o')
not_after = $c.NotAfter.ToUniversalTime().ToString('o')
chain_ok = [bool]$chain.ok
chain_status = @($chain.status)
}
}
}
}
}

$signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue
if(-not $signtool) {
$candidates = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Filter signtool.exe -Recurse -File -ErrorAction SilentlyContinue |
Sort-Object FullName -Descending
$signtoolPaths = @($candidates | ForEach-Object { $_.FullName })
} else {
$signtoolPaths = @($signtool.Source)
}

[pscustomobject]@{
code_signing_certificates = @($certs)
usable_code_signing_certificate_count = @($certs | Where-Object { $_.has_private_key -and $_.chain_ok -and ([datetime]$_.not_after -gt [datetime]::UtcNow) }).Count
signtool_paths = @($signtoolPaths)
} | ConvertTo-Json -Depth 6 -Compress
"""

def main() -> int:
p = subprocess.run(
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", PS],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
check=False,
)
payload: dict[str, object] = {
"schema_version": 1,
"operation": "phase7q-authenticode-signing-capability-preflight",
"modifies_files": False,
"modifies_policy": False,
"returncode": p.returncode,
"stderr": p.stderr,
}
if p.stdout.strip():
try:
payload["result"] = json.loads(p.stdout)
except json.JSONDecodeError:
payload["raw_stdout"] = p.stdout
print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
return 0 if p.returncode == 0 else p.returncode

if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading