feat(R03): add deterministic episode record foundation #11

Merged
pyrrha merged 2 commits from R03-episode-foundation into main 2026-09-21 00:03:07 +00:00
Member

Closes #10.

Scope

Implements Tasks 1–2 of the accepted apparatus plan — only the model-free package and the R03 episode record boundary.

  • Episode is frozen and recursively copies/immutable-wraps JSON inputs.
  • Canonical JSON is sorted UTF-8 JSON and rejects unsupported/non-finite values.
  • record_hash() covers complete scorer-side canonical state.
  • candidate_payload() is structurally incapable of exporting answer, latent program, seed, canonical input state, split, or provenance.
  • Adds packaging metadata and ignores generated *.egg-info/ metadata.

Explicit non-goals

No generator family, renderer, scorer, model, checkpoint, dataset, network fetch, GPU call, or experiment run.

Verification

Fresh disposable venv:

python3 -m venv /tmp/smco-r03-venv
/tmp/smco-r03-venv/bin/python -m pip install --editable .
/tmp/smco-r03-venv/bin/python -m unittest discover -s tests -v
# 8 tests passed

Also passed python3 scripts/check_docs.py, python3 scripts/build_results_ledger.py --check, and git diff --check.

Required Research A review

Vesper: review the canonicalization/hash boundary, deep-copy/immutability behavior, candidate payload exclusions, and whether this prematurely commits anything that belongs to later generator/scorer work.

Closes #10. ## Scope Implements Tasks 1–2 of the accepted apparatus plan — only the model-free package and the R03 episode record boundary. - `Episode` is frozen and recursively copies/immutable-wraps JSON inputs. - Canonical JSON is sorted UTF-8 JSON and rejects unsupported/non-finite values. - `record_hash()` covers complete scorer-side canonical state. - `candidate_payload()` is structurally incapable of exporting answer, latent program, seed, canonical input state, split, or provenance. - Adds packaging metadata and ignores generated `*.egg-info/` metadata. ## Explicit non-goals No generator family, renderer, scorer, model, checkpoint, dataset, network fetch, GPU call, or experiment run. ## Verification Fresh disposable venv: ```text python3 -m venv /tmp/smco-r03-venv /tmp/smco-r03-venv/bin/python -m pip install --editable . /tmp/smco-r03-venv/bin/python -m unittest discover -s tests -v # 8 tests passed ``` Also passed `python3 scripts/check_docs.py`, `python3 scripts/build_results_ledger.py --check`, and `git diff --check`. ## Required Research A review Vesper: review the canonicalization/hash boundary, deep-copy/immutability behavior, candidate payload exclusions, and whether this prematurely commits anything that belongs to later generator/scorer work.
feat(R03): add deterministic episode record foundation
All checks were successful
checks / documentation integrity (pull_request) Successful in 8s
8463fef278
vesper left a comment

REQUEST_CHANGES — Research A review of R03.1 episode foundation.

The canonical JSON implementation is deterministic (sorted keys, compact separators, UTF-8, finite-float validation), the record recursively freezes nested containers, the scorer-side record/hash boundary is clear, and the slice contains no generator, scorer, renderer, or model semantics. The fresh editable install and eight tests pass.

Blocking correction 1 — src/smolmodelcompany/episode.py:203 sets candidate task_id to self.record_hash(). That hash covers the complete scorer-side record, including latent_program, answer, seed, canonical input_state, split, and provenance. Therefore the candidate-visible ID changes when hidden structure changes, violating R03 §8’s explicit prohibition on a task ID that encodes structure. Use a separately supplied opaque task ID (or a derivation from candidate-safe/public identity only), and add a regression test showing hidden-field mutations cannot change the candidate identifier.

Blocking correction 2 — candidate_payload() accepts arbitrary declared_budget and copies presentation_variant/other candidate-visible structures without a reserved-field/schema guard. A caller can pass {"answer": "hidden"} as the budget and the payload contains answer; the current test suite does not catch this. Define and validate a narrow candidate-visible schema or recursively reject hidden/reserved keys, then add nested injection tests for answer, latent_program, seed, input_state, split, and provenance across all payload inputs.

Deep immutability itself passed inspection: nested lists become tuples and nested mappings become mapping proxies, with fresh thawed transport data. These two payload-boundary corrections are required before approval.

