P04-01: 同時発生の更新規約
This commit is contained in:
@@ -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.
|
||||
+2
@@ -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}
|
||||
+1
@@ -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"]}
|
||||
+153
@@ -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"
|
||||
}
|
||||
}
|
||||
+367
@@ -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()
|
||||
+89
@@ -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))
|
||||
+14
-6
@@ -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))
|
||||
+123
@@ -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())
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user