From 6908ab3b501a801f18016bb33f04f71b08d1e503 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Tue, 15 Sep 2026 23:30:07 +0000 Subject: [PATCH] fix(validation): retain the resolved Core Csc failure in history Accept the one current-bootstrap absence observation and preserve the original 0056 failure with its consumed build/test unit in the existing handoff and current readers. Keep ordinary Windows capacity and final graph gates intact. Separate current-reader admission from the original 0055 reader-source pins without changing the historical validator or original receipt flags. Refs: #108 --- .../experiments/windows-slice-validation.md | 168 ++++++++++++++++-- tools/validation/final_publish_contracts.py | 157 +++++++++++++++- tools/validation/run_managed.py | 155 +++++++++++++++- tools/validation/run_windows.py | 164 ++++++++++++++++- 4 files changed, 612 insertions(+), 32 deletions(-) diff --git a/docs/research/experiments/windows-slice-validation.md b/docs/research/experiments/windows-slice-validation.md index 641e976c..28b08889 100644 --- a/docs/research/experiments/windows-slice-validation.md +++ b/docs/research/experiments/windows-slice-validation.md @@ -5580,15 +5580,18 @@ consent effect, resource request, deployment or support commitment is admitted. A later complete final caller uses `final-publish-after-guard-handoff-v2` while retaining the six fields `schema`, `source`, `histories`, `recomputedCounters`, `knownEndpoints` and `guardAction`. Ordinary entries retain the existing complete -entry shape and evidence checks. The sole closed exception is Windows action -0054, represented by exactly `number` and `failedGuardDisposition`. Its number +entry shape and evidence checks. Windows action +0054 has its closed exception, represented by exactly `number` and +`failedGuardDisposition`. Its number is `0054`; its descriptor equals the original successor authority's exact failed history disposition. No other platform, action number, extra field or alternate failure may use this variant. It does not assert a complete 0054 tree inventory. Preserve the accepted post-0053 manifest and acceptance bytes. The later handoff contains the exact original 45 Linux and 53 Windows entries, followed only on -Windows by disposed 0054 and independently accepted successful 0055. Source-bound +Windows by disposed 0054, independently accepted successful 0055, and the exact +[disposed 0056 variant](#exact-0056-failed-history-disposition). Preserve the complete +accepted post-0055 handoff bytes as the predecessor of that added suffix. Source-bound hashes of the compact, ordered original entry lists enforce that prefix without another provenance input. The handoff reviewer verifies their derivation from the unchanged accepted manifest. The paired action order remains contiguous; reject @@ -5700,7 +5703,9 @@ charge from its accepted disposition. Count both guard preparations once. Exact post-0055 product counters are Linux `[8,37,0,0]` and Windows `[7,48,0,48]`; preparation ceilings remain Linux 9, Windows 7 and combined 16. Preserve the external singleton fixture debit in every relevant combined-capacity check: -`37+48+1=86/120`, leaving 34. A final publication charges zero preparation and +`37+48+1=86/120` at that post-0055 boundary. The later dedicated 0056 charge raises +the current combined count to 87/120 as specified by its +[exact disposition](#exact-0056-failed-history-disposition). A final publication charges zero preparation and build/test, one publish and zero synthetic process scenarios. There is no refund, extra guard or new fixture/product reservation. @@ -7085,8 +7090,9 @@ that completion remains unknown. Releasing the local lease is not evidence that receipts completed. A fresh original deadline/cancellation check after all local persistence and lock/handler finalization decides the original caller's success. A provisional success-looking receipt cannot override a late, nonzero, cancelled or -uncollected original invocation. Any incomplete pair blocks subsequent work; retain -owned partial state without speculative cleanup or a replacement receipt. +uncollected original invocation. An incomplete pair blocks subsequent work until +its separately accepted exact failure disposition; retain owned partial state +without speculative cleanup or a replacement receipt. ### Dedicated Allocation and Later Consumers @@ -7107,9 +7113,10 @@ including failed start, consumes the unit; a partial reservation blocks continua Use the existing paired history/handoff carrier, with no second ledger. The prospective ordinary readers fail closed when they encounter this observer, -including a provisional successful receipt. Independent acceptance of the original -outcome must update the existing handoff and its current consumers in the same change -before later publication or validation. Preserve all old receipts, flags, historical +including a provisional successful receipt, except for the subsequently accepted +[exact failed-0056 disposition](#exact-0056-failed-history-disposition). That disposition +updates the existing handoff and its current consumers together before later +publication or validation. Preserve all old receipts, flags, historical 48/80 limits and dispositions. A failed observer does not receive another unit. Before this action can execute, independently admit the complete current physical and @@ -7161,16 +7168,19 @@ The Windows result, clock-ready, clock-remaining and subject-start-attempt leave were absent at their individual observations; the cancellation leaf was a regular empty file by metadata. The three inspected pending leaves were also absent. These observations are not an atomic snapshot and do not independently prove that -no process started or that every process has exited. The exact underlying failure -and Windows/helper quiescence remain unresolved. The controller's outer admission +no process started or that every process has exited. At the initial recovery, the exact underlying failure +and Windows/helper quiescence remained unresolved. The later +[current-bootstrap result](#current-bootstrap-observation-outcome) resolves only +its stated current lifetime question. The controller's outer admission catch does not persist its exception, and the dispatcher discarded bootstrap output; the retained evidence cannot recreate those diagnostics. Both the observer and its initial recovery invocation are consumed. Preserve all original receipts, partial state and false graph, artifact, independent-observation and continuation flags. The exit-zero receipt copier remains ineligible. Current -history readers must continue to reject this incomplete pair; no accepted successor -handoff or continuation grant is created by this failure record. Before dependent +history readers rejected this incomplete pair before the later +[exact failed-history disposition](#exact-0056-failed-history-disposition). This original +failure record itself creates no successor handoff or continuation grant. Before dependent execution, resolve the outstanding lifetime evidence and accept the required bounded protocol and current-consumer changes. No observer retry, additional recovery, speculative cleanup, publication or account operation is authorized here. @@ -7294,3 +7304,135 @@ Retain the sanitized original transport and its review in the existing private execution carrier. Preserve original and partial artifacts without cleanup. This amendment does not refund the observer, rewrite original receipts or enable any other experiment. + +### Current Bootstrap Observation Outcome + +The sole query executed under accepted protocol commit +`a42b6a648d4dfe4dc542256b9a8cc3faf70e0bd7` and the exact tracked source above. +Its original tool invocation yielded once; collection of that same original session +completed with exit code zero. The complete combined output was one 160-byte JSON +line ending in CRLF: `status: absent`, `reason: complete`, `queryCompleted: true`, +zero rows, zero exact matches, zero ambiguous rows and `elapsedMs: 1630`. +The conservative Linux monotonic interval, including pre-call bookkeeping, +Windows startup and original completion collection, was exactly 2,687,223,000 ns +(2,687.223 ms), within the 30-second acceptance deadline. + +| Evidence role | Bytes | SHA-256 | +| --- | ---: | --- | +| Exact query invocation admission | 7,222 | `c6bf6399e9c7a719f2c9c6f90ef27b31375afb5c8d5693c53f5e9343c8cf25bf` | +| Complete original query transport | 1,920 | `629bf72891f0785693df27f36c798e8b293853705b2b82dbcc23317461d603cd` | +| Independent original outcome review | 6,267 | `6f83fab9ad93260677671763237d0e57e53f5a5096a896efc01a73e78b8a296e` | + +Independent review accepted the current absence of the exact original bootstrap. +Combined with the separately accepted old-source reachability exclusion, this +resolves the selected current bootstrap/subject lifetime question under the +existing ordinary runtime/provider trust. The bootstrap absence is a current +observation; the guarded-subject exclusion remains a source-qualified inference. +Neither conclusion recovers the original exception, observes historical no-launch, +proves future absence or establishes general host/provider quiescence. + +The observation consumed its only attempt. No query capacity remains, and this +result grants no retry, cleanup, account operation or SDK execution. No original +files were read or changed by the query. Preserve the failed observer's original +receipts, all false flags and its consumed build/test charge. Compiler graph, +artifact, final CLI/WSL and account evidence remain incomplete. + +## Exact 0056 Failed-History Disposition + +Accept only action 0056 as the original failed `core-csc-observer`, with its bounded +current lifetime resolved by the evidence above. This is a historical failure +variant for the existing readers and handoff; it does not accept the observer, +compiler graph, artifact or original Windows completion. Keep the exact original +WSL `RuntimeError`, `safetyStop: true`, `launchAttempted: true`, null proxy status +and all original false completion, quiescence and continuation flags. + +The existing `final-publish-after-guard-handoff-v2` carrier retains its six fields, +unchanged 45-Linux/53-Windows prefix, disposed 0054 and successful 0055. Append only +Windows entry `0056`, with exactly `number` and `failedObserverDisposition`. +The latter descriptor binds the private `core-csc-observer-failed-history-disposition-v1` +instance to the existing canonical protocol section, predecessor handoff, original +tool result, accepted recovery report and six copied originals, accepted source +exclusion, current-bootstrap transport and independent outcome review. The +canonical protocol/source revision is supplied by the enclosing exact admission; +the private evidence binding does not grant authority. Preserve the predecessor +manifest and acceptance bytes, and independently accept the new exact handoff +through its existing acceptance carrier before any consumer executes. + +The six known original roles are the paired starts, paired invocations, WSL result +and WSL controller-attempt record. Their exact sizes and hashes remain those in +the initial recovery table. The byte-identical starts and invocations, their +reservation joins, original product and protocol identities, previous counters +and fixed `[0,1,0,0]` charge identify this exact failure. No other action number, +platform, observer, reordered suffix or arbitrary failed receipt may use this +variant. Unbound, changed or incomplete required evidence rejects. + +### Prospective Fixed-Role Verification + +The private disposition instance is 3,202 bytes, SHA-256 +`6a241958bfd4693de219393c5920277e18ead52f8d039cd265f412837d142525`. +Each of the three current consumers retains an unbound +`CORE_CSC_FAILED_DISPOSITION_BINDING`; separately reviewed source materialization +must supply exactly that descriptor before use. Do not accept an alternate path, +CLI/environment override, different instance or arbitrary accepted Boolean. +The existing enclosing source/protocol and original-history acceptance gates remain. + +One ordinary-reader pass reads that fixed private instance and the six fixed +original roles, each for its exact length plus one byte. There are seven reads +requesting at most 15,093 bytes, including 11,890 requested original bytes. Paths +are source-fixed and direct; each walk checks at most sixteen ancestors. Require +regular files, exact sizes/hashes and stable device, inode, mode, size, mtime, ctime +and link count before/after reading and against the current leaf. No directory +listing, absent-leaf probe, additional original read, private historical-review +reread or Windows operation is selected by this verification. The seven fixed +paths require at most forty ancestor stat calls and twenty-one held/leaf identity +observations, sixty-one metadata calls per pass. + +The final publication caller uses the same seven-read check at its two existing +history checkpoints, for at most fourteen reads requesting 30,186 bytes and +122 metadata calls. Both +passes share thirty seconds of active verification time and require the same +current identities and bytes at the second checkpoint. The original caller's +outer deadline/cancellation remains in force through the gap and each check; +the earlier deadline always wins. A failed pass latches failure, and neither +consumer may restart the allowance or obtain a third pass. The ordinary reader's +single pass also has a thirty-second allowance. These checks add no experiment +reservation and borrow no time or reads from the unchanged 0054/0055 schedule. +They remain prospective until the complete enclosing source, schedule, handoff +and literal invocation have their separate exact admission. + +This failure variant does not assert a complete 0056 directory inventory or a +new snapshot of the old report's absence observations. In particular, the original +`windows-input.json`, staged payload inventories and current missing Windows +receipt are not needed to count and retain this resolved failure. Do not fabricate +their content or promote source-expected output to an observed file. Retain all +original and partial state without cleanup. Future fixed-role continuity checks +are separate current observations and cannot replace the failed original outcome. + +Count one observer build/test charge and zero preparation, publication or synthetic +charges. Current totals are Linux `[8,37,0,0]`, Windows `[7,49,0,48]` and combined +`37 + 49 + 1 = 87/120`, including the existing fixture exactly once. Ordinary Windows +build/test remains exhausted at 48; the aggregate 49 includes this sole dedicated +unit. Linux's prospective ceiling remains 79. The twelve final CLI scenarios, +preparation 15/16 and publication 0/12 are unchanged. Derive the next Windows number +from the admitted contiguous history; with this exact suffix it is 0057. This +number does not allocate or authorize another action. + +The final caller's second 0054 continuity checkpoint therefore requires reserved +action 0057 for this exact suffix. Its first checkpoint still has no reservation; +the original pass order, failure latch, evidence, continuity and budgets remain. + +Current consumers recognize the exact failed branch before their ordinary-success, +complete-inventory and Windows-completion checks, without rewriting those original +receipts. Final publication selects successful guard 0055 by its exact number rather +than assuming it is the last action. Preserve all 0054/0055 validators and their +original provenance, all other historical dispositions and the existing final +source, graph, K, artifact, publication and literal-execution admission gates. + +The ordinary wrappers validate their current executing source through their existing +current-source admission and actual `__file__` identity check. Their unchanged +historical 0055 validator receives the fixed retained original reader paths already +recognized by the final caller. It continues checking those original bytes against +the original 0055 authority. Passing a later current wrapper as though it were the +historical source would fail those pins before reaching 0056; do not repair that +mismatch by rewriting original pins, weakening the historical check or substituting +historical-source acceptance for current-source admission. diff --git a/tools/validation/final_publish_contracts.py b/tools/validation/final_publish_contracts.py index 7ca88d9b..8fc485f2 100644 --- a/tools/validation/final_publish_contracts.py +++ b/tools/validation/final_publish_contracts.py @@ -1500,6 +1500,131 @@ def names(path): return values +# Exact failed 0056 evidence; activation remains part of a reviewed future caller. +CORE_CSC_FAILED_DISPOSITION_BINDING = None +CORE_CSC_FAILED_DISPOSITION = { + "path": "/tmp/windows-core-csc-0056-failed-history-disposition-root-v1.json", + "bytes": 3202, + "sha256": "6a241958bfd4693de219393c5920277e18ead52f8d039cd265f412837d142525", +} +CORE_CSC_FAILED_ORIGINALS = { + "wsl-result": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/result.json", + "wsl-started": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/started.json", + "wsl-controller-attempt": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/controller-start-attempt.json", + "windows-started": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/started.json", + "wsl-invocation": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/invocation.json", + "windows-invocation": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/invocation.json", +} + + +def verify_disposed_core_csc_observer(state=None, deadline=None, cancelled=None): + """Read only the disposition and six fixed originals, never a complete tree. + + Each pass has seven content reads requesting at most 15,093 bytes. Final + publication uses two passes sharing 30 seconds and checks current continuity; + an ordinary history reader uses one pass. Historical absence stays historical. + """ + if CORE_CSC_FAILED_DISPOSITION_BINDING != CORE_CSC_FAILED_DISPOSITION: + raise ValueError("UNBOUND: exact failed 0056 disposition") + state = {} if state is None else state + if state.get("failed") or state.get("passes", 0) >= 2: + raise ValueError("Failed 0056 history verification cannot repeat") + began = time.monotonic() + remaining = state.get("remainingSeconds", 30.0) + end = began + remaining + if deadline is not None: + end = min(end, deadline) + state["passes"] = state.get("passes", 0) + 1 + state["failed"] = True + snapshot = {} + + def check(): + if cancelled is not None and cancelled(): + raise InterruptedError("Failed 0056 history verification cancelled") + if time.monotonic() >= end: + raise TimeoutError("Failed 0056 shared history deadline exhausted") + + def identity(info): + return (info.st_dev, info.st_ino, info.st_mode, info.st_size, + info.st_mtime_ns, info.st_ctime_ns, info.st_nlink) + + def fixed_read(path, expected): + path = Path(path) + if not path.is_absolute() or len(path.parts) > 17: + raise ValueError("Invalid fixed 0056 evidence path") + for parent in reversed(path.parents): + check() + if not stat.S_ISDIR(os.stat(parent, follow_symlinks=False).st_mode): + raise ValueError("Nonordinary 0056 evidence ancestor") + check() + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + before = os.fstat(fd) + if not stat.S_ISREG(before.st_mode) or before.st_size != expected["bytes"]: + raise ValueError("Failed 0056 evidence type or size changed") + check() + with os.fdopen(fd, "rb", closefd=False) as stream: + raw = stream.read(expected["bytes"] + 1) + check() + after = os.fstat(fd) + current = os.stat(path, follow_symlinks=False) + if (identity(before) != identity(after) or identity(after) != identity(current) or + len(raw) != expected["bytes"] or hashlib.sha256(raw).hexdigest() != expected["sha256"]): + raise ValueError("Failed 0056 evidence identity or bytes changed") + snapshot[str(path)] = identity(after) + return raw + finally: + os.close(fd) + + try: + disposition = json.loads(fixed_read( + CORE_CSC_FAILED_DISPOSITION["path"], CORE_CSC_FAILED_DISPOSITION)) + # The complete descriptor hash pins all accepted historical and lifetime + # evidence references. Only these six fixed originals are reread here. + if (disposition["schema"] != "core-csc-observer-failed-history-disposition-v1" or + disposition["actionNumber"] != "0056" or disposition["actionKind"] != "core-csc-observer" or + set(disposition["originalCopies"]) != set(CORE_CSC_FAILED_ORIGINALS)): + raise ValueError("Wrong exact failed 0056 disposition") + raw = {role: fixed_read(path, disposition["originalCopies"][role]) + for role, path in CORE_CSC_FAILED_ORIGINALS.items()} + if raw["wsl-started"] != raw["windows-started"] or raw["wsl-invocation"] != raw["windows-invocation"]: + raise ValueError("Failed 0056 original pairs changed") + started, result = json.loads(raw["wsl-started"]), json.loads(raw["wsl-result"]) + invocation, attempt = json.loads(raw["wsl-invocation"]), json.loads(raw["wsl-controller-attempt"]) + reservation_hash = hashlib.sha256(raw["wsl-started"]).hexdigest() + if (started["number"] != "0056" or started["action"] != "core-csc-observer" or + started["handoffSha256"] != disposition["baseHandoff"]["sha256"] or + started["priorCounters"] != {"linux": [8, 37, 0, 0], "windows": [7, 48, 0, 48]} or + [started[key] for key in ("preparationCharge", "buildTestCharge", "publishCharge", + "reservedProcessScenarios")] != [0, 1, 0, 0] or + any(value["reservationSha256"] != reservation_hash for value in (result, invocation, attempt)) or + result["invocationSha256"] != hashlib.sha256(raw["wsl-invocation"]).hexdigest() or + invocation["endpoint"] != started["endpoint"]): + raise ValueError("Failed 0056 reservation or original charge changed") + if (result["failureType"] != "RuntimeError" or result["outcome"] != "incomplete" or + result["proxyExitCode"] is not None or result["launchAttempted"] is not True or + result["safetyStop"] is not True or any(result[key] is not False for key in ( + "normalCompletion", "quiescent", "originalWindowsCompletionJoined", + "graphAccepted", "artifactAccepted", "independentObservationAccepted", "continuation_allowed"))): + raise ValueError("Failed 0056 original result flags changed") + if state.get("snapshot") is not None and snapshot != state["snapshot"]: + raise ValueError("Failed 0056 current continuity changed") + check() + state["snapshot"] = snapshot + state["failed"] = False + return started, result + finally: + state["remainingSeconds"] = remaining - (time.monotonic() - began) + if state["remainingSeconds"] <= 0: + state["failed"] = True + raise TimeoutError("Failed 0056 shared verification time exhausted") + try: + check() + except BaseException: + state["failed"] = True + raise + + def classify(platform, start): action = start.get('action') if platform == 'linux': @@ -1620,7 +1745,7 @@ def verify_failed_handoff(admission, deadline, cancelled, reserved): state = admission['failedHistory'] expected_pass = 0 if reserved is None else 1 if (state['failed'] or state['passes'] != expected_pass or - (reserved is not None and reserved != '0056')): + (reserved is not None and reserved != '0057')): fail('Failed-history checkpoint is missing, repeated or reordered') # Latch before I/O. An interrupted or rejected pass cannot obtain a retry. state['failed'] = True @@ -1685,18 +1810,22 @@ def refresh_history(admission, deadline, cancelled, reserved=None): keys(manifest['recomputedCounters'], ('linux', 'windows')) for platform, pin in ORIGINAL_HISTORY_PREFIX.items(): entries = manifest['histories'][platform] - expected_length = pin['entries'] + (2 if platform == 'windows' else 0) + expected_length = pin['entries'] + (3 if platform == 'windows' else 0) if (type(entries) is not list or len(entries) != expected_length or any(type(item) is not dict for item in entries) or sha(compact(entries[:pin['entries']])) != pin['compactSha256']): fail('Original accepted M53 prefix or exact successor suffix changed') - failed_entry, successful_entry = manifest['histories']['windows'][-2:] + failed_entry, successful_entry, observer_entry = manifest['histories']['windows'][-3:] keys(failed_entry, ('number', 'failedGuardDisposition')) if (failed_entry['number'] != '0054' or successful_entry.get('number') != '0055' or manifest['guardAction'] != '0055' or compact(failed_entry['failedGuardDisposition']) != compact( admission['callerProvenance']['successorHistory']['failedGuardDisposition'])): fail('Exact failed 0054 and successful 0055 handoff join changed') + keys(observer_entry, ('number', 'failedObserverDisposition')) + if (observer_entry['number'] != '0056' or + observer_entry['failedObserverDisposition'] != CORE_CSC_FAILED_DISPOSITION): + fail('Exact failed 0056 handoff disposition changed') totals = {'linux': [0, 0, 0, 0], 'windows': [0, 0, 0, 0]} starts = [] endpoints = [] @@ -1722,6 +1851,15 @@ def refresh_history(admission, deadline, cancelled, reserved=None): totals[platform] = [a + b for a, b in zip(totals[platform], failed['charge'])] starts.append(failed['started']) continue + if platform == 'windows' and item['number'] == '0056': + if totals != {'linux': [8, 37, 0, 0], 'windows': [7, 48, 0, 48]}: + fail('Original post-0055 charges changed before disposed 0056') + start, _original_result = verify_disposed_core_csc_observer( + admission.setdefault('failedObserverHistory', {}), deadline, cancelled) + totals[platform][1] += 1 + starts.append(start) + endpoints.append(string(start['endpoint'], '[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}')) + continue keys(item, ('number', 'localEntryNames', 'localFiles', 'windowsFiles', 'safetyMarkers')) local = base / item['number'] if CORE_CSC_HISTORY_ONLY: @@ -1764,15 +1902,16 @@ def refresh_history(admission, deadline, cancelled, reserved=None): if totals != manifest['recomputedCounters'] or sorted(endpoints) != manifest['knownEndpoints'] or len(endpoints) != len(set(endpoints)): fail('Original counters or endpoint history mismatch') if (sum(s['action'] == 'final-guard-prepare' for s in starts) != 2 or - starts[-1]['action'] != 'final-guard-prepare' or - manifest['histories']['windows'][-1]['number'] != manifest['guardAction']): - fail('Publication requires disposed 0054 followed by the original successful final 0055') + sum(s['action'] == 'core-csc-observer' for s in starts) != 1 or + starts[-1]['action'] != 'core-csc-observer' or + successful_entry['number'] != manifest['guardAction']): + fail('Publication requires disposed 0054, original successful 0055 and disposed 0056') if sum(s['action'] == 'bootstrap' for s in starts) != 1 or sum(s['action'] == 'restore' for s in starts) != 4: fail('Original five bootstrap/restore actions changed') lp, lb, lpub, ls = totals['linux'] wp, wb, wpub, ws = totals['windows'] - if (compact(totals) != compact({'linux': [8, 37, 0, 0], 'windows': [7, 48, 0, 48]}) or - lp > 9 or wp != 7 or lp + wp > 16 or wb > 48 or lb + wb + 1 > 120 or + if (compact(totals) != compact({'linux': [8, 37, 0, 0], 'windows': [7, 49, 0, 48]}) or + lp > 9 or wp != 7 or lp + wp > 16 or lb > 79 or wb > 49 or lb + wb + 1 > 120 or lpub != 0 or wpub != 0 or ls != 0 or ws != 48 or wpub + 1 > 12): fail('After-guard cumulative allocation differs from the accepted publication-only slot') # Exact original guard evidence is joined to the thirteen-field projection. @@ -1788,7 +1927,7 @@ def refresh_history(admission, deadline, cancelled, reserved=None): fail('Original guard artifact or completion binding changed') if sha(read(local / 'result.json', deadline, cancelled)) != guard['preparationWslResultSha256']: fail('Original guard WSL completion changed') - if manifest['histories']['windows'][-1]['windowsFiles'].get('final-guard/artifact-acceptance.json') != guard['artifactAcceptanceSha256']: + if successful_entry['windowsFiles'].get('final-guard/artifact-acceptance.json') != guard['artifactAcceptanceSha256']: fail('Original artifact acceptance copy is absent from the admitted handoff') artifact_copy = read(projection(guard['artifactAcceptancePath']), deadline, cancelled, CALLER_PROVENANCE_LIMIT) if (sha(artifact_copy) != guard['artifactAcceptanceSha256'] or diff --git a/tools/validation/run_managed.py b/tools/validation/run_managed.py index 167a3fdf..18fa8e0c 100644 --- a/tools/validation/run_managed.py +++ b/tools/validation/run_managed.py @@ -21,6 +21,7 @@ import re import selectors import signal +import stat import subprocess import sys import time @@ -359,6 +360,131 @@ def final_guard_process_reservation(started): FINAL_GUARD_SUCCESSOR_BINDING = None +# Exact failed 0056 evidence; activation remains part of a reviewed future caller. +CORE_CSC_FAILED_DISPOSITION_BINDING = None +CORE_CSC_FAILED_DISPOSITION = { + "path": "/tmp/windows-core-csc-0056-failed-history-disposition-root-v1.json", + "bytes": 3202, + "sha256": "6a241958bfd4693de219393c5920277e18ead52f8d039cd265f412837d142525", +} +CORE_CSC_FAILED_ORIGINALS = { + "wsl-result": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/result.json", + "wsl-started": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/started.json", + "wsl-controller-attempt": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/controller-start-attempt.json", + "windows-started": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/started.json", + "wsl-invocation": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/invocation.json", + "windows-invocation": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/invocation.json", +} + + +def verify_disposed_core_csc_observer(state=None, deadline=None, cancelled=None): + """Read only the disposition and six fixed originals, never a complete tree. + + Each pass has seven content reads requesting at most 15,093 bytes. Final + publication uses two passes sharing 30 seconds and checks current continuity; + an ordinary history reader uses one pass. Historical absence stays historical. + """ + if CORE_CSC_FAILED_DISPOSITION_BINDING != CORE_CSC_FAILED_DISPOSITION: + raise ValueError("UNBOUND: exact failed 0056 disposition") + state = {} if state is None else state + if state.get("failed") or state.get("passes", 0) >= 2: + raise ValueError("Failed 0056 history verification cannot repeat") + began = time.monotonic() + remaining = state.get("remainingSeconds", 30.0) + end = began + remaining + if deadline is not None: + end = min(end, deadline) + state["passes"] = state.get("passes", 0) + 1 + state["failed"] = True + snapshot = {} + + def check(): + if cancelled is not None and cancelled(): + raise InterruptedError("Failed 0056 history verification cancelled") + if time.monotonic() >= end: + raise TimeoutError("Failed 0056 shared history deadline exhausted") + + def identity(info): + return (info.st_dev, info.st_ino, info.st_mode, info.st_size, + info.st_mtime_ns, info.st_ctime_ns, info.st_nlink) + + def fixed_read(path, expected): + path = Path(path) + if not path.is_absolute() or len(path.parts) > 17: + raise ValueError("Invalid fixed 0056 evidence path") + for parent in reversed(path.parents): + check() + if not stat.S_ISDIR(os.stat(parent, follow_symlinks=False).st_mode): + raise ValueError("Nonordinary 0056 evidence ancestor") + check() + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + before = os.fstat(fd) + if not stat.S_ISREG(before.st_mode) or before.st_size != expected["bytes"]: + raise ValueError("Failed 0056 evidence type or size changed") + check() + with os.fdopen(fd, "rb", closefd=False) as stream: + raw = stream.read(expected["bytes"] + 1) + check() + after = os.fstat(fd) + current = os.stat(path, follow_symlinks=False) + if (identity(before) != identity(after) or identity(after) != identity(current) or + len(raw) != expected["bytes"] or hashlib.sha256(raw).hexdigest() != expected["sha256"]): + raise ValueError("Failed 0056 evidence identity or bytes changed") + snapshot[str(path)] = identity(after) + return raw + finally: + os.close(fd) + + try: + disposition = json.loads(fixed_read( + CORE_CSC_FAILED_DISPOSITION["path"], CORE_CSC_FAILED_DISPOSITION)) + # The complete descriptor hash pins all accepted historical and lifetime + # evidence references. Only these six fixed originals are reread here. + if (disposition["schema"] != "core-csc-observer-failed-history-disposition-v1" or + disposition["actionNumber"] != "0056" or disposition["actionKind"] != "core-csc-observer" or + set(disposition["originalCopies"]) != set(CORE_CSC_FAILED_ORIGINALS)): + raise ValueError("Wrong exact failed 0056 disposition") + raw = {role: fixed_read(path, disposition["originalCopies"][role]) + for role, path in CORE_CSC_FAILED_ORIGINALS.items()} + if raw["wsl-started"] != raw["windows-started"] or raw["wsl-invocation"] != raw["windows-invocation"]: + raise ValueError("Failed 0056 original pairs changed") + started, result = json.loads(raw["wsl-started"]), json.loads(raw["wsl-result"]) + invocation, attempt = json.loads(raw["wsl-invocation"]), json.loads(raw["wsl-controller-attempt"]) + reservation_hash = hashlib.sha256(raw["wsl-started"]).hexdigest() + if (started["number"] != "0056" or started["action"] != "core-csc-observer" or + started["handoffSha256"] != disposition["baseHandoff"]["sha256"] or + started["priorCounters"] != {"linux": [8, 37, 0, 0], "windows": [7, 48, 0, 48]} or + [started[key] for key in ("preparationCharge", "buildTestCharge", "publishCharge", + "reservedProcessScenarios")] != [0, 1, 0, 0] or + any(value["reservationSha256"] != reservation_hash for value in (result, invocation, attempt)) or + result["invocationSha256"] != hashlib.sha256(raw["wsl-invocation"]).hexdigest() or + invocation["endpoint"] != started["endpoint"]): + raise ValueError("Failed 0056 reservation or original charge changed") + if (result["failureType"] != "RuntimeError" or result["outcome"] != "incomplete" or + result["proxyExitCode"] is not None or result["launchAttempted"] is not True or + result["safetyStop"] is not True or any(result[key] is not False for key in ( + "normalCompletion", "quiescent", "originalWindowsCompletionJoined", + "graphAccepted", "artifactAccepted", "independentObservationAccepted", "continuation_allowed"))): + raise ValueError("Failed 0056 original result flags changed") + if state.get("snapshot") is not None and snapshot != state["snapshot"]: + raise ValueError("Failed 0056 current continuity changed") + check() + state["snapshot"] = snapshot + state["failed"] = False + return started, result + finally: + state["remainingSeconds"] = remaining - (time.monotonic() - began) + if state["remainingSeconds"] <= 0: + state["failed"] = True + raise TimeoutError("Failed 0056 shared verification time exhausted") + try: + check() + except BaseException: + state["failed"] = True + raise + + def guard_transaction(): if DRAFT_ONLY or FINAL_GUARD_SUCCESSOR_BINDING is None: raise ValueError("UNBOUND: exact successor reader/module/dispositions") @@ -396,12 +522,17 @@ def verify_accepted_final_guard_preparation(action, windows_action, started, res "windowsHistoryReader": str(REPOSITORY / "tools/validation/run_windows.py")} if str(Path(__file__).absolute()) not in readers.values(): raise ValueError("Reader path is not its actual accepted source") + # Current source admission covers this wrapper. The unchanged guard validator + # checks the original 0055 reader bytes only as historical evidence. + original_readers = Path("/tmp/azureauth-windows-guard-successor-fixture-source-108/tools/validation") + historical_readers = {"linuxHistoryReader": str(original_readers / "run_managed.py"), + "windowsHistoryReader": str(original_readers / "run_windows.py")} if own_transaction and Path(action).name == "0055": module.validate_history_action('linux-reader', Path("/var/tmp/azureauth-windows-slice-108/windows-actions/0054"), Path("/mnt/c/Temp/azureauth-windows-slice-108/actions/0054"), None, None, state) value = module.validate_history_action('linux-reader', action, windows_action, started, result, - state, FINAL_GUARD_REVIEWED_BINDING, readers) + state, FINAL_GUARD_REVIEWED_BINDING, historical_readers) if own_transaction: module.finish(state) return value @@ -412,6 +543,10 @@ def windows_process_reservation(number, started): action = started.get("action") if action == "final-guard-prepare": return final_guard_process_reservation(started) + if action == "core-csc-observer": + if number != 56 or started.get("number") != "0056" or started.get("reservedProcessScenarios") != 0: + raise ValueError("Wrong disposed observer process allocation") + return 0 if action not in ("bootstrap", "restore", "build", "test"): raise ValueError("Unknown Windows action allocation") if number <= 14: @@ -459,6 +594,7 @@ def windows_consumption(): default_http_processes = 0 default_http_phases = [] guard_preparations = 0 + disposed_observers = 0 guard_context = None windows = Path("/mnt/c/Temp/azureauth-windows-slice-108/actions") for number, action in enumerate(sorted(history.iterdir()), 1): @@ -470,6 +606,12 @@ def windows_consumption(): verify_accepted_final_guard_preparation(action, windows / action.name, None, None, guard_context) prefix = "failed" if action.name == "0054" else "success" started, receipt = (guard_context[1][prefix + key] for key in ("Started", "Result")) + elif action.name == "0056": + if (guard_context is None or not guard_context[1].get("successValidated") or + guard_preparations != 2 or preparation != 7 or build_test != 48 or process_scenarios != 48): + raise ValueError("Disposed 0056 requires the accepted 0054/0055 prefix") + started, receipt = verify_disposed_core_csc_observer() + disposed_observers += 1 else: if action.is_symlink(): raise ValueError("Noncontiguous Windows action history") @@ -504,9 +646,11 @@ def windows_consumption(): guard_preparations += 1 if guard_preparations > 2 or action.name != ("0054" if guard_preparations == 1 else "0055"): raise ValueError("Unallocated guard history composition") + elif action.name == "0056" and started.get("action") == "core-csc-observer": + pass # Exact disposition preserves the original incomplete result. elif receipt.get("continuation_allowed") is not True or receipt.get("quiescent") is not True: raise ValueError("Unresolved Windows action stops both validation loops") - if started.get("action") != "final-guard-prepare": + if started.get("action") not in ("final-guard-prepare", "core-csc-observer"): for name, expected in receipt["evidence"].items(): if digest(windows / action.name / name) != expected: raise ValueError("Windows action evidence changed") @@ -523,12 +667,15 @@ def windows_consumption(): preparation += 1 elif started["action"] in ("build", "test"): build_test += 1 + elif action.name == "0056" and started["action"] == "core-csc-observer": + pass # Its dedicated charge is included once in the aggregate below. else: raise ValueError("Unknown Windows action allocation") if guard_context is not None: guard_context[0].finish(guard_context[1]) if preparation > 7 or preparation - guard_preparations > 5 or \ - build_test > 48 or process_scenarios > 60 or \ + build_test > 48 or disposed_observers > 1 or build_test + disposed_observers > 49 or \ + process_scenarios > 60 or \ owned_processes > 20 or default_http_processes > 4 or \ process_scenarios - owned_processes - default_http_processes > 36 or \ default_http_phases not in ([], ["red"], ["red", "green"]): @@ -543,7 +690,7 @@ def windows_consumption(): raise ValueError("Windows UI-admission red must complete before Linux continuation") if number == 39: raise ValueError("Windows corrected owned-process build must complete before Linux continuation") - return preparation, build_test + return preparation, build_test + disposed_observers def verify_disposed_owned_process_red(action, failed): diff --git a/tools/validation/run_windows.py b/tools/validation/run_windows.py index 97e7bcd2..17959a6d 100644 --- a/tools/validation/run_windows.py +++ b/tools/validation/run_windows.py @@ -21,6 +21,7 @@ import platform import re import signal +import stat import subprocess import sys import time @@ -558,6 +559,131 @@ def final_guard_process_reservation(started): FINAL_GUARD_SUCCESSOR_BINDING = None +# Exact failed 0056 evidence; activation remains part of a reviewed future caller. +CORE_CSC_FAILED_DISPOSITION_BINDING = None +CORE_CSC_FAILED_DISPOSITION = { + "path": "/tmp/windows-core-csc-0056-failed-history-disposition-root-v1.json", + "bytes": 3202, + "sha256": "6a241958bfd4693de219393c5920277e18ead52f8d039cd265f412837d142525", +} +CORE_CSC_FAILED_ORIGINALS = { + "wsl-result": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/result.json", + "wsl-started": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/started.json", + "wsl-controller-attempt": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/controller-start-attempt.json", + "windows-started": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/started.json", + "wsl-invocation": "/var/tmp/azureauth-windows-slice-108/windows-actions/0056/invocation.json", + "windows-invocation": "/mnt/c/Temp/azureauth-windows-slice-108/actions/0056/invocation.json", +} + + +def verify_disposed_core_csc_observer(state=None, deadline=None, cancelled=None): + """Read only the disposition and six fixed originals, never a complete tree. + + Each pass has seven content reads requesting at most 15,093 bytes. Final + publication uses two passes sharing 30 seconds and checks current continuity; + an ordinary history reader uses one pass. Historical absence stays historical. + """ + if CORE_CSC_FAILED_DISPOSITION_BINDING != CORE_CSC_FAILED_DISPOSITION: + raise ValueError("UNBOUND: exact failed 0056 disposition") + state = {} if state is None else state + if state.get("failed") or state.get("passes", 0) >= 2: + raise ValueError("Failed 0056 history verification cannot repeat") + began = time.monotonic() + remaining = state.get("remainingSeconds", 30.0) + end = began + remaining + if deadline is not None: + end = min(end, deadline) + state["passes"] = state.get("passes", 0) + 1 + state["failed"] = True + snapshot = {} + + def check(): + if cancelled is not None and cancelled(): + raise InterruptedError("Failed 0056 history verification cancelled") + if time.monotonic() >= end: + raise TimeoutError("Failed 0056 shared history deadline exhausted") + + def identity(info): + return (info.st_dev, info.st_ino, info.st_mode, info.st_size, + info.st_mtime_ns, info.st_ctime_ns, info.st_nlink) + + def fixed_read(path, expected): + path = Path(path) + if not path.is_absolute() or len(path.parts) > 17: + raise ValueError("Invalid fixed 0056 evidence path") + for parent in reversed(path.parents): + check() + if not stat.S_ISDIR(os.stat(parent, follow_symlinks=False).st_mode): + raise ValueError("Nonordinary 0056 evidence ancestor") + check() + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) + try: + before = os.fstat(fd) + if not stat.S_ISREG(before.st_mode) or before.st_size != expected["bytes"]: + raise ValueError("Failed 0056 evidence type or size changed") + check() + with os.fdopen(fd, "rb", closefd=False) as stream: + raw = stream.read(expected["bytes"] + 1) + check() + after = os.fstat(fd) + current = os.stat(path, follow_symlinks=False) + if (identity(before) != identity(after) or identity(after) != identity(current) or + len(raw) != expected["bytes"] or hashlib.sha256(raw).hexdigest() != expected["sha256"]): + raise ValueError("Failed 0056 evidence identity or bytes changed") + snapshot[str(path)] = identity(after) + return raw + finally: + os.close(fd) + + try: + disposition = json.loads(fixed_read( + CORE_CSC_FAILED_DISPOSITION["path"], CORE_CSC_FAILED_DISPOSITION)) + # The complete descriptor hash pins all accepted historical and lifetime + # evidence references. Only these six fixed originals are reread here. + if (disposition["schema"] != "core-csc-observer-failed-history-disposition-v1" or + disposition["actionNumber"] != "0056" or disposition["actionKind"] != "core-csc-observer" or + set(disposition["originalCopies"]) != set(CORE_CSC_FAILED_ORIGINALS)): + raise ValueError("Wrong exact failed 0056 disposition") + raw = {role: fixed_read(path, disposition["originalCopies"][role]) + for role, path in CORE_CSC_FAILED_ORIGINALS.items()} + if raw["wsl-started"] != raw["windows-started"] or raw["wsl-invocation"] != raw["windows-invocation"]: + raise ValueError("Failed 0056 original pairs changed") + started, result = json.loads(raw["wsl-started"]), json.loads(raw["wsl-result"]) + invocation, attempt = json.loads(raw["wsl-invocation"]), json.loads(raw["wsl-controller-attempt"]) + reservation_hash = hashlib.sha256(raw["wsl-started"]).hexdigest() + if (started["number"] != "0056" or started["action"] != "core-csc-observer" or + started["handoffSha256"] != disposition["baseHandoff"]["sha256"] or + started["priorCounters"] != {"linux": [8, 37, 0, 0], "windows": [7, 48, 0, 48]} or + [started[key] for key in ("preparationCharge", "buildTestCharge", "publishCharge", + "reservedProcessScenarios")] != [0, 1, 0, 0] or + any(value["reservationSha256"] != reservation_hash for value in (result, invocation, attempt)) or + result["invocationSha256"] != hashlib.sha256(raw["wsl-invocation"]).hexdigest() or + invocation["endpoint"] != started["endpoint"]): + raise ValueError("Failed 0056 reservation or original charge changed") + if (result["failureType"] != "RuntimeError" or result["outcome"] != "incomplete" or + result["proxyExitCode"] is not None or result["launchAttempted"] is not True or + result["safetyStop"] is not True or any(result[key] is not False for key in ( + "normalCompletion", "quiescent", "originalWindowsCompletionJoined", + "graphAccepted", "artifactAccepted", "independentObservationAccepted", "continuation_allowed"))): + raise ValueError("Failed 0056 original result flags changed") + if state.get("snapshot") is not None and snapshot != state["snapshot"]: + raise ValueError("Failed 0056 current continuity changed") + check() + state["snapshot"] = snapshot + state["failed"] = False + return started, result + finally: + state["remainingSeconds"] = remaining - (time.monotonic() - began) + if state["remainingSeconds"] <= 0: + state["failed"] = True + raise TimeoutError("Failed 0056 shared verification time exhausted") + try: + check() + except BaseException: + state["failed"] = True + raise + + def guard_transaction(): if DRAFT_ONLY or FINAL_GUARD_SUCCESSOR_BINDING is None: raise ValueError("UNBOUND: exact successor reader/module/dispositions") @@ -595,12 +721,17 @@ def verify_accepted_final_guard_preparation(action, windows_action, started, res "windowsHistoryReader": str(REPOSITORY / "tools/validation/run_windows.py")} if str(Path(__file__).absolute()) not in readers.values(): raise ValueError("Reader path is not its actual accepted source") + # Current source admission covers this wrapper. The unchanged guard validator + # checks the original 0055 reader bytes only as historical evidence. + original_readers = Path("/tmp/azureauth-windows-guard-successor-fixture-source-108/tools/validation") + historical_readers = {"linuxHistoryReader": str(original_readers / "run_managed.py"), + "windowsHistoryReader": str(original_readers / "run_windows.py")} if own_transaction and Path(action).name == "0055": module.validate_history_action('windows-reader', Path("/var/tmp/azureauth-windows-slice-108/windows-actions/0054"), Path("/mnt/c/Temp/azureauth-windows-slice-108/actions/0054"), None, None, state) value = module.validate_history_action('windows-reader', action, windows_action, started, result, - state, FINAL_GUARD_REVIEWED_BINDING, readers) + state, FINAL_GUARD_REVIEWED_BINDING, historical_readers) if own_transaction: module.finish(state) return value @@ -611,6 +742,10 @@ def windows_process_reservation(number, started): action = started.get("action") if action == "final-guard-prepare": return final_guard_process_reservation(started) + if action == "core-csc-observer": + if number != 56 or started.get("number") != "0056" or started.get("reservedProcessScenarios") != 0: + raise ValueError("Wrong disposed observer process allocation") + return 0 if action not in ("bootstrap", "restore", "build", "test"): raise ValueError("Unknown Windows action allocation") if number <= 14: @@ -841,6 +976,16 @@ def histories(): verify_accepted_final_guard_preparation(action, ROOT / "actions" / action.name, None, None, guard_context) prefix = "failed" if action.name == "0054" else "success" started, result = (guard_context[1][prefix + key] for key in ("Started", "Result")) + elif action.name == "0056": + if (guard_context is None or not guard_context[1].get("successValidated") or + guard_preparations != 2 or len(windows) != 55 or + sum(start["action"] in ("bootstrap", "restore", "final-guard-prepare") + for _, start, _ in windows) != 7 or + sum(start["action"] in ("build", "test") for _, start, _ in windows) != 48 or + sum(windows_process_reservation(int(prior.name), start) + for prior, start, _ in windows) != 48): + raise ValueError("Disposed 0056 requires the accepted 0054/0055 prefix") + started, result = verify_disposed_core_csc_observer() else: result = read(action / "result.json") started = read(action / "started.json") @@ -873,9 +1018,11 @@ def histories(): guard_preparations += 1 if guard_preparations > 2 or action.name != ("0054" if guard_preparations == 1 else "0055"): raise ValueError("Unallocated guard history composition") + elif action.name == "0056" and started.get("action") == "core-csc-observer": + pass # Exact disposition preserves the original incomplete result. elif result.get("continuation_allowed") is not True or result.get("quiescent") is not True: raise ValueError("Unresolved Windows action") - if started.get("action") != "final-guard-prepare": + if started.get("action") not in ("final-guard-prepare", "core-csc-observer"): for name, expected in result["evidence"].items(): if digest(ROOT / "actions" / action.name / name) != expected: raise ValueError("Windows evidence changed") @@ -1636,15 +1783,20 @@ def execute(args, attended, finish_preparation): ordinary_prep = sum(start["action"] in ("bootstrap", "restore") for _, start, _ in previous) guard_preparations = sum(start["action"] == "final-guard-prepare" for _, start, _ in previous) prep = ordinary_prep + guard_preparations - tests = len(previous) - prep + tests = sum(start["action"] in ("build", "test") for _, start, _ in previous) + disposed_observers = sum(start["action"] == "core-csc-observer" for _, start, _ in previous) + aggregate_tests = tests + disposed_observers + if len(previous) != prep + aggregate_tests or disposed_observers > 1: + raise ValueError("Unknown or repeated Windows allocation") if guard_preparations > 2 or ordinary_prep + preparation > 5 or \ - prep + preparation > 7 or tests + (not preparation) > 48: + prep + preparation > 7 or tests + (not preparation) > 48 or \ + aggregate_tests + (not preparation) > 49: raise ValueError("Windows allocation exhausted") linux_preparation = sum(item["action"] in ("fetch", "restore") for item in linux) if linux_preparation > 9: raise ValueError("Transferred Linux preparation allocation exceeded") if linux_preparation + prep + preparation > 16 or \ - sum(item["action"] in ("build", "test") for item in linux) + tests + (not preparation) + 1 > 120: + sum(item["action"] in ("build", "test") for item in linux) + aggregate_tests + (not preparation) + 1 > 120: raise ValueError("Combined Wave capacity exhausted") if args.action == "bootstrap" and previous or args.action != "bootstrap" and not previous: raise ValueError("Bootstrap occurs exactly once, before restore/build/test") @@ -1654,7 +1806,7 @@ def execute(args, attended, finish_preparation): start = {"action": args.action, "utc": utc(), "protocol": args.protocol, "source": args.source, "sourceTree": git("rev-parse", args.source + "^{tree}"), "target": args.target, "review": args.review, "expected": args.expect, "linuxActions": len(linux), - "priorWindowsPreparation": prep, "priorWindowsBuildTest": tests, + "priorWindowsPreparation": prep, "priorWindowsBuildTest": aggregate_tests, "reservedProcessScenarios": reserved_processes, "priorProcessScenarios": prior_processes, "graphTransition": graph_transition, "testSuite": args.suite} write_new(local / "started.json", start)