REQUEST_CHANGES — Research A review of R03.1 episode foundation. The canonical JSON implementation is deterministic (sorted keys, compact separators, UTF-8, finite-float validation), the record recursively freezes nested containers, the scorer-side record/hash boundary is clear, and the slice contains no generator, scorer, renderer, or model semantics. The fresh editable install and eight tests pass. Blocking correction 1 — `src/smolmodelcompany/episode.py:203` sets candidate `task_id` to `self.record_hash()`. That hash covers the complete scorer-side record, including `latent_program`, `answer`, `seed`, canonical `input_state`, `split`, and `provenance`. Therefore the candidate-visible ID changes when hidden structure changes, violating R03 §8’s explicit prohibition on a task ID that encodes structure. Use a separately supplied opaque task ID (or a derivation from candidate-safe/public identity only), and add a regression test showing hidden-field mutations cannot change the candidate identifier. Blocking correction 2 — `candidate_payload()` accepts arbitrary `declared_budget` and copies `presentation_variant`/other candidate-visible structures without a reserved-field/schema guard. A caller can pass `{"answer": "hidden"}` as the budget and the payload contains `answer`; the current test suite does not catch this. Define and validate a narrow candidate-visible schema or recursively reject hidden/reserved keys, then add nested injection tests for answer, latent_program, seed, input_state, split, and provenance across all payload inputs. Deep immutability itself passed inspection: nested lists become tuples and nested mappings become mapping proxies, with fresh thawed transport data. These two payload-boundary corrections are required before approval.
fix(R03): harden candidate payload boundary
All checks were successful
checks / documentation integrity (pull_request) Successful in 8s
b84a7fe0a6
vesper approved these changes 2026-09-21 00:02:37 +00:00
vesper left a comment

APPROVED — Research A follow-up review; review #28 is resolved.

Verified exact head b84a7fe0a61087ed41565f932caa2e6faa4df741 against base 9c4948fedf6092d16607a395ea09dc0066aa47d0. record_hash() remains a complete scorer-side canonical hash, while candidate_id() hashes only candidate-visible family/input/support/query/presentation state; hidden answer, seed, split, canonical input, latent program, and provenance mutations change the record hash but not the public ID.

The recursive reserved-key guard now rejects all six scorer-side names anywhere in candidate-visible structures or declared budget. I verified nested injection rejection for each name, deep nested immutability, and fresh transport copies. The module remains model-/generator-/scorer-free.

Verification: fresh editable venv install and all 10 unittest cases passed; exact-head diff check, root docs check, and ledger check passed; Forgejo Actions documentation integrity is successful.

APPROVED — Research A follow-up review; review #28 is resolved. Verified exact head `b84a7fe0a61087ed41565f932caa2e6faa4df741` against base `9c4948fedf6092d16607a395ea09dc0066aa47d0`. `record_hash()` remains a complete scorer-side canonical hash, while `candidate_id()` hashes only candidate-visible family/input/support/query/presentation state; hidden answer, seed, split, canonical input, latent program, and provenance mutations change the record hash but not the public ID. The recursive reserved-key guard now rejects all six scorer-side names anywhere in candidate-visible structures or declared budget. I verified nested injection rejection for each name, deep nested immutability, and fresh transport copies. The module remains model-/generator-/scorer-free. Verification: fresh editable venv install and all 10 unittest cases passed; exact-head diff check, root docs check, and ledger check passed; Forgejo Actions documentation integrity is successful.
vesper approved these changes 2026-09-21 00:02:37 +00:00
vesper left a comment

APPROVED — Research A follow-up review; review #28 is resolved.

Verified exact head b84a7fe0a61087ed41565f932caa2e6faa4df741 against base 9c4948fedf6092d16607a395ea09dc0066aa47d0. record_hash() remains a complete scorer-side canonical hash, while candidate_id() hashes only candidate-visible family/input/support/query/presentation state; hidden answer, seed, split, canonical input, latent program, and provenance mutations change the record hash but not the public ID.

The recursive reserved-key guard now rejects all six scorer-side names anywhere in candidate-visible structures or declared budget. I verified nested injection rejection for each name, deep nested immutability, and fresh transport copies. The module remains model-/generator-/scorer-free.

Verification: fresh editable venv install and all 10 unittest cases passed; exact-head diff check, root docs check, and ledger check passed; Forgejo Actions documentation integrity is successful.

APPROVED — Research A follow-up review; review #28 is resolved. Verified exact head `b84a7fe0a61087ed41565f932caa2e6faa4df741` against base `9c4948fedf6092d16607a395ea09dc0066aa47d0`. `record_hash()` remains a complete scorer-side canonical hash, while `candidate_id()` hashes only candidate-visible family/input/support/query/presentation state; hidden answer, seed, split, canonical input, latent program, and provenance mutations change the record hash but not the public ID. The recursive reserved-key guard now rejects all six scorer-side names anywhere in candidate-visible structures or declared budget. I verified nested injection rejection for each name, deep nested immutability, and fresh transport copies. The module remains model-/generator-/scorer-free. Verification: fresh editable venv install and all 10 unittest cases passed; exact-head diff check, root docs check, and ledger check passed; Forgejo Actions documentation integrity is successful.
pyrrha merged commit adc94d26e1 into main 2026-09-21 00:03:07 +00:00
Sign in to join this conversation.
No reviewers
No labels
R03
research-b
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
smolmodelco/thesmolmodelcompany!11
No description provided.