P04-01: 同時発生の更新規約
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
# Repair24 — source-ready submission
|
||||||
|
|
||||||
|
Bounded continuation of P04-01 request-repair24; tests NOT RUN.
|
||||||
|
Existing wrapper edits and the untracked identity fixture were read first and
|
||||||
|
preserved/corrected. Only this execution subtree was written. No Git, engine,
|
||||||
|
Runner, agent, commit or integration operation was performed.
|
||||||
|
|
||||||
|
Changed source/runbook SHA256 (exact bytes):
|
||||||
|
|
||||||
|
| File | SHA256 |
|
||||||
|
| --- | --- |
|
||||||
|
| s4/run_s4_native.py | dda495d33566eae14c8a707e77f8ec9dc6c7bd043076a091e90dd53f6a7490db |
|
||||||
|
| p04_native_series.py | 348197759c169c2094db3b1114ca3abe5525b8d4bc9aca611d4579871a130b40 |
|
||||||
|
| s4/engine_identity_negative_fixtures.py | f9148ef79cb62ec6631e7e3f23e1d3b062434f7152977aab3b9d18d82672255e |
|
||||||
|
| s4/S4_ROOT_RUN.md | c1a00e3dad8d91d28df1a7f868f6e4e905f1df721baac964865e341c84356135 |
|
||||||
|
|
||||||
|
This new status document records the hashes above; its own hash must be taken
|
||||||
|
from the submitted checkout, outside its own contents.
|
||||||
|
|
||||||
|
Wrapper pins changed: the own-B bare Godot copy is replaced with exactly
|
||||||
|
`/Applications/Godot.app/Contents/MacOS/Godot`, rejecting symlinks and any
|
||||||
|
different resolved path. Its constant SHA remains
|
||||||
|
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`.
|
||||||
|
Engine identity no longer depends on an old cache runner-input. Fresh NO-CACHE
|
||||||
|
is the default at plan and native collection. Optional explicit cache mode
|
||||||
|
rejects failed receipts before reading their inputs or cache contents.
|
||||||
|
No default cache or cache PASS is required. No engine is copied, re-signed or
|
||||||
|
modified, and no codesign/xattr/security operation is added.
|
||||||
|
|
||||||
|
Before-launch, during-collection and explicit after-cleanup re-pins retain the
|
||||||
|
full runtime descriptor (path/resolved path, bytes, SHA, device, inode, mode,
|
||||||
|
mtime and ctime). Runner-input and execution retain equal before/during/after
|
||||||
|
identity records and execution retains labeled observations. fixed_guard still
|
||||||
|
re-pins the engine. The verifier permits only this exact installed engine
|
||||||
|
outside B, reads/hashes it exactly and checks the recorded identities.
|
||||||
|
Fixed inputs and QA remain own-B only; actual command[0] must equal the pin.
|
||||||
|
|
||||||
|
Defaults and Root staging are now:
|
||||||
|
`p04-s4-native-r24`, `p04-s4-plan-r24.json`,
|
||||||
|
`audit-support/p04-s4-source-r24`. Only RUNS S4 changes to r24.
|
||||||
|
C/S1-S3 bindings, other series and the frozen C block (20916) are unchanged.
|
||||||
|
Source1338, parse316, sole QA override, default-player protection, owned
|
||||||
|
four-distinct-PID capture, live-engine refusal, disk admission, fresh output,
|
||||||
|
cleanup-before-fixed-guard, formal Writer/Reader, NO_POWER, typed declarations,
|
||||||
|
forbidden mutations/bypasses and mana constants guards remain.
|
||||||
|
|
||||||
|
Untouched checker: SHA256
|
||||||
|
`a61c5b496e656f60c66898e42500bc9a2d45f947690ce8f5f19a03984ebbc850`,
|
||||||
|
78399 B. Untouched probe: SHA256
|
||||||
|
`ceb592d853fd50063f20496c7b6df05257528dbe1c0d0d80bc6a8ccb1f9dd6d7`.
|
||||||
|
All 1338 game files are unchanged; no game source was written. No new claim of
|
||||||
|
executed 1338-file verification is made.
|
||||||
|
|
||||||
|
The formal reviewer's five findings are carried forward unchanged, with no
|
||||||
|
closure claimed. Their individual review text was not supplied in this dispatch
|
||||||
|
or found in the local execution records searched; it is not reconstructed here.
|
||||||
|
Main 9b remains frozen at original 13/88. Support 53cb remains review pending.
|
||||||
|
D05 remains pending. No acceptance denominator, specification, main integration
|
||||||
|
or original completion state changes.
|
||||||
|
|
||||||
|
r22 is an immutable failed proof and remains ineligible. Diagnostics:
|
||||||
|
`audit-support/p04-root-d393-s4-r01/ENGINE_STAGING_DIAGNOSTICS.json`.
|
||||||
|
Its pre-banner staged-copy failure is an environment/staging fault; no product
|
||||||
|
logic or probe/checker repair is claimed. Root uses the installed executable
|
||||||
|
directly and preserves r22. Do not rerun S1-S3, C or full178.
|
||||||
|
|
||||||
|
Validation: static AST parsing and in-memory compile completed for the wrapper,
|
||||||
|
series verifier and identity fixture; no module was imported or executed.
|
||||||
|
Read-only SHA/byte inspection completed for the four listed files, checker and
|
||||||
|
probe. These are source checks only. Tests NOT RUN: identity fixtures, all other
|
||||||
|
fixtures/suites, check-only plan generation, native S4, full178, registered
|
||||||
|
validation and independent review. No unexecuted result is reported as passed.
|
||||||
|
|
||||||
|
Registered validation stays exit 2 until Root generates the current
|
||||||
|
MANIFEST.json collection; the gate is not changed. This is the carried-forward
|
||||||
|
expected gate state, not a newly executed validation result. Root execution,
|
||||||
|
current exact-version evidence and formal independent review remain pending.
|
||||||
@@ -17,7 +17,7 @@ MANIFEST_SHA256 = 'a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c
|
|||||||
MANIFEST_BYTES = 1244135
|
MANIFEST_BYTES = 1244135
|
||||||
PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
|
PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
|
||||||
RUNS = {'s1': 'p04-s1-native-r02', 's2a': 'p04-s2a-native-r03',
|
RUNS = {'s1': 'p04-s1-native-r02', 's2a': 'p04-s2a-native-r03',
|
||||||
's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01', 's4': 'p04-s4-native-r22'}
|
's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01', 's4': 'p04-s4-native-r24'}
|
||||||
# Native descriptors pinned from literal files, never ROOT result passed fields.
|
# Native descriptors pinned from literal files, never ROOT result passed fields.
|
||||||
NATIVE_PINS = {
|
NATIVE_PINS = {
|
||||||
's1': ('1e26d9585396da9415747d87d77b70f23425a3ee0714e4fca114748f9479fd4e', '01372beb0ffec181c892d3b4e88bce32001e08fb2007938d7bf19135511a7acc', '0b6d11eb34d8af64b22cb806caeb06eed4ec521fa9ee24d1204ea185345e655c'),
|
's1': ('1e26d9585396da9415747d87d77b70f23425a3ee0714e4fca114748f9479fd4e', '01372beb0ffec181c892d3b4e88bce32001e08fb2007938d7bf19135511a7acc', '0b6d11eb34d8af64b22cb806caeb06eed4ec521fa9ee24d1204ea185345e655c'),
|
||||||
@@ -968,8 +968,33 @@ def power_stop(s):
|
|||||||
'service_id': sid, 'transaction_id': paid[0]['transaction_id'], 'priority_approved': False}
|
'service_id': sid, 'transaction_id': paid[0]['transaction_id'], 'priority_approved': False}
|
||||||
|
|
||||||
|
|
||||||
|
S4_ENGINE_PATH = '/Applications/Godot.app/Contents/MacOS/Godot'
|
||||||
|
S4_ENGINE_SHA256 = 'c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf'
|
||||||
|
|
||||||
|
|
||||||
|
def verify_s4_engine(pin, identity):
|
||||||
|
require(pin['path'] == S4_ENGINE_PATH and pin['sha256'] == S4_ENGINE_SHA256,
|
||||||
|
'S4 exact installed engine pin required')
|
||||||
|
path = ordinary(pin['path'])
|
||||||
|
require(str(path.resolve(strict=True)) == S4_ENGINE_PATH, 'S4 engine resolved path')
|
||||||
|
before = path.stat()
|
||||||
|
require(type(pin['bytes']) is int, 'S4 recorded engine bytes required')
|
||||||
|
checked(path.read_bytes(), pin, 'S4 installed engine')
|
||||||
|
after = path.stat()
|
||||||
|
fields = {'bytes': 'st_size', 'mtime_ns': 'st_mtime_ns', 'mode': 'st_mode',
|
||||||
|
'device': 'st_dev', 'inode': 'st_ino', 'ctime_ns': 'st_ctime_ns'}
|
||||||
|
require(all(getattr(before, f) == getattr(after, f) for f in fields.values()),
|
||||||
|
'S4 engine changed while reading')
|
||||||
|
ordinary(path)
|
||||||
|
require(pin['resolved_path'] == S4_ENGINE_PATH and
|
||||||
|
all(pin[k] == getattr(after, f) for k, f in fields.items()), 'S4 runtime engine descriptor')
|
||||||
|
require(set(identity) == {'before', 'during', 'after'} and identity['before'] == pin and
|
||||||
|
identity['after'] == pin and isinstance(identity['during'], list) and identity['during'] and
|
||||||
|
all(value == pin for value in identity['during']), 'S4 before/during/after engine identity')
|
||||||
|
|
||||||
|
|
||||||
class S4Series(Series):
|
class S4Series(Series):
|
||||||
"""Fresh S4: all verifier inputs live in own B; profile paths are provenance."""
|
"""Fresh S4: own-B materials and exact installed engine; profiles are provenance."""
|
||||||
def __init__(self, build):
|
def __init__(self, build):
|
||||||
self.name, self.build, self.run = 's4', Path(build), RUNS['s4']
|
self.name, self.build, self.run = 's4', Path(build), RUNS['s4']
|
||||||
self.base = self.build / self.run
|
self.base = self.build / self.run
|
||||||
@@ -993,10 +1018,16 @@ class S4Series(Series):
|
|||||||
require(inputs['QA_overrides'] == ['tests/s3/test_runtime_staff_assignment.gd'], 'S4 sole QA override')
|
require(inputs['QA_overrides'] == ['tests/s3/test_runtime_staff_assignment.gd'], 'S4 sole QA override')
|
||||||
selected[inputs['QA_overrides'][0]] = digest(qa.read_bytes())
|
selected[inputs['QA_overrides'][0]] = digest(qa.read_bytes())
|
||||||
require(selected == inputs['selected_source_map'], 'S4 selected source map')
|
require(selected == inputs['selected_source_map'], 'S4 selected source map')
|
||||||
for pin in inputs['fixed_input_pins'] + [inputs['QA'], inputs['engine']]:
|
for pin in inputs['fixed_input_pins'] + [inputs['QA']]:
|
||||||
path = Path(pin['path'])
|
path = Path(pin['path'])
|
||||||
require(path.is_relative_to(self.build), 'S4 material outside own B')
|
require(path.is_relative_to(self.build), 'S4 material outside own B')
|
||||||
self.read_build(path.relative_to(self.build).as_posix(), pin)
|
self.read_build(path.relative_to(self.build).as_posix(), pin)
|
||||||
|
verify_s4_engine(inputs['engine'], execution['engine_identity'])
|
||||||
|
require(inputs['engine_identity'] == execution['engine_identity'], 'S4 input/execution engine identity')
|
||||||
|
observations = execution['engine_observations']
|
||||||
|
require(any(row['stage'] == 'after-cleanup' for row in observations) and
|
||||||
|
all(row.get('descriptor') == inputs['engine'] for row in observations),
|
||||||
|
'S4 runtime engine observations')
|
||||||
require(execution['error'] is None and execution['guard_errors'] == [] and execution['cleanup_errors'] == [] and
|
require(execution['error'] is None and execution['guard_errors'] == [] and execution['cleanup_errors'] == [] and
|
||||||
[p['phase'] for p in execution['phases']] == ['import', 'parse', 'writer', 'reader'], 'S4 native phase/guard failure')
|
[p['phase'] for p in execution['phases']] == ['import', 'parse', 'writer', 'reader'], 'S4 native phase/guard failure')
|
||||||
for phase in execution['phases']:
|
for phase in execution['phases']:
|
||||||
|
|||||||
@@ -6,26 +6,24 @@ source batch is auto-submitted. Do not rerun C/S1/S2a/S2b/S3. Do not run the ful
|
|||||||
product suite now. Preserve all existing raw and REPAIR17–21 history.
|
product suite now. Preserve all existing raw and REPAIR17–21 history.
|
||||||
|
|
||||||
From the clean auto-submitted checkout, stage these three exact reviewed files
|
From the clean auto-submitted checkout, stage these three exact reviewed files
|
||||||
in the fresh own-B source directory. Copy the installed engine executable there as an independently pinned native input. This staging is a Root action, not a worker
|
in the fresh own-B source directory. Run the installed executable directly; do not stage or copy it. This staging is a Root action, not a worker
|
||||||
execution. The wrapper refuses QA outside own B. All input/QA/source footprints
|
execution. The wrapper refuses QA outside own B. QA and fixed inputs stay within registered B; only the exact installed engine is external; external manifest paths are lookup keys and
|
||||||
below are within the registered B; external manifest paths are lookup keys and
|
|
||||||
provenance only. Never open their original production locations.
|
provenance only. Never open their original production locations.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||||
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||||
export PYTHONDONTWRITEBYTECODE=1
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
mkdir "$B/audit-support/p04-s4-source-r23"
|
mkdir "$B/audit-support/p04-s4-source-r24"
|
||||||
cp "$E/s4/run_s4_native.py" "$E/s4/p04_s4_power_probe.gd" "$E/s4/S4_ROOT_RUN.md" "$B/audit-support/p04-s4-source-r23/"
|
cp "$E/s4/run_s4_native.py" "$E/s4/p04_s4_power_probe.gd" "$E/s4/S4_ROOT_RUN.md" "$B/audit-support/p04-s4-source-r24/"
|
||||||
cp /Applications/Godot.app/Contents/MacOS/Godot "$B/audit-support/p04-s4-source-r23/Godot"
|
python3 "$B/audit-support/p04-s4-source-r24/run_s4_native.py" \
|
||||||
python3 "$B/audit-support/p04-s4-source-r23/run_s4_native.py" \
|
|
||||||
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
||||||
--output "$B/p04-s4-native-r22" --plan "$B/p04-s4-plan-r23.json" \
|
--output "$B/p04-s4-native-r24" --plan "$B/p04-s4-plan-r24.json" \
|
||||||
--godot "$B/audit-support/p04-s4-source-r23/Godot" --check-only
|
--godot /Applications/Godot.app/Contents/MacOS/Godot --check-only
|
||||||
python3 "$B/audit-support/p04-s4-source-r23/run_s4_native.py" \
|
python3 "$B/audit-support/p04-s4-source-r24/run_s4_native.py" \
|
||||||
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
--project-root "$B/audit-support/p04-r17-full-inputs-r01/product" \
|
||||||
--output "$B/p04-s4-native-r22" --plan "$B/p04-s4-plan-r23.json" \
|
--output "$B/p04-s4-native-r24" --plan "$B/p04-s4-plan-r24.json" \
|
||||||
--godot "$B/audit-support/p04-s4-source-r23/Godot"
|
--godot /Applications/Godot.app/Contents/MacOS/Godot
|
||||||
```
|
```
|
||||||
|
|
||||||
The first invocation only prepares the pinned plan: zero engine launches. The
|
The first invocation only prepares the pinned plan: zero engine launches. The
|
||||||
@@ -39,20 +37,18 @@ inputs. Reader uses only the existing IO1 formal package mapping.
|
|||||||
|
|
||||||
Exact input footprints:
|
Exact input footprints:
|
||||||
|
|
||||||
- Current S4 wrapper SHA256:
|
- Current wrapper SHA256 is recorded in `../REPAIR24_STATUS.md`. The plan pins
|
||||||
`6d5f4e05e87607413bfb5498dc9c9e17c2bfd96bf751c9e645050ca6f8e69d5c`.
|
its exact bytes and Stage R checks them against the current source checkout.
|
||||||
The plan pins these exact bytes; `p04_native_series.py` checks the wrapper
|
|
||||||
against the current source file dynamically. Historical r22 receipts remain
|
|
||||||
unchanged and do not describe this r23 wrapper.
|
|
||||||
- `B/audit-support/p04-r17-full-inputs-r01/MANIFEST.json`, SHA256
|
- `B/audit-support/p04-r17-full-inputs-r01/MANIFEST.json`, SHA256
|
||||||
`a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`.
|
`a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`.
|
||||||
- All 1338 files below that relocation's `product/`, each matched to the
|
- All 1338 files below that relocation's `product/`, each matched to the
|
||||||
manifest by bytes/SHA. No source is omitted and no old path is followed.
|
manifest by bytes/SHA. No source is omitted and no old path is followed.
|
||||||
- Its `fixed/6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7/completion_marker_contracts.json`.
|
- Its `fixed/6ce6b28c5985e8a4f13b78bfeb1f1e09f6216d874fb01e0656ec1633bfa01cd7/completion_marker_contracts.json`.
|
||||||
- `B/audit-support/p04-review-repair22-r01/ACTUAL_CURRENT_PUBLIC_POWER_FUNCTIONS.json`.
|
- `B/audit-support/p04-review-repair22-r01/ACTUAL_CURRENT_PUBLIC_POWER_FUNCTIONS.json`.
|
||||||
- The three staged S4 files above, pinned by the plan, and the staged native
|
- The three staged S4 files above, pinned by the plan, and the installed executable
|
||||||
executable at B/audit-support/p04-s4-source-r23/Godot, pinned before native
|
`/Applications/Godot.app/Contents/MacOS/Godot`, pinned directly against the
|
||||||
admission and checked unchanged after collection. The only stage override
|
constant SHA before launch, during collection, and explicitly after cleanup.
|
||||||
|
Before/during/after descriptors are retained in runner-input and execution. The only stage override
|
||||||
is `tests/s3/test_runtime_staff_assignment.gd` with the S4 probe. Harness and
|
is `tests/s3/test_runtime_staff_assignment.gd` with the S4 probe. Harness and
|
||||||
kitchen fixture are the unchanged product files; inherited sources are
|
kitchen fixture are the unchanged product files; inherited sources are
|
||||||
included in the full1338 map. The parse roster is all316 `.gd` inputs.
|
included in the full1338 map. The parse roster is all316 `.gd` inputs.
|
||||||
@@ -61,14 +57,15 @@ Exact input footprints:
|
|||||||
the actual full import. All316 scripts are parsed before Writer and Reader.
|
the actual full import. All316 scripts are parsed before Writer and Reader.
|
||||||
- The engine SHA256 is
|
- The engine SHA256 is
|
||||||
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`,
|
`c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf`,
|
||||||
matching the installed executable and preserved r22 staged executable during
|
required directly of the installed executable. Fresh mode does not obtain engine identity from a cache.
|
||||||
source preparation. Fresh mode does not obtain engine identity from a cache.
|
|
||||||
- Optional `--cache-run` retains the registered path, passed-run, complete-source
|
- Optional `--cache-run` retains the registered path, passed-run, complete-source
|
||||||
and engine identity guards. The known red run remains ineligible. The commands
|
and engine identity guards. The known red run remains ineligible. The commands
|
||||||
above use no cache. Preserve the old r22 staging and plan unchanged.
|
above use no cache. Preserve the old r22 staging and plan unchanged. r22 is an immutable failed
|
||||||
|
proof and remains ineligible. Its diagnostics are at
|
||||||
|
`audit-support/p04-root-d393-s4-r01/ENGINE_STAGING_DIAGNOSTICS.json`.
|
||||||
|
|
||||||
Exact output footprints are `B/p04-s4-plan-r23.json` and the fresh
|
Exact output footprints are `B/p04-s4-plan-r24.json` and the fresh
|
||||||
`B/p04-s4-native-r22/**`: private selected source stage, complete phase receipts
|
`B/p04-s4-native-r24/**`: private selected source stage, complete phase receipts
|
||||||
and logs, raw JSON/Variant points and all phase observations, full source maps,
|
and logs, raw JSON/Variant points and all phase observations, full source maps,
|
||||||
PID and cleanup receipts. `formal/{writer,reader}/S4-{boundary,held,recovered}/`
|
PID and cleanup receipts. `formal/{writer,reader}/S4-{boundary,held,recovered}/`
|
||||||
mirrors all four formal members (`journal.log`, `manifest.json`, `thumbnail.png`,
|
mirrors all four formal members (`journal.log`, `manifest.json`, `thumbnail.png`,
|
||||||
@@ -109,10 +106,9 @@ capacity and consumer allocation. Transient `_last_report` is cleared on normal
|
|||||||
restore and is not part of Runtime snapshot equality. The derived
|
restore and is not part of Runtime snapshot equality. The derived
|
||||||
`related_facility_operating` flag is measured separately and checked after restore.
|
`related_facility_operating` flag is measured separately and checked after restore.
|
||||||
|
|
||||||
After successful S4, Root can perform the eight local suites and fresh read-only
|
This source-only dispatch authorizes no worker execution. Root stages the three
|
||||||
R collection described in EVIDENCE_COLLECTION.md. S4 is missing until the actual
|
files, creates the r24 plan with --check-only, then collects once to the fresh
|
||||||
collection succeeds. Subsequent index/cross-gate work is not a source-preparation
|
r24 output. Do not rerun S1-S3, C or full178. No engine copy, signing, xattr,
|
||||||
blocker. Rebind support53cb through its normal registered task against the new
|
codesign call or security-setting change is part of this procedure.
|
||||||
source. Preserve the21615-check proof. Only if exact final1339 evidence is
|
Native evidence, registered validation, independent review, support53cb rebind
|
||||||
required, perform one full run after all fixes, S4, local checks, eight suites
|
and formal acceptance remain pending. D05 remains pending.
|
||||||
and rebind are bundled. Never splice different-version raw or claim1339 identity.
|
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Root-only engine identity controls; tests NOT RUN by worker."""
|
||||||
|
import copy
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
sys.dont_write_bytecode = True
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
spec = importlib.util.spec_from_file_location("series", HERE.parent / "p04_native_series.py")
|
||||||
|
series = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(series)
|
||||||
|
spec = importlib.util.spec_from_file_location("s4", HERE / "run_s4_native.py")
|
||||||
|
s4 = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(s4)
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
# Read only the real installed executable. Never copy or write an engine.
|
||||||
|
pin = s4.engine_pin(s4.ENGINE)
|
||||||
|
identity = dict(before=pin, during=[pin], after=pin)
|
||||||
|
results = []
|
||||||
|
|
||||||
|
def reject(label, action):
|
||||||
|
try:
|
||||||
|
action()
|
||||||
|
except (ValueError, OSError, KeyError, TypeError) as caught:
|
||||||
|
results.append(dict(case=label, result="rejected", error=str(caught)))
|
||||||
|
else:
|
||||||
|
raise AssertionError("negative accepted: " + label)
|
||||||
|
|
||||||
|
# Same bytes/SHA cannot admit an own-B bare copy or another outside-B path.
|
||||||
|
for label, path in [
|
||||||
|
("copied bare engine in B with same SHA", s4.BUILD / "audit-support/p04-s4-source-r24/Godot"),
|
||||||
|
("other outside-B path", Path("/Applications/OtherGodot.app/Contents/MacOS/Godot")),
|
||||||
|
]:
|
||||||
|
other = dict(pin, path=str(path))
|
||||||
|
reject(label, lambda other=other: series.verify_s4_engine(other, identity))
|
||||||
|
reject(label + " wrapper", lambda path=path: s4.engine_pin(path))
|
||||||
|
reject("SHA mismatch", lambda: series.verify_s4_engine(dict(pin, sha256="0" * 64), identity))
|
||||||
|
with patch.object(s4, "ENGINE_SHA256", "0" * 64):
|
||||||
|
reject("wrapper SHA mismatch", lambda: s4.engine_pin(s4.ENGINE))
|
||||||
|
changed = copy.deepcopy(identity)
|
||||||
|
changed["after"]["inode"] += 1
|
||||||
|
reject("after-identity mismatch", lambda: series.verify_s4_engine(pin, changed))
|
||||||
|
reject("wrapper after-identity mismatch",
|
||||||
|
lambda: s4.engine_pin(s4.ENGINE, dict(pin, inode=pin["inode"] + 1)))
|
||||||
|
series.verify_s4_engine(pin, identity)
|
||||||
|
results.append(dict(case="exact installed path", result="accepted"))
|
||||||
|
return dict(scope="read-only engine identity fixtures", results=results, formal_acceptance=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(json.dumps(run(), indent=2, sort_keys=True))
|
||||||
@@ -8,6 +8,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import stat
|
||||||
import sys
|
import sys
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ PRODUCT = BUILD / "audit-support/p04-r17-full-inputs-r01/product"
|
|||||||
CACHE_RUN = BUILD / "p04-final-product-full-r01"
|
CACHE_RUN = BUILD / "p04-final-product-full-r01"
|
||||||
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
TEST = "tests/s3/test_runtime_staff_assignment.gd"
|
||||||
QA = HERE / "p04_s4_power_probe.gd"
|
QA = HERE / "p04_s4_power_probe.gd"
|
||||||
ENGINE = HERE / "Godot"
|
ENGINE = Path("/Applications/Godot.app/Contents/MacOS/Godot")
|
||||||
ENGINE_SHA256 = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
|
ENGINE_SHA256 = "c7cccbf8fb143e34e02fd6521e09be2c2b974f0d5db080b19071c9c570718ccf"
|
||||||
MANIFEST = BUILD / "audit-support/p04-r17-full-inputs-r01/MANIFEST.json"
|
MANIFEST = BUILD / "audit-support/p04-r17-full-inputs-r01/MANIFEST.json"
|
||||||
MANIFEST_SHA = "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8"
|
MANIFEST_SHA = "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8"
|
||||||
@@ -69,24 +70,52 @@ def pin(path):
|
|||||||
data = path.read_bytes()
|
data = path.read_bytes()
|
||||||
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
return dict(path=str(path), bytes=len(data), sha256=hashlib.sha256(data).hexdigest())
|
||||||
|
|
||||||
|
def engine_pin(path, expected=None):
|
||||||
|
path = Path(path)
|
||||||
|
if path != ENGINE or any(p.is_symlink() for p in (path, *path.parents)):
|
||||||
|
raise ValueError("exact installed engine required; no symlink or relocated input")
|
||||||
|
if path.resolve(strict=True) != ENGINE:
|
||||||
|
raise ValueError("installed engine resolved path changed")
|
||||||
|
before = path.stat()
|
||||||
|
if not stat.S_ISREG(before.st_mode):
|
||||||
|
raise ValueError("installed engine must be regular")
|
||||||
|
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||||
|
after = path.stat()
|
||||||
|
fields = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns", "st_mode")
|
||||||
|
if any(getattr(before, k) != getattr(after, k) for k in fields):
|
||||||
|
raise ValueError("engine changed while hashing")
|
||||||
|
if any(p.is_symlink() for p in (path, *path.parents)) or path.resolve(strict=True) != ENGINE:
|
||||||
|
raise ValueError("engine path changed while hashing")
|
||||||
|
value = dict(path=str(path), resolved_path=str(path.resolve(strict=True)),
|
||||||
|
bytes=after.st_size, mtime_ns=after.st_mtime_ns, mode=after.st_mode,
|
||||||
|
device=after.st_dev, inode=after.st_ino, ctime_ns=after.st_ctime_ns, sha256=digest)
|
||||||
|
if digest != ENGINE_SHA256:
|
||||||
|
raise ValueError("engine differs from installed engine constant")
|
||||||
|
if expected is not None and value != expected:
|
||||||
|
raise ValueError("engine identity changed")
|
||||||
|
return value
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
parser.add_argument("--project-root", type=Path, default=PRODUCT)
|
||||||
parser.add_argument("--output", type=Path, default=BUILD / "p04-s4-native-r22")
|
parser.add_argument("--output", type=Path, default=BUILD / "p04-s4-native-r24")
|
||||||
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s4-plan-r23.json")
|
parser.add_argument("--plan", type=Path, default=BUILD / "p04-s4-plan-r24.json")
|
||||||
parser.add_argument("--cache-run", type=Path, default=None,
|
parser.add_argument("--cache-run", type=Path, default=None,
|
||||||
help="optional passed registered cache; default is fresh, without cache access")
|
help="optional passed registered cache; default is fresh, without cache access")
|
||||||
parser.add_argument("--godot", type=Path, default=ENGINE)
|
parser.add_argument("--godot", type=Path, default=ENGINE)
|
||||||
parser.add_argument("--check-only", action="store_true")
|
parser.add_argument("--check-only", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.godot.resolve() != ENGINE.resolve():
|
engine_descriptor = engine_pin(args.godot)
|
||||||
raise ValueError("use the exact own-B staged engine")
|
|
||||||
if args.cache_run is not None and args.cache_run.resolve() != CACHE_RUN.resolve():
|
if args.cache_run is not None and args.cache_run.resolve() != CACHE_RUN.resolve():
|
||||||
raise ValueError("only registered own-B full-run cache")
|
raise ValueError("only registered own-B full-run cache")
|
||||||
root = args.project_root.resolve()
|
root = args.project_root.resolve()
|
||||||
out = args.output.resolve()
|
out = args.output.resolve()
|
||||||
if not out.is_relative_to(BUILD.resolve()) or not args.plan.resolve().is_relative_to(BUILD.resolve()):
|
if not out.is_relative_to(BUILD.resolve()) or not args.plan.resolve().is_relative_to(BUILD.resolve()):
|
||||||
raise ValueError("plan/output must stay in task build")
|
raise ValueError("plan/output must stay in task build")
|
||||||
|
if out.exists() or out.is_symlink():
|
||||||
|
raise ValueError("fresh output required")
|
||||||
|
if args.check_only and (args.plan.exists() or args.plan.is_symlink()):
|
||||||
|
raise ValueError("fresh plan required")
|
||||||
r = module("s4_standard_runner", root / "tools/run_all_tests.py")
|
r = module("s4_standard_runner", root / "tools/run_all_tests.py")
|
||||||
# Preserve the reviewed source_refs -> exactly one QA override order.
|
# Preserve the reviewed source_refs -> exactly one QA override order.
|
||||||
source_before = r.source_refs(root)
|
source_before = r.source_refs(root)
|
||||||
@@ -103,6 +132,10 @@ def main():
|
|||||||
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
selected = dict(root_before=source_before, map={n: p["sha256"] for n, p in refs.items()}, timeout=1200)
|
||||||
cache_note = None
|
cache_note = None
|
||||||
cache_receipts = []
|
cache_receipts = []
|
||||||
|
if args.cache_run is not None:
|
||||||
|
# Reject failed caches before reading their inputs or cache contents.
|
||||||
|
r.require(r.read(args.cache_run / "runner-ended.json")["passed"] is True,
|
||||||
|
"failed cache is ineligible")
|
||||||
for leaf in (["runner-ended.json", "runner-input.json"] if args.cache_run is not None else []):
|
for leaf in (["runner-ended.json", "runner-input.json"] if args.cache_run is not None else []):
|
||||||
path = args.cache_run / leaf
|
path = args.cache_run / leaf
|
||||||
try:
|
try:
|
||||||
@@ -116,29 +149,37 @@ def main():
|
|||||||
QA_overrides=[TEST], fixed_input_pins=fixed,
|
QA_overrides=[TEST], fixed_input_pins=fixed,
|
||||||
cache_run=str(args.cache_run) if args.cache_run is not None else None,
|
cache_run=str(args.cache_run) if args.cache_run is not None else None,
|
||||||
import_mode="cache" if args.cache_run is not None else "fresh",
|
import_mode="cache" if args.cache_run is not None else "fresh",
|
||||||
engine_sha256=ENGINE_SHA256,
|
engine_sha256=ENGINE_SHA256, engine=engine_descriptor,
|
||||||
cache_receipts=cache_receipts, cache_access=cache_note,
|
cache_receipts=cache_receipts, cache_access=cache_note,
|
||||||
phases=["import", "parse", "writer", "reader"],
|
phases=["import", "parse", "writer", "reader"],
|
||||||
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
native_engine_launches=0, whole_goal_accepted=False, unit_accepted=False)
|
||||||
# Check-only only reads source/materials and writes the plan in task build.
|
# Check-only only reads source/materials and writes the plan in task build.
|
||||||
# It deliberately does not read default-player data, inspect processes or stat Godot.
|
# Reads engine identity; never launches Godot or inspects players/processes.
|
||||||
if args.check_only:
|
if args.check_only:
|
||||||
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
args.plan.parent.mkdir(parents=True, exist_ok=True)
|
||||||
if args.plan.exists():
|
r.write(args.plan, plan)
|
||||||
r.require(r.read(args.plan) == plan, "existing plan differs; use a fresh plan path")
|
|
||||||
else:
|
|
||||||
r.write(args.plan, plan)
|
|
||||||
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
print(json.dumps(dict(check_only=True, engines=0, source_files=len(source_before),
|
||||||
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
QA_overrides=[TEST], fixed_pins=len(fixed), plan=str(args.plan),
|
||||||
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
plan_sha256=r.sha(args.plan), cache_access=cache_note), ensure_ascii=False))
|
||||||
return 0
|
return 0
|
||||||
r.require(root == PRODUCT.resolve() and len(source_before) == 1338, "Root native requires product 1338; worker plan is not native evidence")
|
r.require(root == PRODUCT.resolve() and len(source_before) == 1338, "Root native requires product 1338; worker plan is not native evidence")
|
||||||
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
r.require(args.plan.is_file() and r.read(args.plan) == plan, "run exact check-only plan first")
|
||||||
engine_descriptor = None
|
engine_observations = []
|
||||||
|
engine_identity = dict(before=engine_descriptor, during=[], after=None)
|
||||||
|
def repin(label):
|
||||||
|
try:
|
||||||
|
value = engine_pin(args.godot, engine_descriptor)
|
||||||
|
engine_observations.append(dict(stage=label, descriptor=value))
|
||||||
|
if label == "after-cleanup":
|
||||||
|
engine_identity["after"] = value
|
||||||
|
else:
|
||||||
|
engine_identity["during"].append(value)
|
||||||
|
except BaseException as caught:
|
||||||
|
engine_observations.append(dict(stage=label, error=str(caught)))
|
||||||
|
raise
|
||||||
def fixed_guard():
|
def fixed_guard():
|
||||||
|
repin("cleanup-fixed-guard")
|
||||||
template_guard(root)
|
template_guard(root)
|
||||||
if engine_descriptor is not None:
|
|
||||||
r.require(pin(engine_descriptor["path"]) == engine_descriptor, "engine changed during collection")
|
|
||||||
for value in fixed + cache_receipts:
|
for value in fixed + cache_receipts:
|
||||||
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
r.require(pin(value["path"]) == value, "fixed input changed: " + value["path"])
|
||||||
fixed_guard()
|
fixed_guard()
|
||||||
@@ -147,20 +188,16 @@ def main():
|
|||||||
# Native cache admission remains Root-only and fails closed.
|
# Native cache admission remains Root-only and fails closed.
|
||||||
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
r.require(cache_note is None, "Root must read exact cache receipts: " + str(cache_note))
|
||||||
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
r.require(r.read(old / "runner-ended.json")["passed"], "ended cache run must have passed")
|
||||||
old_map = r.read(old / "runner-input.json")["selected_source_map"]
|
cache_input = r.read(old / "runner-input.json")
|
||||||
|
r.require(cache_input["engine_sha256"] == ENGINE_SHA256, "cache engine identity")
|
||||||
|
old_map = cache_input["selected_source_map"]
|
||||||
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
current_map = {n: p["sha256"] for n, p in source_before.items()}
|
||||||
r.require({k: v for k, v in old_map.items() if k != "tools/check_p04_order_evidence.py"} == current_map and len(old_map) == 1339, "immutable full cache: all 1338 product inputs equal; checker is not parsed/executed by product suites")
|
r.require({k: v for k, v in old_map.items() if k != "tools/check_p04_order_evidence.py"} == current_map and len(old_map) == 1339, "immutable full cache: all 1338 product inputs equal; checker is not parsed/executed by product suites")
|
||||||
protected = r.default_player(root)
|
protected = r.default_player(root)
|
||||||
outer = module("s4_owned_outer", r.SUPPORT / "owned_outer.py")
|
outer = module("s4_owned_outer", r.SUPPORT / "owned_outer.py")
|
||||||
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
r.require(not outer.native.helper["live_engines"](), "another Godot owns CPU")
|
||||||
engine = args.godot.resolve()
|
engine = ENGINE
|
||||||
r.require(engine.is_file(), "Root native Godot path")
|
repin("native-admission")
|
||||||
engine_descriptor = pin(engine)
|
|
||||||
r.require(engine_descriptor["sha256"] == ENGINE_SHA256,
|
|
||||||
"exact reviewed staged engine; no environment substitution")
|
|
||||||
if old is not None:
|
|
||||||
r.require(engine_descriptor["sha256"] == r.read(old / "runner-input.json")["engine_sha256"],
|
|
||||||
"staged engine matches preserved full proof; no environment substitution")
|
|
||||||
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
r.require(shutil.disk_usage(out.parent).free >= 1024**3 + engine.stat().st_size + 136*1024**2,
|
||||||
"reviewed native disk admission")
|
"reviewed native disk admission")
|
||||||
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
r.require(not out.exists(), "fresh output required; preserve previous attempts")
|
||||||
@@ -225,7 +262,7 @@ def main():
|
|||||||
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
r.require(TEST not in policy["suite_policies"], "no normal suite watchdog/policy override")
|
||||||
r.write(out / "contracts.json", contracts)
|
r.write(out / "contracts.json", contracts)
|
||||||
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
r.write(out / "runner-input.json", dict(plan, owner_runner_pid=os.getpid(),
|
||||||
wrapper_sha256=r.sha(__file__), QA=pin(QA), engine=engine_descriptor, runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
wrapper_sha256=r.sha(__file__), QA=pin(QA), engine=engine_descriptor, engine_identity=engine_identity, runner_sha256=r.sha(root / "tools/run_all_tests.py"),
|
||||||
default_player_before=protected, fixed_input_pins=fixed,
|
default_player_before=protected, fixed_input_pins=fixed,
|
||||||
functional_watchdog_seconds=1200, source_adoption=False))
|
functional_watchdog_seconds=1200, source_adoption=False))
|
||||||
diagnostics = module("s4_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
diagnostics = module("s4_diagnostics", r.SUPPORT / "diagnostic_gate.py")
|
||||||
@@ -240,6 +277,8 @@ def main():
|
|||||||
if phase == "import" and old is None:
|
if phase == "import" and old is None:
|
||||||
r.require(not (stage / ".godot").exists() and not (stage / ".godot").is_symlink(),
|
r.require(not (stage / ".godot").exists() and not (stage / ".godot").is_symlink(),
|
||||||
"fresh private .godot absent at actual import")
|
"fresh private .godot absent at actual import")
|
||||||
|
repin("before-" + phase)
|
||||||
|
r.write(out / "engine-identity.json", engine_observations)
|
||||||
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
row = r.execute_phase(str(engine), stage, out, phase, test, selected, expected,
|
||||||
previous, policy, contracts, paths, public, protected, root,
|
previous, policy, contracts, paths, public, protected, root,
|
||||||
outer, markers, diagnostics, owners, audio)
|
outer, markers, diagnostics, owners, audio)
|
||||||
@@ -260,6 +299,7 @@ def main():
|
|||||||
"exact one independent Writer/Reader case")
|
"exact one independent Writer/Reader case")
|
||||||
r.require(sorted(receipt["slots"]) == SLOTS, "all formal point slots")
|
r.require(sorted(receipt["slots"]) == SLOTS, "all formal point slots")
|
||||||
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
r.require(receipt["pid"] == row["actual_pid"], "actual native PID/receipt")
|
||||||
|
repin("after-collection")
|
||||||
writer = r.read(out / "raw/writer.json")
|
writer = r.read(out / "raw/writer.json")
|
||||||
reader = r.read(out / "raw/reader.json")
|
reader = r.read(out / "raw/reader.json")
|
||||||
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
r.require(writer["pid"] != reader["pid"] and writer["profile"] != reader["profile"], "distinct formal processes")
|
||||||
@@ -268,11 +308,19 @@ def main():
|
|||||||
except BaseException as caught:
|
except BaseException as caught:
|
||||||
error = type(caught).__name__ + ": " + str(caught)
|
error = type(caught).__name__ + ": " + str(caught)
|
||||||
finally:
|
finally:
|
||||||
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
try:
|
||||||
|
cleanup, cleanup_errors = outer.finish_captured(outer.OWNED)
|
||||||
|
except BaseException as caught:
|
||||||
|
cleanup_errors.append(str(caught))
|
||||||
try:
|
try:
|
||||||
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
r.write(out / "outer-owned-finally.json", dict(cleanup=cleanup, errors=cleanup_errors))
|
||||||
except BaseException as caught:
|
except BaseException as caught:
|
||||||
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
guards.append("finally receipt: " + type(caught).__name__ + ": " + str(caught))
|
||||||
|
# Explicit re-pin after cleanup is independent of other fixed-input guards.
|
||||||
|
try:
|
||||||
|
repin("after-cleanup")
|
||||||
|
except BaseException as caught:
|
||||||
|
guards.append(type(caught).__name__ + ": " + str(caught))
|
||||||
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
# Fixed guard always follows ownership cleanup, even if setup or an engine failed.
|
||||||
try:
|
try:
|
||||||
fixed_guard()
|
fixed_guard()
|
||||||
@@ -318,6 +366,19 @@ def main():
|
|||||||
r.write(out / "formal-relocation.json", relocated)
|
r.write(out / "formal-relocation.json", relocated)
|
||||||
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
r.write(out / "raw-inventory.json", r.inventory(out / "raw"))
|
||||||
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
r.write(out / "PID-set.json", dict(owner_runner_pid=os.getpid(), engine_PIDs=pids))
|
||||||
|
try:
|
||||||
|
repin("after-output-collection")
|
||||||
|
except BaseException as caught:
|
||||||
|
guards.append(str(caught))
|
||||||
|
passed = False
|
||||||
|
execution["passed"] = False
|
||||||
|
r.write(out / "engine-identity.json", engine_observations)
|
||||||
|
execution["engine_observations"] = engine_observations
|
||||||
|
execution["engine_identity"] = engine_identity
|
||||||
|
if (out / "runner-input.json").is_file():
|
||||||
|
final_inputs = r.read(out / "runner-input.json")
|
||||||
|
final_inputs["engine_identity"] = engine_identity
|
||||||
|
r.write(out / "runner-input.json", final_inputs)
|
||||||
r.write(out / "execution.json", execution)
|
r.write(out / "execution.json", execution)
|
||||||
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
r.write(out / "runner-ended.json", dict(execution, ended=r.now()))
|
||||||
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
print(json.dumps(dict(passed=passed, error=error, guard_errors=guards, output=str(out))))
|
||||||
|
|||||||
Reference in New Issue
Block a user