P04-01: 同時発生の更新規約

This commit is contained in:
Agent Control Runner
2026-10-16 06:12:33 +09:00
parent accd07c91d
commit b543c26c95
19 changed files with 3076 additions and 24 deletions
@@ -0,0 +1,92 @@
# REPAIR17 — blocked on required raw access
This is not accepted or completed. Stage R is not implemented. No new condition
is claimed as proven. This document records an observed input-access blocker,
not a replacement implementation or a proposal to trust Root summaries.
## Checkout and scope
- Observed HEAD: `accd07c91d4e34d9b7c7fc20e389df2424b167e1` (the requested reused checkout).
- Registered contract base: `deb1bcb2374eb378b840ca345db952bd8572cb6e`.
- Only this status file was created. The checker, `verify_native`, C checks,
existing fixtures, probes, originals, and logs were not modified.
- No git writes, Runner operations, engine runs, or other agents were used.
## Reproduced blocker
Let `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
The four `B/<series>/raw/{writer,reader}.json` files are readable. Their literal
`slots` mappings locate the formal packages. Attempting `Path.read_bytes()` on
every `journal.log`, `manifest.json`, `thumbnail.png`, and `world.bin` in those
slots returned `PermissionError: [Errno 1] Operation not permitted`:
| Series | Slots per role | Unreadable original + copy members |
| --- | ---: | ---: |
| `p04-s1-native-r02` | 14 | 112 |
| `p04-s2a-native-r03` | 1 | 8 |
| `p04-s2b-native-r02` | 6 | 48 |
| `p04-s3-native-r01` | 22 | 176 |
For example, this exact required S3 original cannot be read:
```text
/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-078167a92c6e4888a6b4ee4a005fd858-standard-runtime-writer/saves/p04_s3/B1-after/world.bin
```
Its Reader counterpart is also inaccessible:
```text
/Users/korizatou114/Library/Application Support/TheTree-PCK-tests-b1fd1956bd28493cbac1e00f3a775232-standard-runtime-reader/saves/p04_s3/B1-after/world.bin
```
Searching each series directory for `world.bin` found only the unrelated
`stage/tests/fixtures/required_kitchen_legacy14/formal-package/world.bin`.
There is no `.review-material` directory in this checkout. Root result SHA
fields cannot replace re-hashing the actual original and Reader member bytes.
Current-game source access also fails: reading the first source_before entry,
`/Users/korizatou114/Projects/とりにく工房ゲーム制作部/AGENTS.md`, returned the same error.
Each run's fixed_input_pins contains three inaccessible entries. The remaining
entries re-hashed successfully (S1: 13, S2a: 14, S2b: 10, S3: 15).
Each staged source tree has 1337 hashes matching source_before and one differing
test script (`tests/s3/test_runtime_staff_assignment.gd`); this is not a proof
that the current game's full 1338-file source set still matches.
Required unblock: make these declared original inputs readable to the worker,
or supply an authorized, provenance-bound relocation of their full bytes,
including both formal roles and the current-game source/fixed inputs. No new
engine run is requested. Permission denial must not be interpreted as missing
files, matching hashes, or failed product behavior.
## Evidence and remaining conditions
Readable raw locations were inspected under all four series, including
runner-input/source_before, lifecycle receipts, raw inventory, and raw Writer
and Reader records. D05 supplement bytes were read. ROOT result files were
used to inspect locators, never as proof of passing conditions.
- S1: EV remains unverified in this run; no paid-service power claim.
- S2a: midnight_settlement, midnight_replay, save_restore remain unverified.
- S2b: night_staff_absent, overtime_task and same-minute rejection remain unverified.
- S3: closure_release, exit_release, patience_release and B4 release-once remain unverified.
- power_stop, destruction_stop, shift_completion, same_minute_replay remain unverified.
- Existing C verification was neither changed nor rerun; no fresh C result is claimed.
- Required native-Windows conditions remain pending individually; this run supplies no Windows proof.
Stage A remains pending: actual HEAD + filesystem candidate binding, the known
`MAIN_ANCHOR` `9694...`, candidate_tree trust, and `3139` metadata holes are
untouched. No final index or composition was generated.
## Footprint and verification
Read-only `df -h` reported about 3.1 GiB available. No evidence payload was
copied. `B/audit-support/p04-raw-collect-r17/` did not exist at the access audit
and was not created. A trustworthy collection footprint cannot be finalized
without access to the required member bytes; no collector was run.
Only Python read/hash/access diagnostics were executed. Fixture suites were
not run and are not reported as passing. `p04_native_series.py`,
`collect_p04_native.py`, and `checker_series_fixtures.py` were not created, so
there are no valid Root collector/new-fixture commands to advertise yet.
Implementation and its required positive/adversarial checks remain outstanding
after the required inputs become readable.
@@ -0,0 +1,2 @@
{"estimated_payload_bytes": 2970454, "files": 5, "available_disk_bytes": 4596576256, "large_payloads_copied": 0}
{"collection": "/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build/audit-support/p04-raw-collect-r17", "manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "actual_payload_bytes": 2970454, "formal_acceptance": false}
@@ -0,0 +1 @@
{"C_provenance_sha256": "51d746879b4197445a09cd44a88dd918c365fa98025d9cd652a740cee09a9040", "C_source_manifest_sha256": "dbaab3c293c191eb5f3aff34854945e2d564f824df50a92802b3a7eda55321a8", "error": "candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion", "formal_acceptance": false, "machine_verified": false, "missing_conditions": ["power_stop", "save_restore", "shift_completion"], "nativeWindows": {"EV": "pending", "closure_release": "pending", "dependency_stop": "pending", "destruction_stop": "pending", "exit_release": "pending", "midnight_replay": "pending", "midnight_settlement": "pending", "night_staff_absent": "pending", "overtime_task": "pending", "patience_release": "pending", "paused_midnight": "pending", "power_stop": "pending", "recovery_completion": "pending", "same_minute_replay": "pending", "save_restore": "pending", "shift_completion": "pending"}, "relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8", "series_bindings": {"s1": "9792569af9c8ecef177ffdeb880b814fb2878231f23e77c3749e18c228ff0ceb", "s2a": "746b58cf1aff95b082c35f6cc2c29556f146a7c56cce0475f4f2c091168605a7", "s2b": "e6d5412c14809825cf368af1d93972bc9c4e41f4c09afa414732b007c9cc7589", "s3": "a9b4b80d54c18ae2a1dc82514052e22d6174ff79999c76fa622282709759b433"}, "series_manifest_sha256": "953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975", "stage_A": "candidate binding stage A pending", "task_id": "P04-01", "verified_conditions": ["EV", "closure_release", "dependency_stop", "destruction_stop", "exit_release", "midnight_replay", "midnight_settlement", "night_staff_absent", "overtime_task", "patience_release", "paused_midnight", "recovery_completion", "same_minute_replay"]}
@@ -0,0 +1,153 @@
{
"s1": {
"facts": {
"paid_service_power_claim": false
},
"formal_acceptance": false,
"owned_pids": [
64745,
64788,
64818,
64850
],
"phase_pairs": 0,
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
"raw_reference_count": 97,
"relocated_reference_count": 1464,
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
"series": "s1",
"source_files": 1338,
"verified_conditions": [
"EV"
],
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
},
"s2a": {
"facts": {
"paid_originals": {
"completed_service_id": "service_00000027",
"same_minute_replay": "request resend, not advance_to settlement replay",
"shift_completion": "missing: zero SERVICE_SALE rows at 1080",
"transaction_id": "ledger_00000085"
},
"payroll_gold": 255,
"raw_getter_bytes_equal": false,
"sales_gold": 25,
"save_restore_gap": "Raw getter also changes typed arrays to untyped arrays; authorized normalization permits only StringName/String and ordering",
"settlement_replay_is_request_resend": false,
"upkeep_gold": 5
},
"formal_acceptance": false,
"owned_pids": [
85229,
85263,
85303,
85425
],
"phase_pairs": 0,
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
"raw_reference_count": 486,
"relocated_reference_count": 1362,
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
"series": "s2a",
"source_files": 1338,
"verified_conditions": [
"midnight_replay",
"midnight_settlement",
"same_minute_replay"
],
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
},
"s2b": {
"facts": {
"same_minute_rejection": "MINUTE_ALREADY_APPLIED"
},
"formal_acceptance": false,
"owned_pids": [
98696,
98744,
98770,
98810
],
"phase_pairs": 12,
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
"raw_reference_count": 163,
"relocated_reference_count": 1397,
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
"series": "s2b",
"source_files": 1338,
"verified_conditions": [
"night_staff_absent",
"overtime_task"
],
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
},
"s3": {
"facts": {
"B1": {
"amount": 0,
"reservation_states": [
"ACTIVE",
"RELEASED"
],
"sales": 0
},
"B2a": {
"amount": 25,
"reservation_states": [
"CONSUMED"
],
"sales": 1
},
"B2b": {
"amount": 0,
"reservation_states": [
"RELEASED"
],
"sales": 0
},
"B3": {
"amount": 0,
"reservation_states": [
"RELEASED"
],
"sales": 0
},
"B4a": {
"amount": 25,
"reservation_states": [
"CONSUMED"
],
"sales": 1
},
"B4b": {
"amount": 0,
"reservation_states": [
"RELEASED"
],
"sales": 0
}
},
"formal_acceptance": false,
"owned_pids": [
10448,
10493,
10523,
10578
],
"phase_pairs": 352,
"product_content": "5f6bc11d6bb6e92d22d787989b219fef3a435bc1",
"raw_reference_count": 891,
"relocated_reference_count": 1530,
"relocation_manifest_sha256": "a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8",
"series": "s3",
"source_files": 1338,
"verified_conditions": [
"closure_release",
"destruction_stop",
"exit_release",
"patience_release"
],
"verifier_sha256": "61f6647be4d2ec95db0599b94a3c6acb5f91ca43501256548f5ae161ab2f0e4f"
}
}
@@ -0,0 +1,367 @@
{
"formal_acceptance": false,
"results": [
{
"class": "small_synthetic_positive",
"result": "pass",
"series": "shared"
},
{
"class": "full_body_positive",
"result": "pass",
"series": "s1"
},
{
"class": "wrong_source_pin",
"error": "SHA drift: native /native/body.gd",
"result": "rejected",
"series": "s1"
},
{
"class": "signature_only_pin",
"error": "full-function-body pin mismatch",
"result": "rejected",
"series": "s1"
},
{
"class": "symlink",
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
"result": "rejected",
"series": "s1"
},
{
"class": "traversal",
"error": "raw traversal",
"result": "rejected",
"series": "s1"
},
{
"class": "unmapped_original_path",
"error": "unmapped original path: /native/unknown.gd",
"result": "rejected",
"series": "s1"
},
{
"class": "relocated_manifest_native_mismatch",
"error": "byte length drift: native /native/body.gd",
"result": "rejected",
"series": "s1"
},
{
"class": "relocated_byte_manifest_mismatch",
"error": "SHA drift: relocation /native/body.gd",
"result": "rejected",
"series": "s1"
},
{
"class": "JSON_only_getter_substitute",
"error": "unsupported Variant flags 8827:25959 at 0",
"result": "rejected",
"series": "s1"
},
{
"class": "forged_scalar",
"error": "SHA drift: p04-s1-native-r02/raw/writer.json",
"result": "rejected",
"series": "s1"
},
{
"class": "changed_raw_byte",
"error": "SHA drift: p04-s1-native-r02/raw/writer.json",
"result": "rejected",
"series": "s1"
},
{
"class": "formal_member_SHA_drift",
"error": "SHA drift: p04-s1-native-r02/execution.json",
"result": "rejected",
"series": "s1"
},
{
"class": "unowned_missing_PID_end",
"error": "missing PID end",
"result": "rejected",
"series": "s1"
},
{
"class": "altered_phase_pair",
"error": "SHA drift: p04-s1-native-r02/raw/reader-landing-held.var",
"result": "rejected",
"series": "s1"
},
{
"class": "relabeled_interruption_reason",
"error": "SHA drift: p04-s1-native-r02/raw/writer-landing-depleted.json",
"result": "rejected",
"series": "s1"
},
{
"class": "full_body_positive",
"result": "pass",
"series": "s2a"
},
{
"class": "wrong_source_pin",
"error": "SHA drift: native /native/body.gd",
"result": "rejected",
"series": "s2a"
},
{
"class": "signature_only_pin",
"error": "full-function-body pin mismatch",
"result": "rejected",
"series": "s2a"
},
{
"class": "symlink",
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
"result": "rejected",
"series": "s2a"
},
{
"class": "traversal",
"error": "raw traversal",
"result": "rejected",
"series": "s2a"
},
{
"class": "unmapped_original_path",
"error": "unmapped original path: /native/unknown.gd",
"result": "rejected",
"series": "s2a"
},
{
"class": "relocated_manifest_native_mismatch",
"error": "byte length drift: native /native/body.gd",
"result": "rejected",
"series": "s2a"
},
{
"class": "relocated_byte_manifest_mismatch",
"error": "SHA drift: relocation /native/body.gd",
"result": "rejected",
"series": "s2a"
},
{
"class": "JSON_only_getter_substitute",
"error": "unsupported Variant flags 8827:25959 at 0",
"result": "rejected",
"series": "s2a"
},
{
"class": "forged_scalar",
"error": "SHA drift: p04-s2a-native-r03/raw/writer.json",
"result": "rejected",
"series": "s2a"
},
{
"class": "changed_raw_byte",
"error": "SHA drift: p04-s2a-native-r03/raw/writer.json",
"result": "rejected",
"series": "s2a"
},
{
"class": "formal_member_SHA_drift",
"error": "SHA drift: p04-s2a-native-r03/execution.json",
"result": "rejected",
"series": "s2a"
},
{
"class": "unowned_missing_PID_end",
"error": "missing PID end",
"result": "rejected",
"series": "s2a"
},
{
"class": "altered_phase_pair",
"error": "SHA drift: p04-s2a-native-r03/raw/reader-C3-recall.var",
"result": "rejected",
"series": "s2a"
},
{
"class": "relabeled_interruption_reason",
"error": "SHA drift: p04-s2a-native-r03/raw/writer-C1-midnight.json",
"result": "rejected",
"series": "s2a"
},
{
"class": "full_body_positive",
"result": "pass",
"series": "s2b"
},
{
"class": "wrong_source_pin",
"error": "SHA drift: native /native/body.gd",
"result": "rejected",
"series": "s2b"
},
{
"class": "signature_only_pin",
"error": "full-function-body pin mismatch",
"result": "rejected",
"series": "s2b"
},
{
"class": "symlink",
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
"result": "rejected",
"series": "s2b"
},
{
"class": "traversal",
"error": "raw traversal",
"result": "rejected",
"series": "s2b"
},
{
"class": "unmapped_original_path",
"error": "unmapped original path: /native/unknown.gd",
"result": "rejected",
"series": "s2b"
},
{
"class": "relocated_manifest_native_mismatch",
"error": "byte length drift: native /native/body.gd",
"result": "rejected",
"series": "s2b"
},
{
"class": "relocated_byte_manifest_mismatch",
"error": "SHA drift: relocation /native/body.gd",
"result": "rejected",
"series": "s2b"
},
{
"class": "JSON_only_getter_substitute",
"error": "unsupported Variant flags 8827:25959 at 0",
"result": "rejected",
"series": "s2b"
},
{
"class": "forged_scalar",
"error": "SHA drift: p04-s2b-native-r02/raw/writer.json",
"result": "rejected",
"series": "s2b"
},
{
"class": "changed_raw_byte",
"error": "SHA drift: p04-s2b-native-r02/raw/writer.json",
"result": "rejected",
"series": "s2b"
},
{
"class": "formal_member_SHA_drift",
"error": "SHA drift: p04-s2b-native-r02/execution.json",
"result": "rejected",
"series": "s2b"
},
{
"class": "unowned_missing_PID_end",
"error": "missing PID end",
"result": "rejected",
"series": "s2b"
},
{
"class": "altered_phase_pair",
"error": "SHA drift: p04-s2b-native-r02/raw/reader-night_absent-1080-before-facility.var",
"result": "rejected",
"series": "s2b"
},
{
"class": "relabeled_interruption_reason",
"error": "SHA drift: p04-s2b-native-r02/raw/writer-night_absent-1081.json",
"result": "rejected",
"series": "s2b"
},
{
"class": "full_body_positive",
"result": "pass",
"series": "s3"
},
{
"class": "wrong_source_pin",
"error": "SHA drift: native /native/body.gd",
"result": "rejected",
"series": "s3"
},
{
"class": "signature_only_pin",
"error": "full-function-body pin mismatch",
"result": "rejected",
"series": "s3"
},
{
"class": "symlink",
"error": "symlink: /Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/P04-01-implement/docs/plans/original-completion-2026-10-02/execution/p04-01/.series-fixtures-ici55vnl/symlink.gd",
"result": "rejected",
"series": "s3"
},
{
"class": "traversal",
"error": "raw traversal",
"result": "rejected",
"series": "s3"
},
{
"class": "unmapped_original_path",
"error": "unmapped original path: /native/unknown.gd",
"result": "rejected",
"series": "s3"
},
{
"class": "relocated_manifest_native_mismatch",
"error": "byte length drift: native /native/body.gd",
"result": "rejected",
"series": "s3"
},
{
"class": "relocated_byte_manifest_mismatch",
"error": "SHA drift: relocation /native/body.gd",
"result": "rejected",
"series": "s3"
},
{
"class": "JSON_only_getter_substitute",
"error": "unsupported Variant flags 8827:25959 at 0",
"result": "rejected",
"series": "s3"
},
{
"class": "forged_scalar",
"error": "SHA drift: p04-s3-native-r01/raw/writer.json",
"result": "rejected",
"series": "s3"
},
{
"class": "changed_raw_byte",
"error": "SHA drift: p04-s3-native-r01/raw/writer.json",
"result": "rejected",
"series": "s3"
},
{
"class": "formal_member_SHA_drift",
"error": "SHA drift: p04-s3-native-r01/execution.json",
"result": "rejected",
"series": "s3"
},
{
"class": "unowned_missing_PID_end",
"error": "missing PID end",
"result": "rejected",
"series": "s3"
},
{
"class": "altered_phase_pair",
"error": "SHA drift: p04-s3-native-r01/raw/reader-B1-phase-960-TIME_BOUNDARIES.var",
"result": "rejected",
"series": "s3"
},
{
"class": "relabeled_interruption_reason",
"error": "SHA drift: p04-s3-native-r01/raw/writer-B1-after.json",
"result": "rejected",
"series": "s3"
}
],
"scope": "file-only Stage R controls; immutable descriptor and primitive negatives"
}
@@ -0,0 +1,211 @@
# REPAIR18 — executable Stage R, blocked on remaining conditions
This is **not accepted or completed**. Executable R artifacts and a reference-only
collection were produced and the file-only checks were run. The whole gate
fails. Original completion credit remains **13/88**, with no additional credit.
The repair counter and `max_repair_attempts: null` were not changed.
## Scope and preservation
- Checkout: `accd07c91d4e34d9b7c7fc20e389df2424b167e1`; registered base:
`deb1bcb2374eb378b840ca345db952bd8572cb6e`.
- Changed `tools/check_p04_order_evidence.py` only to add R composition and its
separate read-only CLI, call composition from the whole missing-check, and
retain an unconditional Stage A failure.
- Created `p04_native_series.py`, `collect_p04_native.py`,
`checker_series_fixtures.py`, this status, and the four output files listed below.
- All existing checker functions except `verify` and `main` were compared with
HEAD and are byte-unchanged, including `verify_native` and all C helpers.
All three existing fixture files are byte-unchanged. Their failures were not
hidden by editing assertions or Stage A code.
- `REPAIR17_STATUS.md` was never written. Its SHA256 is
`6497c335739f266c9026aa6499cf5c49ae80d590d6aa2d0f90d32adc08018061`.
- No git writes, new worktree/branch, agents, Runner operations, engine runs,
full regression, permission requests, production edits, input edits, or final
`B/p04-evidence/index.json` writes occurred. This did not revalidate accd.
## Inputs and actual collection
Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`,
`R=B/audit-support/p04-r17-full-inputs-r01`, and
`E=docs/plans/original-completion-2026-10-02/execution/p04-01`.
The first input identity check verified `R/MANIFEST.json`: **1,244,135 bytes**,
SHA256 `a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8`.
All **1,721** relocated members were used and rehashed across the series,
covering 71,488,772 logical bytes. All four complete native `source_before`
sets equal the supplied 1,338-file product set at content anchor
`5f6bc11d6bb6e92d22d787989b219fef3a435bc1`. The one native QA override is checked
separately against its supplied probe bytes; it is not represented as product
content. Full-function-body pins include trailing comments and declarations.
External original paths remain provenance strings. The resolver uses exact
`original_to_relocated` keys, and checks each used file against both native
descriptors and relocation descriptors. There is no prefix substitution or
external-path fallback. Formal slot membership, directory membership, separate
Writer/Reader paths and inodes, symlinks, traversal and duplicates are checked.
All **344** formal members in **86** original/Reader slots were rehashed.
The collector created only `B/audit-support/p04-raw-collect-r17/`. It did not
exist before creation. It contains four series reference manifests and one
collection manifest; no large evidence snapshots were copied.
Actual collector output (exit 0):
```json
{"estimated_payload_bytes":2970454,"files":5,"available_disk_bytes":4596576256,"large_payloads_copied":0}
{"actual_payload_bytes":2970454,"manifest_sha256":"953a798831043abdf7d54909e8e041cb3fbe6d901ca8affa661ea28e62ca5975","formal_acceptance":false}
```
The estimate was printed before directory creation or output writes. The
collection is deterministic and refuses an existing destination on rerun.
Per-series manifests bind native raw references, relocation references, and
verifier bytes by SHA. Composition reruns C and all series verifiers and
compares the recomputed results and references with those manifests; editing a
`verified_conditions` list cannot add credit.
## Conditions derived from literal raw
| Verifier | Native raw | Relocated formal members | Conditions proved |
| --- | --- | --- | --- |
| S1 | `B/p04-s1-native-r02/raw/` | `R/formal/p04-s1-native-r02/{writer,reader}/` | `EV` |
| S2a | `B/p04-s2a-native-r03/raw/` | `R/formal/p04-s2a-native-r03/{writer,reader}/C3/` | `midnight_settlement`, `midnight_replay` |
| S2b | `B/p04-s2b-native-r02/raw/` | `R/formal/p04-s2b-native-r02/{writer,reader}/` | `night_staff_absent`, `overtime_task` |
| S3 | `B/p04-s3-native-r01/raw/` | `R/formal/p04-s3-native-r01/{writer,reader}/` | `closure_release`, `exit_release`, `patience_release`, `destruction_stop` (B4b) |
| Paid/resend supplement, collected with S2a | `B/audit-support/p04-current-native-series-r01/p04-idempotency-paid-midnight-native-r01/order-evidence/` | Manifest pin resolved through `R/MANIFEST.json`, full raw already inside B | `same_minute_replay` (request resend) |
| Unchanged C | `B/audit-support/native-first-current/` | Existing C bundle, unchanged | `dependency_stop`, `recovery_completion`, `paused_midnight` |
All product reads resolve to `R/product/` through manifest keys. Fixed-input
relocations and every original formal provenance string are retained in the
four generated series manifests; they are not replaced with guessed paths.
Owned import/parse/Writer/Reader PIDs checked against starts, ends, owners,
commands, raw stream hashes, exit gates and cleanup/guard records:
| Series | PIDs | Formal slots per role | Exact raw phase pairs |
| --- | --- | ---: | ---: |
| S1 | 64745, 64788, 64818, 64850 | 14 | 0 (14 exact typed snapshot restore points instead) |
| S2a | 85229, 85263, 85303, 85425 | 1 | 0 (typed settlement/replay and formal Runtime restore comparisons) |
| S2b | 98696, 98744, 98770, 98810 | 6 | 12 |
| S3 | 10448, 10493, 10523, 10578 | 22 | 352 JSON/typed phase files |
- S1 checks task/trip/resource identity, safe landing, no-safe-landing hold,
preserved reservation identity and release. It makes no paid-service power claim.
- S2a derives a 25G sale/one transaction, 255G payroll/one transaction
(Akane 120, Bardo 135 joined to actual staff and shifts), and 5G upkeep in
three rows/one transaction. `advance_to(1440)` has processed minutes 0 and
daily closes 0, with entire typed Runtime bytes unchanged. Formal Reader
Runtime restore and recall bytes match exactly.
- The supplemental request-resend raw preserves typed economy, facilities,
inventory, guests and staff at the same minute, and joins the one completed
service `service_00000027` to sale transaction `ledger_00000085`. This is not
settlement-replay proof; S2a supplies that distinct proof.
- S2b derives `STAFF_UNAVAILABLE`/`WAITING_FOR_STAFF`, the absent night workers,
overtime task/staff/shift joins and one duty minute. Its same-minute call is
rejected as `MINUTE_ALREADY_APPLIED`, with entire typed Runtime unchanged.
- S3 retains `RELATED_FACILITY_INACTIVE` in B1, never relabeling it `NO_POWER`.
The directly closed facility retains its started service while the dependent
facility interruption pauses it; the queued reservation is released.
B2a completes/consumes once; B2b and B3 release without a sale.
B4b's queued event completes, durability becomes 0/`BROKEN`, then the service
fails with `BROKEN` at 961, releases its reservation and has zero sale rows.
## Unresolved raw/specification gaps
1. **`power_stop` missing.** EV evidence is not a paid-service power proof;
B1 is a dependency interruption. No relabeling or invented D05 rule was used.
2. **`shift_completion` missing.** The actual supplied paid-shift originals at
`B/audit-support/p04-current-native-series-r01/p04-paid-shift-midnight-native-r01/order-evidence/`
have zero `SERVICE_SALE` rows in all three 17:59/completion-phase/18:00
snapshots. The `paid` filenames do not establish a charged completion at 1080.
3. **`save_restore` withheld under the fixed normalization restriction.** The
entire formal Runtime does restore exactly. However, the getter comparison
changes not only StringName/String and dictionary order but typed arrays to
untyped arrays. Examples are `/anomalies`, `/anomalies/0/allocations`,
`/anomalies/0/unallocated_by_staff`, `/anomalies/0/facility_ids`,
`/settlement/input_payments` and `/settlement/rows`. Native array headers
include `1c000100...` versus `1c000000...`. The raw getter byte inequality is
preserved and parsed from `.var`; JSON is not substituted. Normalization was
not broadened beyond the instruction, so this condition remains missing.
4. **B4a zero-charge assertion is unsupported.**
`B/p04-s3-native-r01/raw/writer-B4a-next.{json,var}` contains one 25G sale at
960 and a completed service with consumed reservation. B4b has zero sales.
The implementation records both facts, rather than declaring B4a charge 0.
Destruction-stop credit above is specifically the B4b failed-service branch.
D05 approval and supplement full bytes in `p04-whole-closure-r01` were read.
They approve dependency closure and the 18:00 ordering choice, not an invented
blanket policy for all D05 domains. The supplied raw discrepancies require
owner-level resolution or appropriate evidence; this worker did not amend scope.
Each of these native-Windows conditions remains individually **pending**:
`EV`, `power_stop`, `destruction_stop`, `dependency_stop`, `shift_completion`,
`recovery_completion`, `same_minute_replay`, `closure_release`, `exit_release`,
`patience_release`, `paused_midnight`, `midnight_settlement`, `midnight_replay`,
`save_restore`, `night_staff_absent`, `overtime_task`. The machine output also
contains an explicit per-condition pending map.
## Actual validation outputs
- Static Python compilation: **4 files passed** (in-memory compile; no engine).
- Existing C fixture: **exit 0**, 5 positive controls passed and 25 negatives
rejected. `verify_native` reports 10 owned processes and its original three
conditions. No C condition names were edited.
- Existing negative fixture: **exit 1**, at `synthetic_scalar_only_rejected`:
`AssertionError: ('synthetic_scalar_only_rejected', {'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': "'product_binding'"})`.
- Existing applicability fixture: **exit 1**:
`AssertionError: negative fixture accepted: wrong_tested_revision`.
These two suites therefore could not be confirmed to pass/reject as requested.
Their files and the functions involved were unchanged; they were not repaired
by touching the deferred A holes, and no baseline rerun is claimed.
- New series fixtures: **exit 0**, **5 small positive controls and 56 rejected
negatives**, 14 negative classes per series. Controls cover immutable raw and
descriptor overlays, source/body pins, PID endings, paths, relocation byte
drift and typed-vs-JSON rejection. Immutable descriptor hashes are deliberately
not refreshed; the tests do not claim to be new engine evidence.
- Real series verifiers ran successfully during collection and were rerun by
composition. Outputs are recorded in `REPAIR18_REAL_SERIES_OUTPUT.json`.
- Separate composition CLI: **exit 1**, with 13 raw-derived conditions and:
```text
candidate binding stage A pending; uncollected original boundaries: power_stop, save_restore, shift_completion
```
Full new outputs: `REPAIR18_COLLECTOR_OUTPUT.jsonl`,
`REPAIR18_SERIES_FIXTURE_OUTPUT.json`, `REPAIR18_REAL_SERIES_OUTPUT.json`,
`REPAIR18_COMBINED_OUTPUT.json`. The 13 verified boundary conditions are not
13 newly accepted units and do not change the original 13/88 credit.
## Exact Root commands
Run from this existing checkout. These commands perform file-only work:
```sh
B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
E=docs/plans/original-completion-2026-10-02/execution/p04-01
python3 -B "$E/collect_p04_native.py" --build "$B"
python3 -B "$E/checker_series_fixtures.py" --build "$B"
python3 -B "$E/p04_native_series.py" --build "$B"
python3 -B "$E/checker_negative_fixtures.py"
python3 -B "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
python3 -B "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
BUILD_OUTPUT_DIR="$B" python3 -B tools/check_p04_order_evidence.py --verify-series-only
```
The collector command above was already executed successfully; another execution
must refuse the now-existing destination. Review the existing collection rather
than overwriting it. The final command is intentionally failing; it does not
need or generate a final index and never performs candidate composition.
## Stage A and subsequent dependencies
Stage A remains pending: actual HEAD/filesystem candidate binding,
`MAIN_ANCHOR` `9694...`, `candidate_tree` trust and the `3139` metadata holes
are untouched. `--verify-only` still requires the actual final evidence index;
its missing-check unions recomputed C/R results and still fails for any missing
condition, and it cannot succeed while Stage A is pending.
Required sequence remains **R → A → final index → mandatory whole validation →
independent review → ordinary integration**. Outstanding raw/specification gaps
and the two existing fixture failures prevent representing this dispatch as a
completed or accepted implementation.
@@ -0,0 +1,58 @@
# Repair19 persistence explanation corrigendum
This corrects the old Root explanation that the live day-summary comparison
differed only in StringName/String and dictionary order. That explanation missed
generic Array headers. It also corrects the superseded proposal to explain this
path using `_to_snapshot_variant`.
The exact relocated source is
`B/audit-support/p04-r17-full-inputs-r01/product/scripts/operations/operations_coordinator.gd`.
Its 130891 bytes have SHA256
`871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`.
Full bodies, including bytes through the next named function, were read directly
and compared with `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`, where
`T=B/audit-support/p04-repair19-close-r01`. All five bodies, lengths and hashes
match that supplied file:
| Function | Line | Bytes | SHA256 |
| --- | ---: | ---: | --- |
| `get_day_summary` | 577 | 153 | `449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb` |
| `create_snapshot` | 620 | 3988 | `8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309` |
| `_snapshot_root_report` | 2943 | 2062 | `96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe` |
| `_visit_snapshot_tree` | 2992 | 3045 | `70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0` |
| `_to_snapshot_variant` | 3083 | 550 | `f7db3cf2d495bb117fc7f6795633daa272c03d8eb7f99977e462e0a9df20cd8a` |
`get_day_summary` returns `_day_summaries[day].duplicate(true)` directly. It
exposes an internal API representation; the product getter need not change.
`create_snapshot` first validates with capture=true. In that pass,
`day_summaries` is not among the reusable encoded fields. It then assembles
`saved_days` and calls `_snapshot_root_report(raw_snapshot, field_cache, false)`.
That report invokes `_visit_snapshot_tree` with encoding enabled. The visitor
creates untyped arrays, converts StringName to String, stringifies permitted
dictionary keys and sorts the resulting String keys with native sorting.
The separate `_to_snapshot_variant` helper is not on this call chain. Its
`sort_custom(_variant_string_less)`, lack of finite-float/budget checks and
pass-through of unsupported types prevent using it as a general equivalent.
The supplied `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json` lists the following six
changes. These are explicitly attributed to that diagnostic; they have not been
independently rerun by a repair19 fixture in this dispatch.
| Path below `day_summary` | Writer header | Writer element kind | Reader header | Length |
| --- | --- | --- | --- | ---: |
| `/anomalies` | `1c000100` | Dictionary (27) | `1c000000` | 1 |
| `/anomalies/0/allocations` | `1c000100` | Dictionary (27) | `1c000000` | 1 |
| `/anomalies/0/facility_ids` | `1c000100` | String (4) | `1c000000` | 3 |
| `/anomalies/0/unallocated_by_staff` | `1c000100` | Dictionary (27) | `1c000000` | 0 |
| `/settlement/input_payments` | `1c000100` | Dictionary (27) | `1c000000` | 5 |
| `/settlement/rows` | `1c000100` | Dictionary (27) | `1c000000` | 5 |
Every listed Reader array is untyped. Raw getter byte equality remains **false**.
This diagnostic is separate from the source-defined persisted snapshot.
Neither this explanation nor an optional projection may weaken exact native
expected/restored typed-byte comparison, entire Runtime equality, formal 4+4
member equality, settlement counts, duplicate-payment checks or replay checks.
No projection implementation or new save_restore verification is claimed here.
The executable R18 normalization gate is still unchanged, so save_restore is
still open pending the exact encoder port and its required validation.
@@ -0,0 +1,78 @@
# Repair19 — incomplete; execution constraint clarification pending
This is not a ready submission and not formal acceptance. Original credit stays
13/88. All 15 conditions plus EV retain their existing scope; nativeWindows
remains pending for each. No product, acceptance, domain or outcome was changed.
## Blocking instruction conflict
The recorded repair19 direction requires local fixture reruns, isolated temp-Git
negative and positive controls, and removing the Stage-A refusal only after
those tests pass. The final worker instructions prohibit Git writes and say
tests are formally executed by Runner. This dispatch has requested clarification
on whether that prohibition exempts the specifically requested file-only and
isolated temp-Git fixtures. There is no answer at the time of this report.
No exemption has been assumed. This is an execution-constraint blocker, not a
claim that pending formal acceptance itself blocks implementation.
## Changes actually made in this dispatch
- `p04_native_series.py`: retain both JSON and Variant Writer/Reader phase-byte
comparisons, but increment the S3 pair metric only for `.var`. The requested
interpretation is 352 checked file pairs representing 176 observations. A
new execution output confirming that count has not been generated.
- `p04_native_series.py`: add the B1 `power_stop` join for the five observations
at 959–963, positive finite original reserve, actual public mana and power
flags, direct closure, same service identity/progress/reservations, queued B
release/stock/patience, recovery payment and unchanged next ledger. Bind the
saved 961 recovery command and the APPLY_COMMANDS phase. Preserve the actual
`RELATED_FACILITY_INACTIVE` reason. This code is untested; its addition to the
verifier's returned conditions is conditional on every assertion succeeding
when Runner executes it. No successful power_stop result is claimed here.
- `REPAIR19_CORRIGENDUM.md`: correct the StringName/order-only explanation,
distinguish raw getter from public persistence, list all six supplied typed
Array header changes, and record the actual source call-chain full-body pins.
## Static observations and preservation
- In-memory Python `compile` of the edited series module succeeded. No fixture,
native, Godot, full-regression or Runner run occurred.
- The exact relocated operations source was read. All five complete function
bodies, lengths and hashes equal
`B/audit-support/p04-repair19-close-r01/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`.
The hash table and exact call-chain explanation are in the corrigendum.
- The checker region from `NATIVE_RUNS` through `verify_native`, inclusive of
the C constants and helpers, equals its HEAD bytes. Region SHA256:
`5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`.
- No R17/R18 output or status file was edited. The existing uncommitted R18
checker changes remain. No Git write, agent, integration or Runner operation
occurred. No native evidence was created or modified.
- No collector was run. Both the existing r17 collection and the required fresh
r19 destination remain untouched by this dispatch. Since the series verifier
bytes changed, the old r17 collection cannot validate these new verifier bytes;
it must never be overwritten or represented as a repair19 collection.
## Remaining implementation and validation
Repair19 is incomplete. R2's C-backed paid shift join, R3's exact encoder port
and fixtures, Stage A's actual Git-role verifier, new index generator, fixture
protocol repairs and all requested fixture executions remain to be completed.
The old Stage-A unconditional refusal remains in place. The old
`verify_product_target` has not been edited or replaced in this dispatch.
The 9b1a18c anchor resolves to
`9b1a18c1eb25ca1300dcd59da78423c0dda2f498`; the old checker constant has not yet
been changed as part of a complete Stage-A repair.
No new output JSON is reported, and no old fixture success is reused as success
for the edited module. B4a/B4b semantics, frozen save_restore measurements and
its current open result remain unchanged. The 25 C negatives and 56 series
negatives have not been rerun.
There are no valid repair19 index-generation or submitted/merged-candidate Root
commands yet: the required generator and role verifier are not implemented.
Providing plausible commands for nonexistent functionality would be misleading.
The completed follow-up must supply those exact commands after implementing and
validating the protocol, retain all existing outputs, and collect exclusively
into the fresh `B/audit-support/p04-raw-collect-r19` destination.
Here `B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build`.
@@ -0,0 +1,40 @@
# Repair20 persistence corrigendum
The old Root explanation, “StringName/order only”, omitted six generic Array
header changes. `get_day_summary` returns the internal day's `duplicate(true)`;
it does not return the canonical persisted representation. Its raw Writer and
Reader bytes remain unequal. All original raw files and values are retained.
No product getter change is proposed.
| Path under day_summary | Writer header | Element kind | Reader header | Length |
| --- | --- | --- | --- | ---: |
| anomalies | 1c000100 | Dictionary (27) | 1c000000 | 1 |
| anomalies/0/allocations | 1c000100 | Dictionary (27) | 1c000000 | 1 |
| anomalies/0/facility_ids | 1c000100 | String (4) | 1c000000 | 3 |
| anomalies/0/unallocated_by_staff | 1c000100 | Dictionary (27) | 1c000000 | 0 |
| settlement/input_payments | 1c000100 | Dictionary (27) | 1c000000 | 5 |
| settlement/rows | 1c000100 | Dictionary (27) | 1c000000 | 5 |
These are the six entries in `T/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json`.
The new code derives them again from typed nodes and compares the list to that
diagnostic. This execution is **NOT RUN** in repair20.
The persistence chain is `create_snapshot` (620) →
`_snapshot_root_report(raw_snapshot, field_cache, false)` (2943) →
`_visit_snapshot_tree(..., encode=true)` (2992). The earlier capture=true pass
does not cache encoded day_summaries. `_to_snapshot_variant` is not on this path.
The projection is confined to raw getter → persisted day1. It retains all
scalar Variant bytes, including numeric type, rejects nonfinite floats and
unsupported values, stringifies permitted keys, rejects canonical collisions,
sorts String keys, rebuilds untyped Arrays, and enforces collection, depth, node,
single-string and aggregate-string limits. Unsupported dictionary-key types
are rejected, never silently normalized. The optional admitted subset does not
include composite-key Godot stringification. No projection is allowed on the
native persisted expected/restored comparison.
Full Runtime Writer/Reader/restored equality, formal 4+4 member equality,
settlement/duplicate-payment checks and the actual advance_to replay checks
remain independent strict requirements. If getter projection still differs
from the native canonical_day1 bytes, `save_restore` remains open and reports
that exact mismatch. No new successful equality is claimed without Root's run.
@@ -0,0 +1,183 @@
# Repair20 — source submission; tests NOT RUN
Source-only repair. No Git command, fixture, collector, series, Godot/native,
Runner, agent, commit, branch or integration operation was executed. The only
code validation performed was Python `compile(..., 'exec')` in memory, without
imports/execution of the edited modules. No acceptance is claimed. Original
credit remains 13/88; all 15 conditions + EV, seven domains and three safety
outcomes retain their scope. nativeWindows remains pending for every condition.
The next submission SHA is assigned by Runner and is not predeclared here.
Changed files:
- `tools/check_p04_order_evidence.py`: real checkout/role binding, isolated
object stores under B, first-1080 R2 join, schema2 recomputation and r20 collection.
- `p04_native_series.py`: B1 departure/despawn/full-state joins, explicit 352/176
S3 checks, confined getter projection, persistence pins and six-header diagnostic.
- `collect_p04_native.py`: exclusive new collection at
`B/audit-support/p04-raw-collect-r20`.
- `checker_negative_fixtures.py`: retain existing content-case IDs and meanings;
isolate their content layer explicitly in the test harness, while the separate
real-Git fixture exercises production role binding without a bypass.
- New `build_p04_index.py`, `checker_role_fixtures.py`,
`checker_encoder_fixtures.py`, `checker_join_fixtures.py`.
- New `REPAIR20_STATUS.md` and `REPAIR20_CORRIGENDUM.md`.
In-memory compilation: all eleven Python files (checker plus ten modules in E)
compile. This is syntax validation only; every test below is **NOT RUN**.
The retained C region, `NATIVE_RUNS` through `verify_native`, is byte-for-byte
equal to T's retained source, SHA256
`5d778b3cfab7c139ca03812489508ce9aae90c1d17f8c2249c340f30d1496b3b`.
`verify_product_target` itself is also byte-for-byte retained and no longer called.
Existing repair17/18/19 status, corrigendum, raw outputs and T's retained archives
were not modified. Existing C 25 negatives and series 56 negatives remain required.
## R3 source pins
Product source: `scripts/operations/operations_coordinator.gd`, 130891 bytes,
SHA256 `871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90`.
Runtime verification checks the complete bodies and cross-checks the actual
body bytes, lengths and hashes against `T/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json`.
| Function | SHA256 |
| --- | --- |
| get_day_summary | 449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb |
| create_snapshot | 8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309 |
| _snapshot_root_report | 96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe |
| _visit_snapshot_tree | 70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0 |
The corrigendum lists all six Array header changes. Native persisted comparisons
are direct typed-byte comparisons. Raw getter inequality remains diagnostic.
The optional projection admits only the supported persistence subset; unsupported
composite dictionary keys fail closed. Its negative controls include field loss,
extra fields, both numeric-type directions, value changes, nonfinite floats,
invalid keys, collisions, unsaveable values, wrong call chain and use on native
persisted comparisons. The positive changes only Array headers and StringName.
## Condition disposition on this untested source
No condition is newly proven by this dispatch. All tests are **NOT RUN**.
| Condition | State and reason pending Root execution |
| --- | --- |
| power_stop | Open: new B1 959–963 join; real B at 959/960, normal recorded departure then absence, unchanged one-time release, stock 1, wallet 50, no B charge; recovery 25 at 962 and no extra 963 charge. |
| destruction_stop | Pending: retained S3 B4b BROKEN failure and zero-sale/release predicates. |
| dependency_stop | Pending: unchanged C paused service identity/progress/reservation proof. |
| shift_completion | Open: new C first-1080 join; PIDs 77462/77504/77536 ended, 1338-file content 5f, 44→45, ledger_00000006/25G, DAY2 present before handover and zero after, no extra at 1081. |
| recovery_completion | Pending: unchanged C recovery transaction/idempotency join. |
| same_minute_replay | Pending: retained real paid request-resend join; never substituted for settlement replay. |
| closure_release | Pending: retained S3 closure/release plus despawn-aware B1 checks. |
| exit_release | Pending: retained S3 B2 release/completion ordering. |
| patience_release | Pending: retained S3 B3 patience expiration and release. |
| paused_midnight | Pending: unchanged C paused-midnight series. |
| midnight_settlement | Pending: retained S2a nonzero sales/payroll/upkeep/duty joins. |
| midnight_replay | Pending: actual advance_to replay, zero processed minutes and zero daily closes, exact Runtime bytes. |
| save_restore | Open: direct full typed Runtime and formal 4+4 equality remain mandatory; optional getter projection must equal actual persisted day1. A remaining mismatch is explicitly reported, never projected out of native restore. |
| night_staff_absent | Pending: retained S2b WAITING_FOR_STAFF at 1081. |
| overtime_task | Pending: retained S2b interrupt-level-zero task/staff/shift and once-only duty joins. |
| EV | Pending: retained S1 reachable reserved staff power-stop proof and pinned static chain. |
Stage A is implemented but **NOT RUN**. Submission is inferred from actual Git
ancestry and scoped diff; the composed product is labelled prospective, not a
tested worker game. Integration/final requires an actual two-parent merge and
exact recomputed merge tree, native product map/modes and submitted checker/doc
patch. Every tracked file and executing module must match HEAD bytes/mode;
untracked/ignored files and symlinks fail. Unknown/deleted sources and candidate,
code-only or phase declarations cannot authorize a role. Metadata membership
comes from the real 9b1a18c tree, without the old count assumptions.
Schema2 retains native source/config/input/environment, real lifecycle PIDs,
formal proofs, complete raw references, authority and original-audit uncertainty,
domain and reachability mappings. Its entire payload is recomputed from raw by
the same checker. It contains no candidate==HEAD pin or Root-passed flag.
`generation-invocation.json` separately records the actual invocation/HEAD.
## Root execution order (all commands below NOT RUN)
Run from the clean, actual auto-submitted checkout. Root supplies actual clean
merged/final checkout paths later; do not substitute invented SHAs. Preserve all
prior output directories and use exclusive fresh output destinations.
```sh
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 BUILD_OUTPUT_DIR="$B"
export PYTHONDONTWRITEBYTECODE=1
```
1. Local file-only and fixture controls on the actual new SHA:
```sh
python3 "$E/checker_negative_fixtures.py"
python3 "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
python3 "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
python3 "$E/checker_series_fixtures.py" --build "$B"
python3 "$E/checker_encoder_fixtures.py"
python3 "$E/checker_join_fixtures.py" --build "$B"
```
2. Real series against B, then the exclusive r20 collection:
```sh
python3 "$E/p04_native_series.py" --build "$B"
python3 "$E/collect_p04_native.py" --build "$B"
```
3. After auto-submit, generate the fresh normal Runner index. B/p04-evidence was
absent at static inspection. If it is now occupied, retain it and choose an
explicit fresh directory with P04_EVIDENCE_DIR for manual checks; do not overwrite.
The normal Runner uses B/p04-evidence and must receive that actual generated index.
```sh
python3 "$E/build_p04_index.py" --build "$B" --output "$B/p04-evidence" \
--invocation validate \
--registered-ref deb1bcb2374eb378b840ca345db952bd8572cb6e \
--tested-ref 5f6bc11d6bb6e92d22d787989b219fef3a435bc1
```
4. Checker on submitted, merged and final actual checkouts. Real temp-Git fixtures
need the generated index, so their execution follows step 3, not a fabricated
pre-submission index. They create throwaway repositories/objects only under B.
```sh
python3 "$E/checker_role_fixtures.py" --build "$B" --evidence "$B/p04-evidence"
python3 tools/check_p04_order_evidence.py --verify-only
python3 "$P04_MERGED_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
python3 "$P04_FINAL_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
```
The same index works in all three roles. For separate invocation records, the
generator also accepts `--invocation prepare-integration` and `--invocation final`
with explicit fresh output paths while executing from those actual checkouts.
These labels are receipt metadata and grant no role. Root must respect the normal
Runner state machine: its prepare_integration requires prior formal review.
An isolated local prospective-merge control does not replace that review or move
Runner state. `runner.validate("P04-01")`, `runner.prepare_integration("P04-01")`
and `runner.integrate("P04-01")` remain Root operations only.
5. Mandatory whole validation and full 178-game regression once on exact final
sources. The supplied Runner core exposes
`runner.verify("P04-01", final_checkout, actual_final_sha, "final")`; retain every
configured mandatory validation, then run the existing guarded full-game runner:
```sh
python3 "$P04_FINAL_CHECKOUT/tools/run_all_tests.py" \
--project-root "$P04_FINAL_CHECKOUT" --output "$B/p04-final-product-full-r01"
```
Do not change timeout, marker contracts or suite policy, splice phases, or reuse
historical 177-suite proof. Confirm the final selected source map and 178-suite
roster, and preserve source/default-profile invariance. This command is the one
full run, not permission for an additional `make test` native run.
6. Independent Opus review using Root's existing reviewer route, with exact
source/validation materials mirrored into the frozen checkout's .review-material.
The supplied core's formal method is `runner.review("P04-01")`; that method reviews
the actual submitted revision and requires its validation evidence. Any additional
final-composition Opus review must explicitly receive the actual final revision.
The external commander/Opus CLI entry point and installed Runner configuration
were not supplied in this checkout, so no guessed shell command is asserted for
them. Root retains its existing formal validation/review/integration workflow.
The absent external entry-point/configuration details are handoff context gaps,
not evidence of a pass, and not a blocker solely because Root testing is pending.
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Build a fresh evidence index from supplied native bytes after auto-submit.
The index has no candidate identity or Root-passed flag. Invocations share the
same payload; checkout roles are established afresh by the checker.
"""
import argparse
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import sys
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[5]
def checker():
spec = importlib.util.spec_from_file_location('p04_index_checker', ROOT / 'tools/check_p04_order_evidence.py')
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def descriptor(path):
data = path.read_bytes()
return {'path': str(path.absolute()), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
def payload(build, gate=None):
gate = gate or checker()
build = Path(build).resolve(strict=True)
native = build / 'audit-support/native-first-current'
c = gate.verify_native(native)
raw = gate.combine_native_series(build, c)
source = gate.product_manifest(gate.tree_entries(gate.TESTED_PRODUCT))
gate.require(len(source) == 1338, 'complete native source required')
source_map = {p: v['sha256'] for p, v in source.items()}
# Resolve content from supplied roots only; never open original provenance
# paths, never omit required source/config/input/environment material.
supplied = {}
roots = [build / 'audit-support' / name for name in
('native-first-current', 'p04-whole-closure-r01', 'p04-current-native-series-r01',
'p04-r17-full-inputs-r01')]
for root in roots:
if not root.exists():
continue
for path in sorted(root.rglob('*')):
if path.is_file():
gate.safe(root, path.relative_to(root).as_posix())
pin = descriptor(path)
supplied.setdefault(pin['sha256'], pin)
runners = []
for run in gate.NATIVE_RUNS:
path = native / run / 'runner-input.json'
inputs = gate.load(path)
row = descriptor(path)
row['materials'] = {}
for pin in inputs.get('fixed_input_pins', []) + [inputs['QA']]:
gate.require(pin['sha256'] in supplied, 'missing supplied runner material: ' + pin['path'])
actual = supplied[pin['sha256']]
gate.require(actual['bytes'] == pin['bytes'], 'runner material length')
row['materials'][pin['path']] = actual
gate.require(inputs['wrapper_sha256'] in supplied, 'missing supplied native wrapper')
row['wrapper'] = supplied[inputs['wrapper_sha256']]
runners.append(row)
# C references retain every lifecycle, raw phase, source, environment and
# formal member mapping; Stage R retains its complete original->relocated map.
references = {}
for row in gate.load(native / 'SOURCE_MANIFEST.json')['files']:
references['C/' + row['copy']] = descriptor(gate.safe(native, row['copy']))
for row in gate.load(native / 'OWNERSHIP_RAW_PROVENANCE.json')['copied']:
references['C/' + row['target']] = descriptor(gate.safe(native, row['target']))
series = {}
collection = build / 'audit-support/p04-raw-collect-r20'
for name in ('s1', 's2a', 's2b', 's3'):
series[name] = gate.load(collection / (name + '.json'))
relocation = gate.load(build / 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json')
authorities = {}
for ref in sorted({r for _, refs, _ in gate.REQUIRED.values() for r in refs}):
relative = (gate.DOC_SCOPE + 'D05_OWNER_APPROVAL.json' if ref.startswith('D05-P04-') else
'docs/traceability/original/BASELINE.txt' if ref.startswith('BASELINE:') else
'docs/DESIGN_DECISIONS.md' if ref == 'D-034' else
'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/ORDER_CONTRACT_DRAFT.md')
path = gate.safe(gate.ROOT, relative)
if ref.startswith('D05-P04-'):
gate.require(gate.sha(path) == gate.OWNER_HASH, 'owner approval hash')
authorities[ref] = {'source': relative, 'sha256': gate.sha(path),
'status': 'approved' if not ref.startswith('BASELINE:') else 'original'}
conditions = {cid: {'case_id': cid, 'authority_refs': list(refs), 'nativeWindows': 'pending',
'raw_verified': cid in raw['verified_conditions'],
'required_measurements': metrics}
for cid, (_, refs, metrics) in gate.REQUIRED.items()}
domains = {domain: {'conditions': [cid for cid, (d, _, _) in gate.REQUIRED.items() if d == domain],
'source_refs': [{'path': p, 'sha256': source_map[p], 'functions': list(fs)}
for p, fs in gate.ORDER_FUNCTIONS.items()],
'authority_refs': sorted({r for _, (d, refs, _) in gate.REQUIRED.items()
if d == domain for r in refs})}
for domain in sorted(gate.DOMAINS)}
reachability = {}
for name, rows in gate.REACH_REFS.items():
refs = []
for path, function, digest, lines in rows:
gate.resolve_anchor(gate.git_bytes('show', gate.TESTED_PRODUCT + ':' + path), function, digest, lines)
refs.append({'path': path, 'sha256': source_map[path], 'function': function,
'anchor_sha256': digest, 'line_count': lines})
reachability[name] = {'reach': name == 'EV', 'case_id': 'EV' if name == 'EV' else None, 'source_refs': refs,
'raw_verified': 'EV' in raw['verified_conditions'] if name == 'EV' else None}
delta = gate.source_delta(gate.REGISTERED_COMMIT, gate.TESTED_PRODUCT)
index = {'schema_version': 2, 'task_id': 'P04-01', 'formal_acceptance': False,
'versions': {'registered': gate.REGISTERED_COMMIT, 'execution_origin': gate.EXECUTION_ORIGIN,
'tested': gate.TESTED_PRODUCT},
'product_binding': {'tested_product': gate.TESTED_PRODUCT, 'runner_inputs': runners},
'source_map': source_map, 'registered_pins': gate.load(gate.SPEC)['current_source_pins'],
'applicability_delta': delta,
'delta_cases': {r['path'] + ':' + r['function']: {cid: cid for cid in
gate.boundary_conditions(r['path'], r['function'])} for r in delta},
'domains': domains, 'authority': authorities, 'conditions': conditions,
'reachability': reachability, 'raw_conditions': raw, 'raw_references': references,
'series': series, 'source_config_input_environment_and_formal': relocation,
'native_bundle': descriptor(native / 'OWNERSHIP_RAW_PROVENANCE.json'),
'original_audit': {'sha256': gate.sha(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json'),
'content': gate.load(gate.ROOT / gate.DOC_SCOPE / 'order-audit.json')},
'review_status': 'pending_review', 'original_credit': '13/88',
'safety_outcomes': ['no charging while legitimately stopped',
'no double midnight settlement', 'no orphan reservations']}
gate.applicability(index, gate.load(gate.SPEC), source_map, gate.TESTED_PRODUCT)
return index
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', required=True, type=Path)
parser.add_argument('--output', required=True, type=Path, help='fresh directory; exclusive creation')
parser.add_argument('--invocation', required=True, choices=('validate', 'prepare-integration', 'final'))
parser.add_argument('--registered-ref', required=True)
parser.add_argument('--tested-ref', required=True)
args = parser.parse_args()
gate = checker()
os.environ['BUILD_OUTPUT_DIR'] = str(args.build.resolve(strict=True))
gate.require(args.registered_ref == gate.REGISTERED_COMMIT and args.tested_ref == gate.TESTED_PRODUCT,
'source refs differ from immutable evidence roles')
output = args.output.absolute()
gate.require(not output.exists() and not output.is_symlink(), 'output must be fresh')
for path in output.parents:
gate.require(not path.is_symlink(), 'output parent symlink')
index = payload(args.build, gate)
with gate.isolated_objects():
_, role = gate.verify_roles(index, args.build.resolve())
# The historical invocation is separate from the reusable evidence index.
receipt = {'invocation': args.invocation, 'actual_head': role['actual_head'],
'actual_tree': role['actual_tree'], 'role': role['role'], 'formal_acceptance': False}
output.mkdir(parents=False)
for name, value in (('index.json', index), ('generation-invocation.json', receipt)):
with (output / name).open('x', encoding='utf-8') as stream:
json.dump(value, stream, ensure_ascii=False, sort_keys=True, indent=2)
stream.write('\n')
print(json.dumps({'evidence_directory': str(output), **receipt}))
if __name__ == '__main__':
main()
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""Optional getter projection controls; these are never native proof."""
import json
import struct
import sys
sys.dont_write_bytecode = True
import p04_native_series as s
def string(value, kind=4):
data = value.encode()
return struct.pack('<II', kind, len(data)) + data + b'\0' * ((-len(data)) % 4)
def integer(value):
return struct.pack('<Ii', 2, value)
def dictionary(rows):
return struct.pack('<II', 27, len(rows)) + b''.join(k + v for k, v in rows)
def array(values, typed=None):
return (struct.pack('<I', 28 if typed is None else 28 | (1 << 16)) +
(b'' if typed is None else struct.pack('<I', typed)) +
struct.pack('<I', len(values)) + b''.join(values))
def run():
results = []
canonical = dictionary([(string('a'), array([string('item')])), (string('n'), integer(7))])
getter = dictionary([(string('n', 21), integer(7)),
(string('a', 21), array([string('item', 21)], typed=21))])
assert s.persisted_day_projection(s.variant(getter)) == canonical
results.append({'case': 'typed_array_headers_and_StringName_only', 'result': 'accepted'})
def reject(name, action, error=None):
try:
action()
except ValueError as exc:
assert error is None or error in str(exc), (name, str(exc))
results.append({'case': name, 'result': 'rejected', 'error': str(exc)})
else:
raise AssertionError('negative accepted: ' + name)
def compare(data):
s.require(s.persisted_day_projection(s.variant(data)) == canonical, 'persisted value mismatch')
reject('dropped_field', lambda: compare(dictionary([(string('n'), integer(7))])))
reject('extra_field', lambda: compare(dictionary([(string('a'), array([string('item')])),
(string('n'), integer(7)), (string('x'), integer(0))])))
reject('int_to_float', lambda: compare(dictionary([(string('a'), array([string('item')])),
(string('n'), struct.pack('<If', 3, 7.0))])))
floating = dictionary([(string('n'), struct.pack('<If', 3, 7.0))])
reject('float_to_int', lambda: s.require(s.persisted_day_projection(s.variant(floating)) ==
dictionary([(string('n'), integer(7))]), 'numeric type changed'))
reject('value_change', lambda: compare(dictionary([(string('a'), array([string('item')])),
(string('n'), integer(8))])))
reject('nonfinite_float', lambda: s.persisted_day_projection(s.variant(dictionary([
(string('n'), struct.pack('<If', 3, float('inf')))]))), 'NON_FINITE_NUMBER')
reject('invalid_key_type', lambda: s.persisted_day_projection(s.variant(dictionary([
(struct.pack('<II', 1, 1), integer(1))]))), 'INVALID_DICTIONARY_KEY')
reject('key_collision', lambda: s.persisted_day_projection(s.variant(dictionary([
(integer(1), integer(1)), (string('1'), integer(2))]))), 'DUPLICATE_CANONICAL_KEY')
reject('unsaveable_variant', lambda: s.persisted_day_projection(s.variant(dictionary([
(string('x'), struct.pack('<Iff', 5, 1.0, 2.0))]))), 'UNSAVEABLE_VARIANT')
reject('wrong_to_snapshot_variant_chain', lambda: s.persisted_day_projection(s.variant(getter),
chain='_to_snapshot_variant'), 'projection call chain')
reject('projection_on_native_persisted_comparison', lambda: s.persisted_day_projection(s.variant(getter),
comparison='native_expected_restored'), 'projection forbidden')
reject('single_string_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
(string('x'), string('x' * 4097))]))), 'STRING_TOO_LONG')
reject('collection_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
(string('x'), array([integer(0)] * 20001))]))), 'COLLECTION_TOO_LARGE')
nested = integer(1)
for _ in range(33):
nested = array([nested])
reject('depth_budget', lambda: s.persisted_day_projection(s.variant(dictionary([
(string('x'), nested)]))), 'MAX_VARIANT_DEPTH')
# The collision prepass must win over invalid-key and child errors.
reject('collision_error_precedence', lambda: s.persisted_day_projection(s.variant(dictionary([
(struct.pack('<II', 1, 1), struct.pack('<If', 3, float('inf'))),
(string('true'), integer(0))]))), 'DUPLICATE_CANONICAL_KEY')
return {'scope': 'synthetic projection controls only', 'results': results}
if __name__ == '__main__':
print(json.dumps(run(), indent=2))
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Real B1/R2 positive controls and semantic mutation negatives for Root."""
import argparse
import copy
import json
from pathlib import Path
import sys
from unittest.mock import patch
sys.dont_write_bytecode = True
import p04_native_series as series
from build_p04_index import checker
def run(build):
gate = checker()
base = build / 'audit-support/native-first-current'
results = []
def reject(name, action):
try:
action()
except (ValueError, KeyError, TypeError) as exc:
results.append({'case': name, 'result': 'rejected', 'error': str(exc)})
else:
raise AssertionError('negative accepted: ' + name)
context = series.Series(build, 's3')
series.power_stop(context)
results.append({'case': 'B1_real_despawn_positive', 'result': 'accepted'})
original_value = context.value
for name, label, mutate in (
('B1_missing_at_closure', 'after', lambda r: r['guests'].update({'p04.s3.guest.b': {}})),
('B1_synthetic_later_guest', 'next', lambda r: r['guests'].update({'p04.s3.guest.b': {'patience_remaining': 45}})),
('B1_wrong_release_minute', 'next', lambda r: r['reservations'][1].update(terminal_at=961)),
('B1_changed_wallet', 'recovered', lambda r: r['wallets'].update({'p04.s3.guest.b': 25})),
('B1_missing_departure_history', 'next', lambda r: r['snapshot']['systems']['runtime']['guests'].update(persistent_records=[])),
):
def altered(key, label=label, mutate=mutate):
value = copy.deepcopy(original_value(key))
if key == 'writer-B1-' + label:
mutate(value)
return value
with patch.object(context, 'value', altered):
reject(name, lambda: series.power_stop(context))
gate.verify_shift_completion(base)
results.append({'case': 'R2_real_first_1080_positive', 'result': 'accepted'})
original_load = gate.load
observation_path = base / 'actual-native/order-evidence/observations.json'
for name, mutate in (
('R2_paid_midnight_substitution', lambda o: o['observations'][2]['service'].update(completed_at=1442)),
('R2_wrong_transaction', lambda o: o['observations'][2]['receipts'][0].update(id='ledger_00000099')),
('R2_missing_DAY2', lambda o: o['observations'][2].update(active_staff=[])),
('R2_after_staff_still_present', lambda o: o['observations'][3].update(active_staff=['p04.order.server.a'])),
('R2_next_extra_receipt', lambda o: o['observations'][5]['receipts'].append(copy.deepcopy(o['first_receipts'][0]))),
):
def altered(path, mutate=mutate):
value = original_load(path)
if path == observation_path:
mutate(value)
return value
with patch.object(gate, 'load', altered):
reject(name, lambda: gate.verify_shift_completion(base))
return {'scope': 'semantic controls only; no new native execution', 'results': results}
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', type=Path, required=True)
print(json.dumps(run(parser.parse_args().build), indent=2))
@@ -9,6 +9,7 @@ from pathlib import Path
import subprocess
import sys
import tempfile
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[5]
CHECKER = ROOT / 'tools/check_p04_order_evidence.py'
@@ -124,12 +125,19 @@ def run():
(evidence / 'index.json').write_text(json.dumps(candidate))
if name == 'duplicate_key':
(evidence / 'index.json').write_text('{"schema_version":1,"schema_version":1}')
proc = subprocess.run(['python3', str(CHECKER), '--verify-only'],
env=dict(os.environ, BUILD_OUTPUT_DIR=str(base)), text=True, capture_output=True)
output = json.loads(proc.stdout)
assert proc.returncode == (0 if error is None else 1), (name, proc.stdout, proc.stderr)
# These are content-layer unit cases. Stage-A real Git/FS controls
# live in checker_role_fixtures.py; no production bypass is added.
head = index['candidate_commit']
with patch.object(gate, 'verify_roles', return_value=(head, {})), patch.dict(
os.environ, {'BUILD_OUTPUT_DIR': str(base), 'P04_EVIDENCE_DIR': str(evidence)}):
try:
gate.verify(base)
code, output = 0, {}
except (ValueError, OSError, KeyError, TypeError) as exc:
code, output = 1, {'error': str(exc)}
assert code == (0 if error is None else 1), (name, output)
assert error is None or error in output.get('error',''), (name, output)
results.append(dict(fixture=name, exit_code=proc.returncode, result=output))
results.append(dict(fixture=name, exit_code=code, result=output))
check('synthetic_scalar_only_rejected', lambda i: None, 'native bundle required')
check('forged_registered_pin', lambda i: i['registered_pins'].update({'autoload/simulation_runtime.gd':'0'*64}), 'registered pin declaration')
check('false_delta', lambda i: i['applicability_delta'].append({'path':'autoload/simulation_runtime.gd','function':'initialize'}), 'git diff/applicability_delta mismatch')
@@ -141,7 +149,7 @@ def run():
(evidence / 'stdout.json').symlink_to(evidence / 'native_log.json')
check('symlink', symlink, 'symlink')
check('hash_mismatch', lambda i: i['artifacts']['stdout'].update(sha256='0'*64), 'artifact hash')
check('stale_pin', lambda i: i['source_map'].update({'autoload/simulation_runtime.gd':'0'*64}), 'stale pin')
check('stale_pin', lambda i: i['source_map'].update({'autoload/simulation_runtime.gd':'0'*64}), 'tested game differs from Git')
def receipt_mutation(i):
aid = 'power_stop_receipt'
value = gate.load(evidence / artifacts[aid]['path'])
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Real temp-Git role controls. Root executes after auto-submit; never evidence."""
import argparse
import copy
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[5]
def run(build, evidence):
build, evidence = build.resolve(strict=True), evidence.resolve(strict=True)
original = json.loads((evidence / 'index.json').read_text())
results = []
env = dict(os.environ, BUILD_OUTPUT_DIR=str(build), PYTHONDONTWRITEBYTECODE='1',
GIT_CONFIG_NOSYSTEM='1', GIT_AUTHOR_NAME='P04 fixture', GIT_AUTHOR_EMAIL='fixture@invalid',
GIT_COMMITTER_NAME='P04 fixture', GIT_COMMITTER_EMAIL='fixture@invalid')
for name in ('GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY',
'GIT_ALTERNATE_OBJECT_DIRECTORIES'):
env.pop(name, None)
with tempfile.TemporaryDirectory(prefix='p04-role-fixtures-', dir=build) as temp:
base = Path(temp)
checkout = base / 'checkout'
subprocess.run(['git', 'clone', '--shared', '--no-checkout', str(ROOT), str(checkout)],
env=env, check=True, capture_output=True)
def git(*args):
return subprocess.check_output(['git', '-c', 'core.hooksPath=/dev/null', *args], cwd=checkout, env=env).decode().strip()
actual = git('rev-parse', 'HEAD')
anchor = '9b1a18c1eb25ca1300dcd59da78423c0dda2f498'
ancestry = subprocess.run(['git', 'merge-base', '--is-ancestor', anchor, actual], cwd=checkout, env=env)
submission = git('show', '-s', '--format=%P', actual).split()[1] if ancestry.returncode == 0 else actual
git('checkout', '--detach', submission)
# Objects created by clone commits/merge-tree remain under build. The
# shared canonical objects are consulted as read-only alternates only.
probe = base / 'probe.py'
probe.write_text(
'import importlib.util,json,sys\nfrom pathlib import Path\n'
'sys.dont_write_bytecode=True\n'
's=importlib.util.spec_from_file_location("gate",Path.cwd()/"tools/check_p04_order_evidence.py")\n'
'g=importlib.util.module_from_spec(s);sys.modules[s.name]=g;s.loader.exec_module(g)\n'
'with g.isolated_objects():\n'
' g.verify_roles(json.loads(Path(sys.argv[1]).read_text()),Path(sys.argv[2]))\n')
index_path = base / 'index.json'
def check(name, index=None, positive=False, full=False):
index_path.write_text(json.dumps(original if index is None else index))
if full:
directory = base / name
directory.mkdir()
(directory / 'index.json').write_text(index_path.read_text())
command = [sys.executable, 'tools/check_p04_order_evidence.py', '--verify-only']
process_env = dict(env, P04_EVIDENCE_DIR=str(directory))
else:
command = [sys.executable, str(probe), str(index_path), str(build)]
process_env = env
process = subprocess.run(command, cwd=checkout, env=process_env, capture_output=True, text=True)
assert (process.returncode == 0) == positive, (name, process.stdout, process.stderr)
results.append({'case': name, 'result': 'accepted' if positive else 'rejected',
'stdout': process.stdout, 'stderr': process.stderr})
check('real_submission_positive', positive=True)
game = checkout / 'project.godot'
original_game = game.read_bytes()
game.write_bytes(original_game + b'\n')
check('HEAD_tree_FS_mismatch')
game.write_bytes(original_game)
game.unlink()
game.symlink_to(base / 'outside')
(base / 'outside').write_bytes(original_game)
check('symlink_substitution')
game.unlink()
game.write_bytes(original_game)
unknown = checkout / 'scripts/p04_unknown.gd'
unknown.write_text('extends RefCounted\n')
check('untracked_substitution')
git('add', 'scripts/p04_unknown.gd')
git('commit', '-m', 'fixture unknown source')
check('unknown_source_commit')
git('reset', '--hard', submission)
game.unlink()
git('add', '-u')
git('commit', '-m', 'fixture deleted source')
check('deleted_source_commit')
git('reset', '--hard', submission)
changed = copy.deepcopy(original)
changed['product_binding']['tested_product'] = submission
check('wrong_tested_revision', changed)
for name, key, value in [('forged_candidate', 'candidate_tree', '0' * 40),
('code_only_real_candidate', 'code_only', True),
('phase_bypass', 'phase', 'candidate')]:
changed = copy.deepcopy(original)
changed[key] = value
check(name, changed)
git('checkout', '--detach', anchor)
git('merge', '--no-ff', '--no-edit', submission)
merged = git('rev-parse', 'HEAD')
check('real_integration_positive', positive=True)
check('real_final_positive', positive=True)
game.unlink()
git('add', '-u')
git('commit', '--amend', '--no-edit')
changed = copy.deepcopy(original)
changed['product_binding']['metadata'] = ['project.godot']
check('metadata_hiding_deleted_source', changed)
git('reset', '--hard', merged)
changed = copy.deepcopy(original)
changed['conditions']['power_stop']['required_measurements']['charges'] = 9
check('forged_index', changed, full=True)
return {'scope': 'fixture controls only', 'results': results}
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', required=True, type=Path)
parser.add_argument('--evidence', required=True, type=Path)
args = parser.parse_args()
print(json.dumps(run(args.build, args.evidence), indent=2))
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""Stage R file-only controls. Synthetic primitive controls plus real raw controls.
Tests never alter supplied originals or the relocation. In-memory overlays are
used for native descriptor/raw adversaries; filesystem adversaries are disposable.
"""
import argparse
import copy
import json
from pathlib import Path
import struct
import sys
import tempfile
from unittest.mock import patch
sys.dont_write_bytecode = True
import p04_native_series as s
def run(build):
results = []
def reject(series, name, action):
try:
action()
except (ValueError, KeyError, OSError, TypeError, UnicodeError) as exc:
results.append({'series': series, 'class': name, 'result': 'rejected', 'error': str(exc)})
else:
raise AssertionError('negative accepted: ' + series + '/' + name)
with tempfile.TemporaryDirectory(prefix='.series-fixtures-', dir=s.HERE) as tmp:
base = Path(tmp)
data = b'func boundary():\n\treturn 42\n# included trailing comment\n'
path = base / 'body.gd'
path.write_bytes(data)
pin = {'sha256': s.digest(data), 'bytes': len(data)}
# A deliberately small resolver fixture bypasses the production-only
# fixed 1721-file constructor, but exercises its exact read path.
resolver = s.Resolver.__new__(s.Resolver)
resolver.base, resolver.used = base, {}
resolver.mapping = {'/native/body.gd': dict(pin, relocated=str(path))}
resolver.manifest = {'product_source': {'body.gd': dict(pin, path='/native/body.gd')}}
assert resolver.read('/native/body.gd', pin) == data
assert s.variant(struct.pack('<Ii', 2, 42)).plain() == 42
assert s.checked(data, pin, 'positive') == data
results.append({'series': 'shared', 'class': 'small_synthetic_positive', 'result': 'pass'})
link = base / 'symlink.gd'
link.symlink_to(path)
for name in s.RUNS:
context = s.Series.__new__(s.Series)
context.build, context.run, context.references = base, s.RUNS[name], {}
body_row = {'path': 'body.gd', 'function': 'boundary',
'production_body_sha256': s.digest(data), 'registered_body_sha256': s.digest(data)}
s.source_body_pins(resolver, [body_row])
results.append({'series': name, 'class': 'full_body_positive', 'result': 'pass'})
reject(name, 'wrong_source_pin', lambda: resolver.read('/native/body.gd', dict(pin, sha256='0' * 64)))
signature = s.digest(data.splitlines(keepends=True)[0])
reject(name, 'signature_only_pin', lambda: s.source_body_pins(resolver, [dict(
body_row, production_body_sha256=signature, registered_body_sha256=signature)]))
reject(name, 'symlink', lambda: s.ordinary(link))
reject(name, 'traversal', lambda: context.read_build('../body.gd'))
reject(name, 'unmapped_original_path', lambda: resolver.read('/native/unknown.gd', pin))
reject(name, 'relocated_manifest_native_mismatch', lambda: resolver.read('/native/body.gd', dict(pin, bytes=len(data)+1)))
path.write_bytes(data + b'!')
try:
reject(name, 'relocated_byte_manifest_mismatch', lambda: resolver.read('/native/body.gd', pin))
finally:
path.write_bytes(data)
reject(name, 'JSON_only_getter_substitute', lambda: s.variant(b'{"getter": {}, "passed": true}'))
# Each series' real verifier is called for descriptor/raw overlays.
# The native descriptor pins are intentionally not refreshed: these
# runs and their original native descriptors are immutable inputs.
original_read = s.Series.read_build
run_name = s.RUNS[name]
targets = {
'forged_scalar': run_name + '/raw/writer.json',
'changed_raw_byte': run_name + '/raw/writer.json',
'formal_member_SHA_drift': run_name + '/execution.json',
'unowned_missing_PID_end': run_name + '/writer/ended.json',
'altered_phase_pair': run_name + '/raw/' + {
's1': 'reader-landing-held.var', 's2a': 'reader-C3-recall.var',
's2b': 'reader-night_absent-1080-before-facility.var',
's3': 'reader-B1-phase-960-TIME_BOUNDARIES.var'}[name],
'relabeled_interruption_reason': run_name + '/raw/' + {
's1': 'writer-landing-depleted.json', 's2a': 'writer-C1-midnight.json',
's2b': 'writer-night_absent-1081.json', 's3': 'writer-B1-after.json'}[name],
}
for label, target in targets.items():
def overlay(self, relative, descriptor=None, target=target, label=label):
if relative != target:
return original_read(self, relative, descriptor)
raw = s.ordinary(self.build / relative).read_bytes()
if label in ('changed_raw_byte', 'altered_phase_pair'):
raw += b' '
else:
obj = s.loads(raw)
if label == 'unowned_missing_PID_end':
obj['event'] = 'ENGINE_STARTED'
elif label == 'forged_scalar':
obj.update(passed=True, verified_conditions=list(s.RUNS), slots={})
elif label == 'formal_member_SHA_drift':
slot = next(iter(obj['formal_before']))
obj['formal_before'][slot]['world.bin']['sha256'] = '0' * 64
elif label == 'relabeled_interruption_reason':
if 'service' in obj:
obj['service']['interruption_reason'] = 'NO_POWER'
else:
obj['task']['state_reason'] = 'NO_POWER'
raw = json.dumps(obj).encode()
if descriptor is not None:
s.checked(raw, descriptor, relative)
return raw
with patch.object(s.Series, 'read_build', overlay):
reject(name, label, lambda name=name: getattr(s, 'verify_' + name)(build))
return {'scope': 'file-only Stage R controls; immutable descriptor and primitive negatives',
'formal_acceptance': False, 'results': results}
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', type=Path, required=True)
print(json.dumps(run(parser.parse_args().build), indent=2, sort_keys=True))
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Deterministic reference-only Stage R collector; never writes the final index."""
import argparse
import json
from pathlib import Path
import shutil
import sys
sys.dont_write_bytecode = True
import p04_native_series as series
def encoded(value):
return (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=False) + '\n').encode()
def collect(build):
build = Path(build).absolute()
destination = build / 'audit-support/p04-raw-collect-r20'
series.require(not destination.exists() and not destination.is_symlink(), 'collection destination already exists')
for p in (destination.parent, *destination.parents):
series.require(not p.is_symlink(), 'collection parent symlink')
payloads = {}
bindings = {}
for name in series.RUNS:
result = getattr(series, 'verify_' + name)(build)
result['verifier_sha256'] = series.digest(Path(series.__file__).read_bytes())
data = encoded(result)
filename = name + '.json'
payloads[filename] = data
bindings[name] = {'path': filename, 'bytes': len(data), 'sha256': series.digest(data),
'relocation_manifest_sha256': series.MANIFEST_SHA256}
manifest = {'protocol': 'p04-native-series-collection-v1', 'formal_acceptance': False,
'stage_A': 'candidate binding stage A pending', 'series': bindings,
'relocation_manifest_sha256': series.MANIFEST_SHA256,
'relocation_manifest': 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json',
'collector_sha256': series.digest(Path(__file__).read_bytes()),
'large_payloads_copied': 0}
payloads['MANIFEST.json'] = encoded(manifest)
estimate = sum(len(v) for v in payloads.values())
# The estimate precedes all writes. Reserve extra directory/allocation space.
print(json.dumps({'estimated_payload_bytes': estimate, 'files': len(payloads),
'available_disk_bytes': shutil.disk_usage(destination.parent).free,
'large_payloads_copied': 0}), flush=True)
series.require(shutil.disk_usage(destination.parent).free > estimate + 1048576, 'insufficient disk for pointers')
destination.mkdir() # exclusive creation: a previous collection is never overwritten
for name, data in sorted(payloads.items()):
with (destination / name).open('xb') as output:
output.write(data)
print(json.dumps({'collection': str(destination), 'manifest_sha256': series.digest(payloads['MANIFEST.json']),
'actual_payload_bytes': estimate, 'formal_acceptance': False}))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', type=Path, required=True)
try:
collect(parser.parse_args().build)
except (ValueError, OSError, KeyError, TypeError) as exc:
print(json.dumps({'collected': False, 'error': str(exc)}))
sys.exit(1)
@@ -0,0 +1,930 @@
"""Read-only P04 Stage R. Paths in native records are provenance, not authority.
No engine invocation, source adoption, candidate binding or acceptance occurs here.
"""
import argparse
import hashlib
import json
import math
from pathlib import Path
import re
import struct
import sys
sys.dont_write_bytecode = True
HERE = Path(__file__).resolve().parent
MANIFEST_SHA256 = 'a15f29fa12537f6d8aaf7cabea390922a9f16e3eb9c10981e5742aafc8c4c8a8'
MANIFEST_BYTES = 1244135
PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
RUNS = {'s1': 'p04-s1-native-r02', 's2a': 'p04-s2a-native-r03',
's2b': 'p04-s2b-native-r02', 's3': 'p04-s3-native-r01'}
# Native descriptors pinned from literal files, never ROOT result passed fields.
NATIVE_PINS = {
's1': ('1e26d9585396da9415747d87d77b70f23425a3ee0714e4fca114748f9479fd4e', '01372beb0ffec181c892d3b4e88bce32001e08fb2007938d7bf19135511a7acc', '0b6d11eb34d8af64b22cb806caeb06eed4ec521fa9ee24d1204ea185345e655c'),
's2a': ('2598cc4979b5e58cf71632187e4fc3c1832a37321344a0da0be8b27ce68edfc2', '346d504af64cb1f86b74c668e27305273d3d526e0f56b4fd24bd6942d98b2a32', 'db61ee7ba066aebfb3f4f20a636c0e3de3c8126abd726f312ab68bef313df5fb'),
's2b': ('608df09daa604ca41706b94cf480943e9a5eccd98a1754bc668dbe38d931fb2d', 'd7f114143b99b1e640da88337ea33918d783e46e5b7c6a1dd4444fbe5c65d105', 'c19c16fe193ff0c8caf9b1bdef71d59b7bb6caa0cc6d59c4f0e8f1356795d6eb'),
's3': ('f1e97dcb0d518f980bdf12b46ab78a0b7026e53a0b73b06d64132ea8d60d91cc', '9795df0a049d9888d60a4dd32a77450e81459b0dad0d42fe325ca067322d557a', '6aa632cdc6d7bb9cc79f46e8fcce396a80a8d04fee01c32aba836795d2e52b71'),
}
def require(ok, message):
if not ok:
raise ValueError(message)
def digest(data):
return hashlib.sha256(data).hexdigest()
def unique(rows):
result = {}
for key, value in rows:
require(key not in result, 'duplicate key: ' + str(key))
result[key] = value
return result
def function_bodies(data):
starts = list(re.finditer(rb'(?m)^(?:static )?func ([A-Za-z_][A-Za-z_0-9]*)\(', data))
return {m[1].decode(): data[m.start():starts[i + 1].start() if i + 1 < len(starts) else len(data)]
for i, m in enumerate(starts)}
def source_body_pins(resolver, rows):
for row in rows:
pin = resolver.manifest['product_source'][row['path']]
body = function_bodies(resolver.read(pin['path'], pin))[row['function']]
require(digest(body) == row['production_body_sha256'] == row['registered_body_sha256'],
'full-function-body pin mismatch')
class Series:
def __init__(self, build, name):
self.name, self.build = name, Path(build)
self.run = RUNS[name]
self.base = self.build / self.run
self.resolver = Resolver(self.build / 'audit-support/p04-r17-full-inputs-r01/MANIFEST.json')
self.references = {}
self.nodes = {}
inputs, inventory, execution = [loads(self.read(n, {'sha256': h})) for n, h in zip(
('runner-input.json', 'raw-inventory.json', 'execution.json'), NATIVE_PINS[name])]
self.inputs, self.inventory, self.execution = inputs, inventory, execution
source = loads(self.read('source_before.json', {'sha256': self.resolver.manifest['source_before_sha'][self.run]}))
require(source == inputs['source_before'] == self.resolver.manifest['product_source'] and len(source) == 1338,
'source_before product set mismatch')
selected = {k: v['sha256'] for k, v in source.items()}
require(inputs['QA_overrides'] == ['tests/s3/test_runtime_staff_assignment.gd'], 'QA override membership')
selected[inputs['QA_overrides'][0]] = inputs['QA']['sha256']
require(inputs['selected_source_map'] == selected, 'selected source pin')
self.resolver.read(inputs['QA']['path'], inputs['QA'])
require(loads(self.read('source_after.json')) == source, 'source changed during native run')
for pin in source.values():
self.resolver.read(pin['path'], pin)
for pin in inputs['fixed_input_pins']:
self.resolver.read(pin['path'], pin)
app = loads(self.read_build('audit-support/native-first-current/POST_P18_CURRENT_SOURCE_APPLICABILITY.json',
{'sha256': '46e0cc35cb95496d5f26d3cab7d3a31cb29faacaa16714daab3aefdf65b420a3'}))
source_body_pins(self.resolver, app['ordering_functions'])
require(execution['error'] is None and execution['guard_errors'] == [] and
execution['cleanup_errors'] == [], 'native guards/cleanup failed')
self.pids = []
require([p['phase'] for p in execution['phases']] == ['import', 'parse', 'writer', 'reader'], 'phase membership')
for phase in execution['phases']:
self.ownership(phase)
require(len(set(self.pids)) == 4 and self.pids == execution['actual_captured_PIDs'], 'owned PID membership')
pidset = loads(self.read('PID-set.json'))
require(pidset['engine_PIDs'] == self.pids and pidset['owner_runner_pid'] == inputs['owner_runner_pid'], 'PID set binding')
for path, pin in inventory.items():
self.read('raw/' + path, pin)
self.formal()
self.phase_pairs = 0
phase_files = 0
for path in inventory:
if path.startswith('writer-') and '-phase-' in path:
other = 'reader-' + path[len('writer-'):]
require(other in inventory and self.read('raw/' + path) == self.read('raw/' + other),
'altered phase pair: ' + path)
phase_files += 1
# Both representations are checked above; count each native
# Writer/Reader observation once, not once per file format.
if path.endswith('.var'):
self.phase_pairs += 1
if name == 's3':
require(phase_files == 352 and self.phase_pairs == 176, 'S3 phase files/unique pair counts')
def read_build(self, relative, pin=None):
require(not Path(relative).is_absolute() and all(p not in ('', '.', '..') for p in relative.split('/')),
'raw traversal')
p = ordinary(self.build / relative)
data = p.read_bytes()
if pin is not None:
checked(data, pin, relative)
self.references[relative] = {'sha256': digest(data), 'bytes': len(data)}
return data
def read(self, relative, pin=None):
return self.read_build(self.run + '/' + relative, pin)
def typed(self, label):
if label not in self.nodes:
name = label + '.var'
require(name in self.inventory, 'typed raw missing (JSON-only substitute): ' + label)
self.nodes[label] = variant(self.read('raw/' + name, self.inventory[name]))
return self.nodes[label]
def value(self, label):
return self.typed(label).plain()
def ownership(self, phase):
role = phase['phase']
start = loads(self.read(role + '/started.json'))
end = loads(self.read(role + '/ended.json'))
process = phase['native_process']
require(start['event'] == 'ENGINE_STARTED' and end['event'] == 'ENGINE_ENDED', 'missing PID end')
require(start['actual_pid'] == end['actual_pid'] == phase['actual_pid'] == process['pid'], 'unowned PID')
require(start['owner_pid'] == end['owner_pid'] == self.inputs['owner_runner_pid'], 'wrong PID owner')
require(start['current_process_identity']['ppid'] == start['owner_pid'] and
start['current_process_identity']['pid'] == start['actual_pid'], 'live PID ownership')
require(start['command'] == end['command'] == phase['command'] == process['argv'], 'PID command binding')
require(end['monotonic_ns'] > start['monotonic_ns'] and end['engine_ended'] is True and
end['exit_code'] == end['actual_process_exit_code'] == phase['exit_code'] == process['exit_code'] == 0
and end['timed_out'] is False and end['lifecycle_error'] is None, 'PID end/gate failure')
for stream in ('stdout', 'stderr'):
self.read(role + '/' + stream + '.log', {'sha256': process[stream + '_sha256']})
self.read(role + '/engine-native.log', phase['raw_native_log'])
require(process['strict_native_diagnostics']['errors'] == [] and process['error_lines'] == [], 'native diagnostics')
require(phase.get('collector_errors', []) == [] and phase['validation_error'] is None, 'native phase gate')
env = phase['controlled_environment']
if role in ('writer', 'reader'):
raw = loads(self.read('raw/' + role + '.json'))
require(raw['pid'] == end['actual_pid'] and raw['profile'] == env['THE_TREE_PACK_PROFILE_EXPECTED'], 'raw PID/environment')
self.pids.append(end['actual_pid'])
def formal(self):
before, after = self.execution['formal_before'], self.execution['formal_after']
require(before == after and len(before) == {'s1': 14, 's2a': 1, 's2b': 6, 's3': 22}[self.name], 'formal original drift/count')
roles = {}
originals = []
for role in ('writer', 'reader'):
record = loads(self.read('raw/' + role + '.json'))
require(set(record['slots']) == set(before), 'formal slot membership')
roles[role] = {}
for slot, original in record['slots'].items():
originals.append(original)
roles[role][slot] = self.resolver.slot(original, self.run, role, slot, before[slot])
require(len(set(originals)) == len(originals), 'duplicate original/Reader slot')
require(roles['writer'] == roles['reader'], 'Reader member drift')
inodes = []
for original in originals:
for member in before[next(k for k in before if original.endswith('/' + k))]:
st = ordinary(self.resolver.mapping[original + '/' + member]['relocated']).stat()
inodes.append((st.st_dev, st.st_ino))
require(len(set(inodes)) == len(inodes), 'original/Reader members share inode')
def result(self, conditions, facts=None):
return {'series': self.name, 'verified_conditions': sorted(conditions), 'facts': facts or {},
'product_content': PRODUCT, 'relocation_manifest_sha256': MANIFEST_SHA256,
'source_files': 1338, 'owned_pids': self.pids, 'phase_pairs': self.phase_pairs,
'references': self.references,
'relocated_references': self.resolver.used, 'formal_acceptance': False}
def loads(data):
return json.loads(data, object_pairs_hook=unique,
parse_constant=lambda v: require(False, 'nonfinite JSON'))
def ordinary(path):
path = Path(path)
require(path.is_absolute() and '..' not in path.parts, 'traversal/relative path')
for parent in (path, *path.parents):
require(not parent.is_symlink(), 'symlink: ' + str(parent))
require(path.is_file(), 'missing file: ' + str(path))
return path
def checked(data, descriptor, label):
require(digest(data) == descriptor['sha256'], 'SHA drift: ' + label)
if 'bytes' in descriptor:
require(type(descriptor['bytes']) is int and len(data) == descriptor['bytes'],
'byte length drift: ' + label)
return data
class Resolver:
def __init__(self, manifest):
self.path = ordinary(manifest)
data = self.path.read_bytes()
require(len(data) == MANIFEST_BYTES and digest(data) == MANIFEST_SHA256,
'relocation MANIFEST identity mismatch')
self.manifest = loads(data)
self.base = self.path.parent
self.mapping = self.manifest['original_to_relocated']
require(len(self.mapping) == 1721 and self.manifest['logical_bytes'] == 71488772,
'relocation membership/count')
require(self.manifest['product_content_anchor'] == PRODUCT, 'product content anchor')
targets = []
for original, row in self.mapping.items():
require(Path(original).is_absolute() and '..' not in Path(original).parts,
'invalid original provenance')
p = Path(row['relocated'])
require(p.is_relative_to(self.base) and '..' not in p.parts, 'relocation traversal')
targets.append(str(p))
require(len(set(targets)) == len(targets), 'duplicate relocated member')
self.used = {}
def read(self, original, native):
require(original in self.mapping, 'unmapped original path: ' + original)
row = self.mapping[original]
data = ordinary(row['relocated']).read_bytes()
checked(data, row, 'relocation ' + original)
checked(data, native, 'native ' + original)
self.used[original] = dict(row)
return data
def slot(self, original, run, role, slot, members):
mapping = self.manifest['formal_slot_original_to_relocated']
require(original in mapping, 'unmapped original slot')
row = mapping[original]
require((row['run'], row['role'], row['slot']) == (run, role, slot),
'wrong parent-slot membership')
require(set(members) == {'journal.log', 'manifest.json', 'thumbnail.png', 'world.bin'},
'formal member membership')
directory = Path(row['relocated'])
require(not directory.is_symlink() and {p.name for p in directory.iterdir()} == set(members),
'formal directory membership')
result = {}
for member, pin in members.items():
key = original + '/' + member
require(key in self.mapping and
self.mapping[key]['relocated'] == row['relocated'] + '/' + member,
'formal parent membership')
result[member] = self.read(key, pin)
return result
class Node:
def __init__(self, kind, value, raw):
self.kind, self.value, self.raw = kind, value, raw
def at(self, key):
require(self.kind == 27, 'typed dictionary required')
rows = [v for k, v in self.value if k.value == key and k.kind in (4, 21)]
require(len(rows) == 1, 'missing/ambiguous typed key: ' + str(key))
return rows[0]
def plain(self):
if self.kind == 27:
return unique((k.value, v.plain()) for k, v in self.value)
if self.kind == 28:
return [v.plain() for v in self.value]
return self.value
def variant(data):
"""Strict non-object Godot 4 var_to_bytes subset; retain every typed byte.
Unknown types/flags and trailing bytes fail, never fall back to JSON.
"""
offset = 0
def take(size):
nonlocal offset
require(0 <= size <= len(data) - offset, 'truncated Variant')
result = data[offset:offset + size]
offset += size
return result
def number(fmt):
return struct.unpack('<' + fmt, take(struct.calcsize('<' + fmt)))[0]
def string():
size = number('I')
value = take(size).decode('utf-8')
require(take((-size) % 4) == b'\0' * ((-size) % 4), 'Variant string padding')
return value
def parse(depth=0):
require(depth < 100, 'Variant nesting')
start = offset
header = number('I')
kind, flags = header & 0xffff, header >> 16
require(flags == 0 or (flags == 1 and kind in (2, 3, 28)), f'unsupported Variant flags {kind}:{flags} at {start}')
if kind == 0:
value = None
elif kind == 1:
value = number('I')
require(value in (0, 1), 'Variant bool')
value = bool(value)
elif kind == 2:
value = number('q' if flags else 'i')
elif kind == 3:
value = number('d' if flags else 'f')
elif kind in (4, 21):
value = string()
elif kind in (5, 6, 9, 10, 12, 13, 20):
count = {5: 2, 6: 2, 9: 3, 10: 3, 12: 4, 13: 4, 20: 4}[kind]
value = tuple(number('i' if kind in (6, 10, 13) else 'f') for _ in range(count))
elif kind in (27, 28):
element_type = number('I') if kind == 28 and flags == 1 else None
count = number('I')
require(count < 1000000, 'shared/oversized Variant collection')
if kind == 27:
value = [(parse(depth + 1), parse(depth + 1)) for _ in range(count)]
keys = [(k.kind, k.raw) for k, _ in value]
require(len(set(keys)) == len(keys), 'duplicate typed key')
else:
value = [parse(depth + 1) for _ in range(count)]
require(element_type is None or all(v.kind == element_type for v in value),
'typed array member mismatch')
elif kind == 29:
count = number('I')
value = take(count)
take((-count) % 4)
elif kind in (30, 31, 32, 33, 34):
count = number('I')
require(count < 1000000, 'oversized packed Variant')
value = tuple(string() if kind == 34 else number({30: 'i', 31: 'q', 32: 'f', 33: 'd'}[kind])
for _ in range(count))
else:
raise ValueError('unsupported Variant type: ' + str(kind))
return Node(kind, value, data[start:offset])
result = parse()
require(offset == len(data), 'trailing Variant bytes')
return result
def normalized(node):
"""Only String/StringName and dictionary insertion order may normalize."""
if node.kind in (4, 21):
return ('string', node.value)
if node.kind == 27:
rows = [(normalized(k), normalized(v)) for k, v in node.value]
require(len({repr(k) for k, _ in rows}) == len(rows), 'getter key collision')
return (27, tuple(sorted(rows, key=lambda row: repr(row[0]))))
if node.kind == 28:
return (node.raw[:4], tuple(normalized(v) for v in node.value))
return (node.kind, node.raw)
PERSISTENCE_BODIES = {
'get_day_summary': '449eef926054cf76c39d6b4c016da3d775d41f2b343b4f8ad57b62c594cb32bb',
'create_snapshot': '8c6e4f000e6df6f0f996445a57c4c17223cae4e1df57a9c2e3a605cf4d9f5309',
'_snapshot_root_report': '96084d76beeac11d165753a82160f789cd47b4dec4b6bb8cd6599a4f5ed08efe',
'_visit_snapshot_tree': '70792369c05882b813632a3c96f934928e9a089d420700cfc0d4c8fa971972a0',
}
def persistence_pins(s):
path = 'scripts/operations/operations_coordinator.gd'
pin = s.resolver.manifest['product_source'][path]
source = s.resolver.read(pin['path'], pin)
bodies = function_bodies(source)
report = loads(s.read_build('audit-support/p04-repair19-close-r01/ROOT_ACTUAL_PERSISTENCE_CALL_CHAIN.json'))
require(digest(source) == report['full_file_sha256'] ==
'871a248ee101b6df543fd5cf0440b5d4b0f02c21fa145a99f0687888c1bc6f90', 'persistence whole-source pin')
for function, expected in PERSISTENCE_BODIES.items():
rows = [r for r in report['full_functions'] if r['function'] == function]
require(len(rows) == 1 and digest(bodies[function]) == rows[0]['sha256'] == expected and
len(bodies[function]) == rows[0]['bytes'] and
bodies[function] == rows[0]['full_body'].encode(), 'persistence full-body/call-chain pin')
def getter_array_headers(writer, reader, path='day_summary'):
changes = []
if writer.kind == reader.kind == 27:
for key, value in writer.value:
require(key.kind in (4, 21), 'diagnostic dictionary key')
changes.extend(getter_array_headers(value, reader.at(key.value), path + '/' + key.value))
elif writer.kind == reader.kind == 28:
require(len(writer.value) == len(reader.value), 'diagnostic array length')
if writer.raw[:4] != reader.raw[:4]:
changes.append({'path': path, 'writer_header': writer.raw[:4].hex(),
'reader_header': reader.raw[:4].hex(),
'writer_typed_builtin': struct.unpack('<I', writer.raw[4:8])[0]
if struct.unpack('<I', writer.raw[:4])[0] >> 16 else None,
'reader_typed_builtin': struct.unpack('<I', reader.raw[4:8])[0]
if struct.unpack('<I', reader.raw[:4])[0] >> 16 else None,
'length': len(writer.value)})
for i, (a, b) in enumerate(zip(writer.value, reader.value)):
changes.extend(getter_array_headers(a, b, path + '/' + str(i)))
return changes
def persisted_day_projection(node, *, chain='_snapshot_root_report/_visit_snapshot_tree',
comparison='getter_to_persisted_day1'):
"""Optional, fail-closed encoder port for the permitted day-summary subset.
Output retains scalar Variant bytes. Only StringName, key coercion/order and
generic Array headers can change. Native persisted comparisons never call it.
The root uses capture=false and an empty cache, exactly the uncached source
path. The original raw getters remain independently unequal and preserved.
"""
require(chain == '_snapshot_root_report/_visit_snapshot_tree', 'projection call chain')
require(comparison == 'getter_to_persisted_day1', 'projection forbidden for native persisted comparison')
budget = [1, 0]
def text_bytes(value):
encoded = value.encode('utf-8')
return struct.pack('<II', 4, len(encoded)) + encoded + b'\0' * ((-len(encoded)) % 4)
def count_node():
budget[0] += 1
require(budget[0] <= 2000000, 'MAX_VARIANT_NODES')
def count_text(text):
require(len(text) <= 4096, 'STRING_TOO_LONG')
budget[1] += len(text)
require(budget[1] <= 8000000, 'TOTAL_STRING_BUDGET')
def key_text(key):
if key.kind in (4, 21, 2):
return str(key.value)
# Godot str for these invalid primitive keys, used only by the collision
# prepass, which precedes key validation. Unsupported composite keys are
# outside this optional projection's admitted subset and fail closed.
if key.kind == 0:
return '<null>'
if key.kind == 1:
return 'true' if key.value else 'false'
return None # Refuse at this key's source-order validation step below.
def dictionary(value, depth, root=False):
require(len(value.value) <= 20000, 'COLLECTION_TOO_LARGE')
keys = [key_text(k) for k, _ in value.value]
known_keys = [key for key in keys if key is not None]
require(len(set(known_keys)) == len(known_keys), 'DUPLICATE_CANONICAL_KEY')
converted = []
for (key, child), text in zip(value.value, keys):
require(key.kind in (4, 21, 2), 'INVALID_DICTIONARY_KEY')
require(root or depth + 1 <= 32, 'MAX_VARIANT_DEPTH')
count_node()
count_text(text)
converted.append((text, visit(child, 1 if root else depth + 1)))
converted.sort(key=lambda row: row[0])
return struct.pack('<II', 27, len(converted)) + b''.join(text_bytes(k) + v for k, v in converted)
def visit(value, depth):
require(depth <= 32, 'MAX_VARIANT_DEPTH')
count_node()
if value.kind in (0, 1, 2):
return value.raw
if value.kind == 3:
require(math.isfinite(value.value), 'NON_FINITE_NUMBER')
return value.raw
if value.kind in (4, 21):
count_text(value.value)
return text_bytes(value.value)
if value.kind == 28:
require(len(value.value) <= 20000, 'COLLECTION_TOO_LARGE')
return struct.pack('<II', 28, len(value.value)) + b''.join(visit(v, depth + 1) for v in value.value)
if value.kind == 27:
return dictionary(value, depth)
raise ValueError('UNSAVEABLE_VARIANT')
require(node.kind == 27, 'day summary root must be Dictionary')
return dictionary(node, 0, root=True)
def runtime(observation):
return observation['snapshot']['systems']['runtime']
def service_reservations(observation, state):
service = observation['service']
reservations = observation['reservations']
ids = service['inventory_reservations']
require(ids and len(set(ids)) == len(ids), 'service reservations missing/duplicate')
for key in ids:
rows = [r for r in reservations if r['reservation_id'] == key]
require(len(rows) == 1 and rows[0]['requester_id'] == service['request_id'] and
rows[0]['state'] == state and rows[0]['purpose'] == 'facility_service', 'reservation/service join')
facilities = runtime(observation)['facilities']
require(any(service in f['active_services'] for f in facilities['instances']) or
service['state'] in ('COMPLETED', 'FAILED'), 'service Runtime join')
def sales(observation):
return [r for r in observation['ledger'] if r['category'] == 'SERVICE_SALE']
def s1_semantics(s):
for branch in ('landing', 'no-safe-landing'):
points = {}
for point in ('before', 'boarded', 'depleted', 'landing', 'stopped', 'held', 'recovered'):
label = branch + '-' + point
w, r = s.typed('writer-' + label), s.typed('reader-' + label)
require(w.at('snapshot').raw == r.at('snapshot').raw, 'S1 typed formal replay mismatch')
points[point] = w.plain()
boarded = points['boarded']
task, trip = boarded['task'], boarded['trip']
require(trip['state'] == 'BOARDED' and trip['actor_id'] == 'staff:' + task['assigned_staff_id'], 'S1 task/trip actor join')
require(len(boarded['reservations']) == 1 and task['reservation_ids'] ==
[boarded['reservations'][0]['reservation_id']], 'S1 reservation identity')
reservation = boarded['reservations'][0]
require(reservation['requester_id'] == task['task_id'] and reservation['purpose'] == 'staff_task', 'S1 resource/task join')
for point in ('depleted', 'landing', 'stopped', 'held', 'recovered'):
obs = points[point]
require(obs['task']['task_id'] == task['task_id'] and len(obs['reservations']) == 1, 'S1 task preserved')
current = obs['reservations'][0]
require(all(current[k] == reservation[k] for k in ('reservation_id', 'requester_id', 'item_id', 'stack_id', 'quantity')), 'S1 same resource reservation')
if branch == 'no-safe-landing' and point != 'recovered':
require(current['state'] == 'ACTIVE' and obs['trip'] == trip and obs['task']['progress_minutes'] == task['progress_minutes'], 'S1 no-safe-landing hold')
else:
require(current['state'] == 'RELEASED' and obs['task']['state_reason'] == 'RESCUE_INTERRUPTED', 'S1 landing release reason')
arrivals = [e for e in points['recovered']['arrivals'] if e['payload']['request_id'] == trip['request_id']]
require(len(arrivals) == 1 and arrivals[0]['payload']['emergency'] is (branch == 'landing'), 'S1 landing once')
return ['EV']
def settlement(before, after):
require(before['minute'] == 1439 and after['minute'] == 1440, 'settlement boundary')
require(after['ledger'][:len(before['ledger'])] == before['ledger'], 'ledger history changed')
service = after['service']
require(service['service_id'] == before['service']['service_id'] and service['state'] == 'COMPLETED'
and service['completed_at'] == 1440, 'midnight service completion')
service_reservations(before, 'ACTIVE')
service_reservations(after, 'CONSUMED')
delta = after['ledger'][len(before['ledger']):]
sale = [r for r in delta if r['category'] == 'SERVICE_SALE']
wages = [r for r in delta if r['category'] == 'WAGE']
upkeep = [r for r in delta if r['category'] == 'MAINTENANCE_PAYMENT']
require(len(sale) == 1 and sale[0]['amount'] == service['price_snapshot'] == 25 and
sale[0]['source_entity_id'] == service['facility_id'] and
sale[0]['credit_account'] == 'GUEST_WALLET:' + service['guest_ids'][0], 'sale identity/25G')
idem = after['idempotency']['economy']
require([r['transaction_id'] for r in idem if r['key'] == 'service.payment.' + service['service_id']]
== [sale[0]['transaction_id']], 'sale idempotency join')
require(len(wages) == 1 and wages[0]['amount'] == 255, 'payroll 255G/one transaction')
payroll = [p for p in after['payroll'] if p['settlement_id'] == wages[0]['source_entity_id']]
require(len(payroll) == 1 and payroll[0]['day'] == 1, 'payroll ledger join')
paid = {p['staff_id']: p['payment'] for p in payroll[0]['rows'] if p['payment'] > 0}
require(paid == {'staff_akane': 120, 'staff_bardo': 135}, 'actual paid staff membership')
for staff_id, amount in paid.items():
staff = [p for p in after['staff']['staff'] if p['staff_id'] == staff_id]
require(len(staff) == 1 and staff[0]['daily_wage'] == amount, 'staff wage source')
shifts = [r for r in after['shifts'] if r['staff_id'] == staff_id and r['start_day'] == 0 and r['worked_minutes'] + r['overtime_minutes'] > 0]
require(shifts, 'actual duty missing')
worked = 0
for shift in shifts:
prior = [r for r in before['shifts'] if r['shift_instance_id'] == shift['shift_instance_id']]
require(len(prior) == 1 and shift['overtime_minutes'] == prior[0]['overtime_minutes'], 'duty shift join')
worked += shift['worked_minutes'] - prior[0]['worked_minutes']
require(worked == (1 if staff_id == 'staff_bardo' else 0), 'actual midnight duty delta')
require(len(upkeep) == 3 and sum(r['amount'] for r in upkeep) == 5 and
len({r['transaction_id'] for r in upkeep}) == 1, 'upkeep 5G/three rows/one transaction')
for payment in upkeep:
accrual = [r for r in delta if r['category'] == 'MAINTENANCE_ACCRUAL' and
r['source_entity_id'] == payment['source_entity_id'] and r['amount'] == payment['amount'] and
r['transaction_id'] == payment['transaction_id'] and r['absolute_minute'] == 1439]
require(len(accrual) == 1 and payment['absolute_minute'] == 1440, 'upkeep accrual join')
require(after['staff'] == runtime(after)['staff'] and after['operations'] == runtime(after)['operations']['coordinator'], 'settlement Runtime join')
days = [d for d in after['operations']['day_summaries'] if d['day'] == 1]
require(len(days) == 1 and days[0]['closed_at'] == 1440, 'one daily close')
def recall(node):
command = node.at('command').plain()
require(command['result']['is_replay'] is True and command['payload']['processed_minutes'] == 0
and command['payload']['boundary_counts']['daily_closes'] == 0, 'advance_to replay result')
require(node.at('before').at('snapshot').raw == node.at('after').at('snapshot').raw,
'entire Runtime replay bytes changed')
def s2a_semantics(s):
persistence_pins(s)
for branch in ('C1', 'C2', 'C3'):
before = s.value('writer-' + branch + '-before')
after = s.value('writer-' + branch + '-midnight')
settlement(before, after)
recall(s.typed('writer-C2-recall'))
reader = s.typed('reader-C3-branch')
writer = s.typed('writer-C3-midnight')
require(writer.at('snapshot').raw == reader.at('expected').at('snapshot').raw ==
reader.at('restored').at('snapshot').raw, 'formal Reader exact Runtime restore')
recall(reader.at('recall'))
getter1, getter2 = writer.at('day_summary'), reader.at('restored').at('day_summary')
require(getter1.raw != getter2.raw, 'raw getter byte inequality lost/JSON-only getter substitute')
conditions = ['midnight_settlement', 'midnight_replay']
# Canonical persisted values are compared directly; projection is confined
# to the diagnostic raw getter, never Reader expected/restored Runtime.
canonical = writer.at('canonical_day1')
if persisted_day_projection(getter1) == canonical.raw == persisted_day_projection(getter2):
conditions.append('save_restore')
return conditions
def s2b_semantics(s):
for branch in ('night_absent', 'overtime'):
for minute in (1079, 1080, 1081):
label = branch + '-' + str(minute)
expected = s.typed('writer-' + label).at('snapshot').raw
require(expected == s.typed('reader-' + label + '-restored').at('actual').at('snapshot').raw, 'S2b formal Reader state')
if minute != 1079:
require(expected == s.typed('reader-' + label + '-replay').at('snapshot').raw, 'S2b exact replay')
for phase in ('before-facility', 'after-facility', 'after-staff'):
require(s.typed('writer-' + label + '-' + phase).raw ==
s.typed('reader-' + label + '-' + phase).raw, 'S2b altered phase pair')
s.phase_pairs += 1
rows = [s.value('writer-night_absent-' + str(m)) for m in (1079, 1080, 1081)]
for row in rows:
service_reservations(row, 'ACTIVE')
require(not sales(row), 'night-absent charge')
require(rows[2]['service']['state'] == 'WAITING_FOR_STAFF' and
rows[2]['service']['interruption_reason'] == 'STAFF_UNAVAILABLE' and
rows[2]['service']['elapsed_minutes'] == rows[1]['service']['elapsed_minutes'], 'night staff stop reason/progress')
require(rows[2]['facility']['active_staff_ids'] == [] and all(w['state'] == 'OFF_DUTY' for w in rows[2]['workers']), 'night staff actual absence')
previous = s.value('writer-overtime-1079')
for minute in (1080, 1081):
obs = s.value('writer-overtime-' + str(minute))
require(obs['task']['interrupt_level'] == 0 and obs['worker']['is_overtime'] is True and
obs['worker']['current_task_id'] == obs['task']['task_id'] and
obs['task']['assigned_staff_id'] == obs['worker']['staff_id'] == obs['shift']['staff_id'], 'overtime task/staff/shift join')
require(obs['shift']['shift_instance_id'] == previous['shift']['shift_instance_id'] and
obs['shift']['overtime_minutes'] == previous['shift']['overtime_minutes'] + 1 and
obs['shift']['worked_minutes'] == previous['shift']['worked_minutes'], 'overtime duty once')
guard = s.typed('writer-overtime-' + str(minute) + '-guard')
require(guard.at('result').plain() == {'ok': False, 'reason': 'MINUTE_ALREADY_APPLIED'} and
guard.at('before').raw == guard.at('after').raw, 'same-minute rejection entire Runtime invariant')
previous = obs
return ['night_staff_absent', 'overtime_task']
def s3_semantics(s):
observed = {}
for slot in s.execution['formal_before']:
require(s.typed('writer-' + slot).at('snapshot').raw ==
s.typed('reader-' + slot + '-restored').at('snapshot').raw,
'S3 formal Reader exact Runtime')
for case in ('B1', 'B2a', 'B2b', 'B3', 'B4a', 'B4b'):
before, after, next_ = [s.value('writer-' + case + '-' + p) for p in ('before', 'after', 'next')]
terminal = next_ if case == 'B4b' else after
require(next_['reservations'] == terminal['reservations'], 'release-once reservation drift')
old = {r['reservation_id']: r for r in before['reservations']}
require(len(old) == len(before['reservations']) and set(old) == {r['reservation_id'] for r in terminal['reservations']}, 'release reservation membership')
for reservation in terminal['reservations']:
prior = old[reservation['reservation_id']]
require(prior['state'] == 'ACTIVE' and all(prior[k] == reservation[k] for k in
('requester_id', 'item_id', 'stack_id', 'quantity')), 'release reservation identity')
if reservation['state'] == 'ACTIVE':
request = terminal['requests'][reservation['requester_id']]
require(request and reservation['reservation_id'] in request['reserved_inventory_ids'], 'orphan active reservation')
else:
require(reservation['state'] in ('RELEASED', 'CONSUMED') and
not terminal['requests'][reservation['requester_id']], 'terminal reservation/request join')
if case == 'B1':
require(after['facility']['operator_open'] is False and after['service']['state'] == 'PAUSED'
and after['service']['interruption_reason'] == 'RELATED_FACILITY_INACTIVE'
and after['service']['service_id'] == before['service']['service_id'], 'B1 direct closure/started service/reason')
service_reservations(after, 'ACTIVE')
require(not sales(after), 'B1 stopped charge')
elif case in ('B2b', 'B3', 'B4b'):
require(not sales(terminal) and all(r['state'] == 'RELEASED' for r in terminal['reservations']), 'release zero charge')
if case == 'B4b':
require(terminal['service']['state'] == 'FAILED' and terminal['service']['failure_reason'] == 'BROKEN', 'B4b actual failed reason')
require(before['event']['state'] == 'QUEUED' and after['event']['state'] == 'COMPLETED'
and after['facility']['state'] == 'BROKEN' and after['facility']['durability'] == 0,
'B4b destruction event/facility join')
else:
require(terminal['service']['state'] == 'COMPLETED' and len(sales(terminal)) == 1 and
sales(terminal)[0]['amount'] == 25, 'completion-before-exit/event actual charge')
observed[case] = {'sales': len(sales(terminal)), 'amount': sum(r['amount'] for r in sales(terminal)),
'reservation_states': [r['state'] for r in terminal['reservations']]}
observed['B1_power'] = power_stop(s)
return ['closure_release', 'exit_release', 'patience_release', 'destruction_stop', 'power_stop'], observed
def power_stop(s):
"""B1's observed power boundary, retaining its actual dependency reason."""
labels = ('before', 'after', 'next', 'recovered', 'duplicate_guard')
points = [s.value('writer-B1-' + label) for label in labels]
before, after, next_, recovered, guard = points
require([p['minute'] for p in points] == [959, 960, 961, 962, 963], 'B1 power minutes')
reserve = before['inputs']['reserve']
require(type(reserve) is int and reserve > 0, 'B1 original finite reserve')
identity = ('service_id', 'request_id', 'facility_id', 'guest_ids',
'inventory_reservations', 'price_snapshot', 'duration_minutes', 'started_at')
original = before['service']
released = None
for i, point in enumerate(points):
service, facility = point['service'], point['facility']
state = runtime(point)
guest_b = point['guests']['p04.s3.guest.b']
live_b = [g for g in state['guests']['guests'] if g['guest_id'] == 'p04.s3.guest.b']
if i < 2:
require(live_b == [guest_b] and guest_b['patience_remaining'] == 45,
'B1 real B before/at closure')
if i == 1:
require(guest_b['state'] == 'LEAVING' and guest_b['state_entered_at'] == 960 and
guest_b['exit_reason'] == 'NO_SUITABLE_FACILITY' and
guest_b['outstanding_reservation_ids'] == [], 'B1 normal departure history')
else:
require(guest_b == {} and live_b == [], 'B1 normal despawn after departure')
records = [g for g in state['guests']['persistent_records'] if g['guest_id'] == 'p04.s3.guest.b']
departed = after['guests']['p04.s3.guest.b']
require(len(records) == 1 and set(records[0]) == {'debt_gold', 'guest_id', 'guest_type_id',
'outstanding_reservation_ids', 'unresolved_incident_ids'} and
records[0] == {k: departed[k] for k in records[0]} and
records[0]['outstanding_reservation_ids'] == [], 'B1 persisted departure join')
account_b = 'GUEST_WALLET:p04.s3.guest.b'
wallet_rows = [r for r in point['ledger'] if account_b in (r['credit_account'], r['debit_account'])]
initial_rows = [r for r in before['ledger'] if account_b in (r['credit_account'], r['debit_account'])]
require(point['wallets']['p04.s3.guest.b'] == 50 and wallet_rows == initial_rows and
len(wallet_rows) == 1 and wallet_rows[0]['category'] == 'INITIALIZATION' and
wallet_rows[0]['debit_account'] == account_b and wallet_rows[0]['amount'] == 50,
'B1 B wallet/no economy charge')
require(point['reservations'] == state['inventory']['reservations'], 'B1 immutable reservation join')
require(all(service[k] == original[k] for k in identity), 'B1 power service identity')
require(facility in state['facilities']['instances'] and
(service in facility['active_services'] or service in state['facilities']['service_history']),
'B1 power Runtime facility/service join')
require(point['ledger'] == state['economy']['entries'], 'B1 power Runtime ledger join')
require(point['snapshot']['game_state']['resources']['mana'] ==
(0 if i in (1, 2) else reserve), 'B1 exact public mana reserve')
require(facility['power_available'] is (i not in (1, 2)), 'B1 actual power flag')
require(facility['operator_open'] is (i == 0), 'B1 direct closure retained')
require(service['elapsed_minutes'] == (44 if i < 3 else 45) and
service['duration_minutes'] == 45, 'B1 power progress')
service_reservations(point, 'ACTIVE' if i < 3 else 'CONSUMED')
if i in (1, 2):
require(service['state'] == 'PAUSED' and
service['interruption_reason'] == 'RELATED_FACILITY_INACTIVE' and not sales(point),
'B1 stopped reason/zero charge')
if i:
require(not any(r['request_id'] == 'p04.s3.request.b' for r in state['facilities']['requests']),
'B1 B request absent from full state')
available = sum(stack['quantity'] - stack['reserved_quantity']
for inventory in state['inventory']['inventories']
if inventory['owner_inventory_id'] == 'facility.inventory:' + original['facility_id']
for stack in inventory['stacks'] if stack['item_id'] == 'ingredient/basic_meal')
require(available == point['available_meals'] == 1, 'B1 full-state available meal stock')
rows = [r for r in point['reservations'] if r['requester_id'] == 'p04.s3.request.b']
require(len(rows) == 1 and rows[0]['state'] == 'RELEASED' and
rows[0]['terminal_at'] == 960 and point['available_meals'] == 1 and
point['requests']['p04.s3.request.b'] == {}, 'B1 queued release/stock/patience')
if released is not None:
require(rows[0] == released, 'B1 repeated release')
released = rows[0]
require(not sales(before) and recovered['service']['state'] == guard['service']['state'] == 'COMPLETED'
and recovered['service']['completed_at'] == 962, 'B1 one recovery completion')
paid = sales(recovered)
require(len(paid) == 1 and paid[0]['amount'] == original['price_snapshot'] == 25 and
paid[0]['absolute_minute'] == 962 and paid[0]['source_entity_id'] == original['facility_id'] and
paid[0]['credit_account'] == 'GUEST_WALLET:' + original['guest_ids'][0] and
guard['ledger'] == recovered['ledger'], 'B1 recovery payment/no extra charge')
links = [r for r in runtime(recovered)['economy']['idempotency']
if r['key'] == 'service.payment.' + original['service_id']]
require(len(links) == 1 and links[0]['transaction_id'] == paid[0]['transaction_id'],
'B1 recovery transaction identity')
commands = s.value('writer-B1-commands')
saved = before['inputs']['recovery']
restored = [c for c in commands['submitted'] if c['serial'] == saved['serial']]
require(len(restored) == 1, 'B1 saved public command membership')
command = restored[0]
require(all(command[k] == saved[k] for k in ('at', 'kind', 'payload', 'idempotency_key')) and
command['issued_at'] == saved['issue_minute'] == 961 and
command['result']['command_id'] == saved['command_id'] and
command['payload'] == {'deltas': {'mana': reserve}}, 'B1 same saved reserve command')
for kind, payload in (
('change_resources', {'deltas': {'mana': -reserve}}),
('set_facility_policy', {'facility_id': original['facility_id'], 'patch': {'business_open': False}})):
matches = [c for c in commands['submitted'] if c['at'] == 960 and c['kind'] == kind and c['payload'] == payload]
require(len(matches) == 1, 'B1 public stop command')
completed = [r for r in commands['completed_including_fixture']
if r['result']['command_id'] == matches[0]['result']['command_id']]
require(len(completed) == 1 and completed[0]['result']['code'] == 'OK' and
completed[0]['result']['is_replay'] is False, 'B1 stop command completion')
phase = s.value('writer-B1-phase-961-APPLY_COMMANDS')
require(phase['minute'] == 961 and phase['phase'] == 'APPLY_COMMANDS' and
phase['snapshot']['game_state']['resources']['mana'] == reserve and
phase['facility']['power_available'] is False and phase['facility']['operator_open'] is False and
phase['service'] == next_['service'] and phase['ledger'] == next_['ledger'],
'B1 restored reserve precedes next power/service evaluation')
return {'reserve': reserve, 'stopped_minutes': [960, 961], 'completion_minute': 962,
'payment': 25, 'interruption_reason': 'RELATED_FACILITY_INACTIVE'}
def verify_s1(build):
s = Series(build, 's1')
return s.result(s1_semantics(s), {'paid_service_power_claim': False})
def verify_s2a(build):
s = Series(build, 's2a')
conditions = s2a_semantics(s)
extra, paid_facts = paid_series(s)
headers = getter_array_headers(s.typed('writer-C3-midnight').at('day_summary'),
s.typed('reader-C3-branch').at('restored').at('day_summary'))
audit = loads(s.read_build('audit-support/p04-repair19-close-r01/ROOT_S2A_TYPED_ARRAY_HEADER_AUDIT.json'))
require(len(headers) == 6 and sorted(headers, key=lambda r: r['path']) ==
sorted(audit['generic_array_header_changes'], key=lambda r: r['path']),
'six raw generic array header changes')
return s.result(conditions + extra, {'paid_originals': paid_facts, 'raw_getter_bytes_equal': False, 'sales_gold': 25, 'payroll_gold': 255,
'generic_array_header_changes': headers, 'persistence_body_pins': PERSISTENCE_BODIES,
'upkeep_gold': 5, 'settlement_replay_is_request_resend': False,
'save_restore_gap': None if 'save_restore' in conditions else
'Exact encoder projection of raw day_summary differs from native canonical_day1'})
def paid_series(s):
prefix = 'audit-support/p04-current-native-series-r01/'
original = str(s.build / prefix / 'SOURCE_MANIFEST.json')
s1_input = loads(s.read_build(RUNS['s1'] + '/runner-input.json', {'sha256': NATIVE_PINS['s1'][0]}))
pins = [p for p in s1_input['fixed_input_pins'] if p['path'] == original]
require(len(pins) == 1, 'paid native manifest pin missing')
manifest = loads(s.resolver.read(original, pins[0]))
rows = manifest['files']
require(len({r['copy'] for r in rows}) == len(rows), 'duplicate paid native member')
mapping = {r['copy']: r for r in rows}
for row in rows:
s.read_build(prefix + row['copy'], row)
def read(name):
require(name in mapping, 'paid raw out of manifest membership')
return s.read_build(prefix + name, mapping[name])
paid = 'p04-paid-shift-midnight-native-r01'
resend = 'p04-idempotency-paid-midnight-native-r01'
for run in (paid, resend):
inputs = loads(read(run + '/runner-input.json'))
require(inputs['source_before'] == s.resolver.manifest['product_source'], 'paid current source mismatch')
for pin in inputs['source_before'].values():
s.resolver.read(pin['path'], pin)
execution = loads(read(run + '/execution.json'))
require(execution['error'] is None and execution['cleanup_errors'] == [], 'paid execution cleanup')
# Use the same PID/log/exit checks with this native layout, without
# treating any ROOT summary as a process receipt.
context = Series.__new__(Series)
context.build, context.run = s.build, prefix + run
context.references, context.inputs, context.pids = s.references, inputs, []
for phase in execution['phases']:
context.ownership(phase)
require(context.pids == execution['actual_captured_PIDs'], 'paid owned PID membership')
def snapshot(run, label):
return variant(read(run + '/order-evidence/' + label + '.var'))
shift_nodes = [snapshot(paid, 'midnight-first-paid-' + suffix) for suffix in
('before-handover-17-59', 'at-completion-before-handover-18-00', 'after-handover-18-00')]
shift_states = [n.at('systems').at('runtime').plain() for n in shift_nodes]
shift_sales = [[e for e in state['economy']['entries'] if e['category'] == 'SERVICE_SALE'] for state in shift_states]
# This supplied run has no sale at 1080. Recording that fact is not a
# shift_completion proof, even though the filenames say "paid".
require(all(not rows for rows in shift_sales), 'paid shift raw changed; review exact boundary semantics')
observations = loads(read(resend + '/order-evidence/observations.json'))
results = observations['duplicate_results']
require([r['label'] for r in results] == ['queued-same-minute', 'paused-00-01', 'completed-00-02'], 'resend case membership')
final = None
for row in results:
label = 'midnight-duplicate-' + row['label']
before, after = [snapshot(resend, label + '-' + side) for side in ('before', 'after')]
require(before.at('clock').raw == after.at('clock').raw, 'resend same-minute clock')
a, b = [n.at('systems').at('runtime') for n in (before, after)]
for key in ('economy', 'facilities', 'inventory', 'guests', 'staff'):
require(a.at(key).raw == b.at(key).raw, 'resend duplicate generation/payment: ' + key)
command = row['command_result']
require(command['code'] == 'DUPLICATE_IDEMPOTENCY_KEY' and
command['details']['original_result']['details']['request_id'] == row['request_id'], 'resend actual request join')
final = b.plain()
history = [v for v in final['facilities']['service_history'] if v['request_id'] == results[0]['request_id']]
require(len(history) == 1 and history[0]['state'] == 'COMPLETED', 'resend one completed service')
service = history[0]
sales_ = [e for e in final['economy']['entries'] if e['category'] == 'SERVICE_SALE']
require(len(sales_) == 1 and sales_[0]['amount'] == service['price_snapshot'] == 25 and
sales_[0]['absolute_minute'] == service['completed_at'], 'resend one payment')
require([e['transaction_id'] for e in final['economy']['idempotency'] if e['key'] == 'service.payment.' + service['service_id']]
== [sales_[0]['transaction_id']], 'resend payment/service idempotency join')
return ['same_minute_replay'], {'shift_completion': 'missing: zero SERVICE_SALE rows at 1080',
'same_minute_replay': 'request resend, not advance_to settlement replay',
'completed_service_id': service['service_id'], 'transaction_id': sales_[0]['transaction_id']}
def verify_s2b(build):
s = Series(build, 's2b')
return s.result(s2b_semantics(s), {'same_minute_rejection': 'MINUTE_ALREADY_APPLIED'})
def verify_s3(build):
s = Series(build, 's3')
conditions, facts = s3_semantics(s)
return s.result(conditions, facts)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build', type=Path, required=True)
args = parser.parse_args()
failed = False
for name in RUNS:
try:
result = globals()['verify_' + name](args.build)
print(json.dumps({k: v for k, v in result.items() if k not in ('references', 'relocated_references')}, sort_keys=True))
except (ValueError, OSError, KeyError, TypeError) as exc:
print(json.dumps({'series': name, 'verified_conditions': [], 'error': str(exc)}))
failed = True
return int(failed)
if __name__ == '__main__':
sys.exit(main())
+316 -18
View File
@@ -15,6 +15,7 @@ import subprocess
import sys
import tempfile
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[1]
SPEC = ROOT / 'docs/spec/acceptance/P04-01.json'
SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881'
@@ -24,7 +25,8 @@ REGISTERED_COMMIT = 'deb1bcb2374eb378b840ca345db952bd8572cb6e'
EXECUTION_ORIGIN = '9767b0a28ca8d252520307dbbac60a1d8cfb87ab'
CHECKER_PATH = 'tools/check_p04_order_evidence.py'
DOC_SCOPE = 'docs/plans/original-completion-2026-10-02/execution/p04-01/'
MAIN_ANCHOR = '9694f50ee63750192376ca69bc7dda3f884784c5'
MAIN_ANCHOR = '9b1a18c1eb25ca1300dcd59da78423c0dda2f498'
TESTED_PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
SELECTION_SHA = '320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4'
_GIT_ENV = None
# Fixed membership, never supplied by the evidence index.
@@ -135,10 +137,17 @@ def literal(a, b):
def git_bytes(*args):
env = _GIT_ENV if _GIT_ENV is not None else dict(os.environ, GIT_OPTIONAL_LOCKS='0')
env = _GIT_ENV if _GIT_ENV is not None else git_environment()
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
def git_environment():
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_CONFIG_NOSYSTEM='1',
GIT_CONFIG_GLOBAL=os.devnull)
return env
@contextmanager
def isolated_objects():
"""merge-tree writes only disposable objects; canonical refs/index stay untouched.
@@ -149,11 +158,9 @@ def isolated_objects():
global _GIT_ENV
require(_GIT_ENV is None, 'nested object store')
objects = git_bytes('rev-parse', '--path-format=absolute', '--git-path', 'objects').decode().strip()
with tempfile.TemporaryDirectory(prefix='p04-object-audit-') as temporary:
env = dict(os.environ)
for key in ('GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_INDEX_FILE', 'GIT_QUARANTINE_PATH'):
env.pop(key, None)
build = Path(os.environ['BUILD_OUTPUT_DIR']).resolve(strict=True)
with tempfile.TemporaryDirectory(prefix='p04-object-audit-', dir=build) as temporary:
env = git_environment()
env.update(GIT_OBJECT_DIRECTORY=temporary, GIT_ALTERNATE_OBJECT_DIRECTORIES=objects,
GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1')
_GIT_ENV = env
@@ -208,7 +215,7 @@ def supplied_file(directory, descriptor):
if path.is_absolute():
support = directory / 'audit-support'
roots = [support / n for n in ('p04-whole-closure-r01', 'native-first-current',
'p04-current-native-series-r01')]
'p04-current-native-series-r01', 'p04-r17-full-inputs-r01')]
matches = [root for root in roots if path.is_relative_to(root)]
require(len(matches) == 1, 'unsupplied external material: ' + name)
path = safe(matches[0], path.relative_to(matches[0]).as_posix())
@@ -304,6 +311,128 @@ def verify_product_target(index, directory, checker_commit):
'tested_product': tested, 'product_target': target}
def ancestor(older, newer):
# merge-base's nonzero status is data only for the ordinary non-ancestor case.
try:
git_bytes('merge-base', '--is-ancestor', older, newer)
return True
except subprocess.CalledProcessError as exc:
require(exc.returncode == 1, 'cannot determine ancestry')
return False
def submission_role(commit):
require(ancestor(REGISTERED_COMMIT, commit) and not ancestor(MAIN_ANCHOR, commit),
'invalid submission ancestry')
changed = git_bytes('diff', '--no-ext-diff', '--no-renames', '--name-only', '-z',
REGISTERED_COMMIT, commit).split(b'\0')
require(all(allowed_path(p.decode()) for p in changed if p), 'submission outside allowed_paths')
def actual_checkout():
"""Bind the executing modules and every worktree file to the actual HEAD."""
commit = git_bytes('rev-parse', '--verify', 'HEAD^{commit}').decode().strip()
tree = git_bytes('rev-parse', 'HEAD^{tree}').decode().strip()
entries = tree_entries(tree)
require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all',
'--ignore-submodules=none'), 'checkout is not clean')
require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution')
for path, (mode, oid) in entries.items():
actual = safe(ROOT, path)
require(actual.read_bytes() == git_bytes('cat-file', 'blob', oid), 'HEAD/tree/FS bytes: ' + path)
require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode,
'HEAD/tree/FS mode: ' + path)
# Also reject untracked directory symlinks, including ones ignored by Git.
for parent, directories, files in os.walk(ROOT, followlinks=False):
if Path(parent) == ROOT:
directories[:] = [p for p in directories if p != '.git']
files = [p for p in files if p != '.git']
for name in directories + files:
require(not (Path(parent) / name).is_symlink(), 'worktree symlink')
require(Path(__file__).resolve() == safe(ROOT, CHECKER_PATH), 'checker module location')
return commit, tree, entries
def verify_roles(index, directory):
"""Infer roles from Git, never from an index or a caller's stage label."""
require(not ({'candidate_commit', 'candidate_tree', 'code_only', 'candidate', 'phase'} & index.keys()),
'candidate/code-only/phase declarations are not evidence')
commit, actual_tree, actual = actual_checkout()
require(_GIT_ENV is not None, 'isolated object store required')
if not ancestor(MAIN_ANCHOR, commit):
submission_role(commit)
submitted, main, role = commit, MAIN_ANCHOR, 'prospective composition'
else:
parents = git_bytes('show', '-s', '--format=%P', commit).decode().split()
require(len(parents) == 2, 'integration/final requires a two-parent no-ff merge')
main, submitted = parents
require(ancestor(MAIN_ANCHOR, main), 'merge main ancestry')
submission_role(submitted)
role = 'integration/final'
composed = git_bytes('-c', 'rerere.enabled=false', 'merge-tree', '--write-tree',
main, submitted).decode().strip()
require(re.fullmatch('[0-9a-f]{40}', composed) is not None, 'conflicted composition')
main_entries, patch, target = [tree_entries(t) for t in (main, submitted, composed)]
anchor_entries = tree_entries(MAIN_ANCHOR)
for path in main_entries.keys() | target.keys():
require(main_entries.get(path) == target.get(path) or allowed_path(path),
'composition changed game/unknown source: ' + path)
# Metadata membership comes from the real main anchor, never a declared exclusion.
metadata = sorted(p for p in anchor_entries if not selected_path(p))
for path in target:
require(selected_path(path) or allowed_path(path) or path in metadata,
'unknown source hidden as metadata: ' + path)
if role == 'integration/final':
require(actual_tree == composed and actual == target, 'actual merge differs from composition')
require(target[CHECKER_PATH] == patch[CHECKER_PATH] == actual[CHECKER_PATH],
'checker blob differs from submission')
for path in patch:
if allowed_path(path):
require(target.get(path) == patch[path], 'reviewed patch differs in merge: ' + path)
binding = index['product_binding']
require(set(binding) == {'tested_product', 'runner_inputs'}, 'unknown product role/metadata declaration')
require(binding['tested_product'] == TESTED_PRODUCT, 'wrong tested revision')
product = product_manifest(target)
tested_entries = tree_entries(TESTED_PRODUCT)
expected = product_manifest(tested_entries)
require(len(product) == 1338 and product == expected and
product_manifest(main_entries) == expected and product_manifest(anchor_entries) == expected,
'complete 1338-file native product mismatch')
require(all(target[p][0] == main_entries[p][0] == tested_entries[p][0] for p in product),
'native product mode mismatch')
require(index['source_map'] == {p: v['sha256'] for p, v in product.items()}, 'stale pin: source map')
runner_paths = set()
for descriptor in binding['runner_inputs']:
path = supplied_file(directory, descriptor)
require(path not in runner_paths, 'duplicate runner input')
runner_paths.add(path)
native = load(path)
before = native['source_before']
require(set(before) == set(product) and all(
{'bytes': v['bytes'], 'sha256': v['sha256']} == product[p] for p, v in before.items()),
'runner complete source map mismatch')
require(native['runner_sha256'] == product['tools/run_all_tests.py']['sha256'], 'runner source pin')
overrides = native['QA_overrides']
require(len(overrides) == 1 and overrides[0].startswith('tests/') and overrides[0] in product,
'QA override membership')
selected = {p: v['sha256'] for p, v in product.items()}
selected[overrides[0]] = native['QA']['sha256']
require(native['selected_source_map'] == selected, 'selected QA source map')
needed = native.get('fixed_input_pins', []) + [native['QA']]
require(set(descriptor['materials']) == {p['path'] for p in needed}, 'input material membership')
for pin in needed:
material = supplied_file(directory, descriptor['materials'][pin['path']])
require(material.stat().st_size == pin['bytes'] and sha(material) == pin['sha256'], 'input bytes')
require(sha(supplied_file(directory, descriptor['wrapper'])) == native['wrapper_sha256'], 'wrapper bytes')
require(runner_paths, 'runner inputs missing')
require(index['versions'] == {'registered': REGISTERED_COMMIT, 'execution_origin': EXECUTION_ORIGIN,
'tested': TESTED_PRODUCT}, 'evidence version roles')
return composed, {'role': role, 'actual_head': commit, 'actual_tree': actual_tree,
'submission': submitted, 'main': main, 'existing_metadata': metadata,
'manifest_sha256': manifest_digest(product), 'runner_paths': runner_paths,
'tested_product': TESTED_PRODUCT}
def function_bytes(data):
"""Top-level named func through next named func, preserving every raw byte.
@@ -366,6 +495,10 @@ def boundary_conditions(path, function):
def applicability(index, spec, source, head):
tested = index.get('versions', {}).get('tested')
require(isinstance(tested, str) and tree_entries(tested) and
product_manifest(tree_entries(tested)) == product_manifest(tree_entries(head)),
'wrong tested revision')
registered = spec['current_source_pins']
require(index.get('registered_pins') == registered, 'registered pin declaration')
for path, digest in registered.items():
@@ -805,6 +938,148 @@ def verify_native(base):
verified_conditions=['dependency_stop', 'recovery_completion', 'paused_midnight'])
def verify_shift_completion(base):
"""C's first 18:00 transaction, never the paid-midnight 1442 sale."""
for row in load(safe(base, 'SOURCE_MANIFEST.json'))['files']:
native_file(base, row['copy'], row)
require(native_applicability(base) == TESTED_PRODUCT, 'shift tested revision')
pids = native_ownership(base)
require([pids['actual-native/' + phase] for phase in NATIVE_RUNS['actual-native']]
== [77462, 77504, 77536], 'shift ended PID identity')
inputs = load(safe(base, 'actual-native/runner-input.json'))
expected = product_manifest(tree_entries(TESTED_PRODUCT))
require(len(inputs['source_before']) == 1338 and
{p: {'bytes': v['bytes'], 'sha256': v['sha256']} for p, v in inputs['source_before'].items()}
== expected, 'shift complete native source')
prefix = 'actual-native/order-evidence/'
observations = load(safe(base, prefix + 'observations.json'))
labels = ['first-before-17-59', 'first-18-00-before-facility', 'first-18-00-after-facility',
'first-18-00-after-staff', 'first-18-00-tick-complete', 'first-18-01-tick-complete']
points = []
previous_ledger = None
service_id = None
paid_rows = None
original_service = None
completed_service = None
for i, label in enumerate(labels):
rows = [r for r in observations['observations'] if r['label'] == label]
require(len(rows) == 1, 'shift phase membership')
observation = rows[0]
snapshot = load(safe(base, prefix + label + '.json'))
minute = [1079, 1080, 1080, 1080, 1080, 1081][i]
require(snapshot['clock']['absolute_minute'] == observation['minute'] == minute, 'shift phase clock')
state = snapshot['systems']['runtime']
service = observation['service']
if i == 0:
service_id = service['service_id']
original_service = service
identity = ('service_id', 'request_id', 'facility_id', 'guest_ids', 'inventory_reservations',
'price_snapshot', 'duration_minutes', 'started_at', 'staff_ids')
require(all(service[k] == original_service[k] for k in identity), 'shift original service identity')
require(service['service_id'] == service_id and service['elapsed_minutes'] == (44 if i < 2 else 45)
and service['duration_minutes'] == 45 and service['price_snapshot'] == 25,
'shift service identity/progress')
candidates = state['facilities']['service_history'] + [s for f in state['facilities']['instances']
for s in f['active_services']]
require([s for s in candidates if s['service_id'] == service_id] == [service], 'shift raw service join')
require(observation['reservations'] == state['inventory'], 'shift inventory join')
reserved = [r for r in state['inventory']['reservations'] if r['reservation_id'] in service['inventory_reservations']]
require(reserved and len(reserved) == len(service['inventory_reservations']) and all(
r['requester_id'] == service['request_id'] and r['state'] == ('ACTIVE' if i < 2 else 'CONSUMED')
and r['terminal_at'] == (-1 if i < 2 else 1080) for r in reserved), 'shift reservation join')
day2 = ['p04.order.server.a', 'p04.order.server.b']
require(observation['active_staff'] == (day2 if i < 3 else []), 'shift DAY2 before/after handover')
facility = [f for f in state['facilities']['instances'] if f['instance_id'] == service['facility_id']]
require(len(facility) == 1 and facility[0]['active_staff_ids'] == observation['active_staff'],
'shift full-state active staff join')
require([w['staff']['staff_id'] for w in observation['workers']] == day2, 'shift worker identity set')
for worker in observation['workers']:
require(worker['staff'] in state['staff']['staff'],
'shift worker raw join')
if i < 3:
require(worker['shift'] in state['staff']['shifts'] and worker['is_present'] is True and worker['shift']['kind'] == 'DAY' and
worker['shift']['end_at'] == 1080 and worker['shift']['actual_ended_at'] == -1,
'shift original DAY worker present')
else:
ended = [r for r in state['staff']['shifts'] if r['staff_id'] == worker['staff']['staff_id']]
require(worker['shift'] == {} and worker['is_present'] is False and
worker['staff']['state'] == 'OFF_DUTY' and len(ended) == 1 and
ended[0]['actual_ended_at'] == ended[0]['end_at'] == 1080,
'shift normal handover history')
ledger = state['economy']['entries']
sale = [r for r in ledger if r['category'] == 'SERVICE_SALE']
if i < 2:
require(sale == [] and observation['receipts'] == [], 'shift precompletion charge')
else:
require(service['state'] == 'COMPLETED' and service['completed_at'] == 1080 and len(sale) == 1,
'shift single completion')
transaction = sale[0]
require(transaction['transaction_id'] == 'ledger_00000006' and transaction['amount'] == 25 and
transaction['absolute_minute'] == 1080 and transaction['source_entity_id'] == service['facility_id']
and transaction['credit_account'] == 'GUEST_WALLET:' + service['guest_ids'][0], 'shift sale identity')
receipts = receipt_rows(observation['receipts'])
require(receipts == [r for r in ledger if r['transaction_id'] == 'ledger_00000006'], 'shift receipt raw join')
require([r for r in state['economy']['idempotency'] if r['key'] == 'service.payment.' + service_id]
== [{'key': 'service.payment.' + service_id, 'transaction_id': 'ledger_00000006'}],
'shift idempotency join')
if i == 2:
completed_service = service
paid_rows = observation['receipts']
require(paid_rows == observations['first_receipts'] and paid_rows[0]['staff_at_receipt'] == day2,
'shift receipt precedes handover')
else:
require(service == completed_service and ledger == previous_ledger and
observation['receipts'] == paid_rows, 'shift extra charge/completion')
previous_ledger = ledger
points.append(label)
return {'verified_conditions': ['shift_completion'], 'phases': points, 'transaction_id': 'ledger_00000006'}
def combine_native_series(directory, native):
"""Independent R composition; rerun raw checks, never trust condition scalars.
The C verifier and its fixtures remain unchanged. This function does not
establish candidate applicability, and cannot grant a successful whole gate.
"""
import importlib.util
module_path = ROOT / DOC_SCOPE / 'p04_native_series.py'
module_spec = importlib.util.spec_from_file_location('p04_series', module_path)
series = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(series)
c_base = directory / 'audit-support/native-first-current'
require(native == verify_native(c_base), 'C result must be recomputed from raw')
base = directory / 'audit-support/p04-raw-collect-r20'
manifest_path = safe(base, 'MANIFEST.json')
manifest = load(manifest_path)
require(manifest['protocol'] == 'p04-native-series-collection-v1' and
manifest['relocation_manifest_sha256'] == series.MANIFEST_SHA256,
'series relocation binding')
require(set(manifest['series']) == set(series.RUNS), 'series manifest membership')
shift = verify_shift_completion(c_base)
verified = set(native['verified_conditions']) | set(shift['verified_conditions'])
bindings = {}
for name in series.RUNS:
binding = manifest['series'][name]
path = safe(base, binding['path'])
require(path.name == name + '.json' and sha(path) == binding['sha256'] and
path.stat().st_size == binding['bytes'] and
binding['relocation_manifest_sha256'] == series.MANIFEST_SHA256, 'series manifest SHA binding')
recorded = load(path)
require(recorded['verifier_sha256'] == sha(module_path), 'series verifier changed since collection')
actual = getattr(series, 'verify_' + name)(directory)
require({k: v for k, v in recorded.items() if k != 'verifier_sha256'} == actual,
'series raw result/reference drift')
verified.update(actual['verified_conditions'])
bindings[name] = binding['sha256']
return {'verified_conditions': sorted(verified), 'series_manifest_sha256': sha(manifest_path),
'C_source_manifest_sha256': sha(safe(c_base, 'SOURCE_MANIFEST.json')),
'C_provenance_sha256': sha(safe(c_base, 'OWNERSHIP_RAW_PROVENANCE.json')),
'series_bindings': bindings, 'relocation_manifest_sha256': series.MANIFEST_SHA256,
'missing_conditions': sorted((set(REQUIRED) | {'EV'}) - verified),
'nativeWindows': {name: 'pending' for name in sorted(set(REQUIRED) | {'EV'})},
'stage_A': 'candidate binding stage A pending', 'formal_acceptance': False}
def verify(directory):
# Contract hashes the supplied spec string, while the checked-in JSON has
# one final LF. Accept only that exact representation difference.
@@ -812,14 +1087,23 @@ def verify(directory):
require(sha(SPEC) == SPEC_HASH or (spec_bytes.endswith(b'\n') and
hashlib.sha256(spec_bytes[:-1]).hexdigest() == SPEC_HASH), 'dispatch spec pin mismatch')
spec = load(SPEC)
evidence = directory / 'p04-evidence'
evidence = Path(os.environ.get('P04_EVIDENCE_DIR', str(directory / 'p04-evidence'))).absolute()
require(not evidence.is_symlink(), 'symlink evidence directory')
index = load(safe(evidence, 'index.json'))
require(type(index.get('schema_version')) is int and index['schema_version'] == 1, 'schema_version')
require(type(index.get('schema_version')) is int and index['schema_version'] in (1, 2), 'schema_version')
require(index.get('task_id') == 'P04-01' and index.get('formal_acceptance') is False, 'task/acceptance')
checker_commit = git_bytes('rev-parse', 'HEAD').decode().strip()
require(index.get('candidate_commit') == checker_commit, 'checker invocation commit mismatch')
head, target_result = verify_product_target(index, directory, checker_commit)
head, target_result = verify_roles(index, directory)
if index['schema_version'] == 2:
import importlib.util
module_path = ROOT / DOC_SCOPE / 'build_p04_index.py'
module_spec = importlib.util.spec_from_file_location('p04_index', module_path)
builder = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(builder)
expected = builder.payload(directory, sys.modules[__name__])
require(index == expected, 'forged index/raw mapping differs from recomputation')
require(not index['raw_conditions']['missing_conditions'], 'uncollected original boundaries: ' +
', '.join(index['raw_conditions']['missing_conditions']))
return target_result
source = index.get('source_map')
applicability(index, spec, source, head)
oldmap = load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json')
@@ -903,23 +1187,37 @@ def verify(directory):
require(native['production_commit'] == target_result['tested_product'], 'native production role mismatch')
# This adapter covers only the supplied dependency/formal series. Never
# promote it to the missing original scenarios through scalar booleans.
missing = (set(REQUIRED) | {'EV'}) - set(native['verified_conditions'])
combined = combine_native_series(directory, native)
missing = set(combined['missing_conditions'])
require(not missing, 'uncollected original boundaries: ' + ', '.join(sorted(missing)) +
'; formal_original_credit=0')
return target_result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-only', action='store_true', required=True)
parser.parse_args()
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument('--verify-only', action='store_true')
mode.add_argument('--verify-series-only', action='store_true', help='Read-only Stage R composition; always leaves Stage A pending')
args = parser.parse_args()
try:
require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required')
if args.verify_series_only:
directory = Path(os.environ['BUILD_OUTPUT_DIR']).absolute()
native = verify_native(directory / 'audit-support/native-first-current')
result = combine_native_series(directory, native)
result.update(task_id='P04-01', machine_verified=False,
error='candidate binding stage A pending; uncollected original boundaries: ' +
', '.join(result['missing_conditions']))
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
return 1
with isolated_objects():
verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve())
result = verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve())
except (ValueError, OSError, KeyError, TypeError, IndexError, AttributeError, subprocess.SubprocessError) as exc:
print(json.dumps({'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': str(exc)}, ensure_ascii=False))
return 1
print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False}))
print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False,
'role': result['role'], 'actual_head': result['actual_head']}))
return 0