Repository navigation
P6b: the promotion commit (transaction, new state, idempotency, concurrency) - #25
Merged
Merged
Conversation
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ts type (Doc 09 §18, §30; C-162) The blocking condition of the P6a verdict: a STRUCTURAL-typed PASS with an INVARIANT target id hid an INVARIANT FAIL, because decide keyed the report's results by target_id alone (PROMOTE). (1) VerificationReport refuses two results with the same target_id under any two types. (2) decide looks each required item up by the type its kind requires (INVARIANT, RESOURCE, or STRUCTURAL for a gate) and examines every result that carries a required target id. (3) A second result, or one of the wrong type, is an INCONSISTENT_REPORTS REJECT condition, the item is not proven by any of them, and a FAIL among them stays blocking. The 15 tests were written first: 13 failed on the unfixed code (the three reproductions gave PROMOTE). Five scratch-copy mutants (each part of the fix removed) are killed. No existing test built such a report on purpose: the full suite is unchanged (3739 tests OK, 15 new). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…st for promote and mark_promoted (Doc 09 §26; C-38, C-44, C-162 to C-168) C-162 to C-167 (Proposed) as written in the work package; C-38 Decided (three cases of a failed commit); one sentence added to C-44 for schema 6. C-168 (Open) records a conflict found while reading the code: the schema-6 trigger "a promoted state needs its PROMOTE decision row" would refuse about 25 direct save_promoted calls in existing persistence tests, while the work package says no existing test may change except the schema literal. The user is asked before step 4. The contract test gains a repository-wide check that core.promotion.commit (and core.domain.state) are the only modules that call TrustedState.promote or mark_promoted; R8 itself is untouched. The check was written first and failed (NameError) until the checker existed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ing, new-state arguments (Doc 09 §26-§27; DATA-INT-004; C-41, C-45, C-163, C-164, C-168) PromotionDecision.committed(state_id) and PromotionEvidence.with_new_state(state_id): once, for a PROMOTE only, hash recomputed, behind a private token so that nothing else can put a state id in (DATA-INT-004 in the domain). core/promotion/ids.py: the C-163 derived ids under one fixed namespace. core/promotion/proofs.py: evaluate_from_results and build_proofs turn the P6a proof plan into InvariantProofs (VERIFIED through the evaluation lifecycle, CARRIED_FORWARD through carry_forward, which still asks the impact report: C-45); every proof carries the binding record id (C-164). core/promotion/newstate.py: the checked arguments of TrustedState.promote (C-41); the call itself will be in commit.py only. User decisions recorded (29 Oct 2026): C-168 option (b), no reverse trigger; C-164 option (1), an additive keyword extra_evidence_ids on InvariantProof.carry_forward (core/domain/invariant.py). The PROMOTABLE -> ESCALATED edge of C-38 is implemented with the schema-6 commit (step 4): the table is pinned by tests of the database triggers, which change with the schema. Tests first: the 43 new tests failed with ImportError / TypeError before the code. Full suite 3784 tests OK (45 new). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…the PROMOTABLE -> ESCALATED edge (Doc 05 §15, §22, §32; C-38, C-163, C-165, C-168) promotion_decisions with its triggers (DATA-INT-003: the parent is the candidate's parent; DATA-INT-004: stored uncommitted, committed_state_id set once, from NULL, for a PROMOTE, to the state that names it as its commit decision; every other column immutable; no delete or replace) and two unique partial indexes (one committed decision per candidate, one state per commit decision). The 92 schema-5 statements are byte-identical (golden SHA-256 pinned, taken before the code changed); the candidate status trigger is dropped and created again in the appended statements with PROMOTABLE -> ESCALATED, which the domain lifecycle gains in the same commit (C-38). DecisionRepository port, SQLite adapter (save is idempotent, a conflicting duplicate raises, commit is the only change, reads recompute the hash), unit-of-work property, three new checkpoint names kept apart from the original four. The health check lists the new table. No reverse trigger (C-168, decision (b)). Existing tests changed (each only for the facts above): test_lifecycle (one legal pair), test_persistence_schema (version literals, the new table, triggers and statement count, one legal pair, the pair count 10 -> 11, the refused-version list), test_repository_ports (the new port and property), test_persistence_repositories (version literal; the promotion sequence now includes the candidate-update checkpoint). The register records C-164's STATE-kind binding record (user decision). New tests written first: 42, all failed before the code. Full suite 3826 tests OK. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nding (Doc 09 §26, §27, Appendix B; Doc 11 §43; C-38, C-163 to C-167) record_decision stores every decision and moves the candidate along its exit. commit_promotion revalidates the decision with decide (C-167), checks that the evidence binds to the new state (C-164, binding.py), builds the state, the STATE binding record and the committed decision, then writes them in one transaction in the order of Appendix B. A lost race stores a REJECT (STALE_PARENT), a revalidation failure escalates the candidate, and any other failure rolls back and raises PromotionCommitError (C-38). The tests (37) were written first and failed with ModuleNotFoundError before core.promotion.commit existed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…service (Doc 11 §44, §45; C-52, C-68, C-164) integrity_of now also compares the payload's hash with the record's, as EvidenceService.verify_integrity does; a test shows both give VALID, TAMPERED and MISSING_PAYLOAD for the same stored records. 30 tests for binding_problems cover each condition of the VERIFIED and CARRIED_FORWARD checks, the chain from a promoted parent and the unreadable record. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…9, Appendix B; C-163, C-166) Sequential and concurrent duplicates, conflicting duplicates, recording twice (test_promotion_idempotency, 12 tests), and 66 schedules of two promotions racing from one parent with real threads, two connections and the checkpoint seam (test_promotion_concurrency, 6 tests): every combination of first runner, interleaving (overlap, sequential, first fails after the head moved) and pause point of the seven, plus 24 seeded draws. A database checker (check_database) reads the file with plain SQL after each schedule. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… Doc 09 §26, §41; C-38) A failure injected at each of the seven checkpoints, at the STATE_PROMOTED audit append and at the binding-record append leaves the previous state current, none of the attempt's rows, the candidate PROMOTABLE and a retry that makes exactly one state, with the decision recorded before and not. Evidence that does not bind and reports whose hash no longer recomputes are revalidation failures that escalate the candidate and change nothing on a second attempt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…real pipeline (Doc 06 §25, §26; Doc 09 §33, §34; C-52, C-164) The safe repair promotes into v1 (INV-SEC-001 VERIFIED, INV-SEC-003 CARRIED_FORWARD, all PROTECTED, the binding record on every reference and usable as proof for v1); the regression is REJECTED with v0 still current and a second candidate from v0 then promotes; the first promotion's v1 is carried forward into v2 through its binding record; Stateful-Full carries nothing. tests/promotion_pipeline.py gains keyword parameters (candidate id, sequence, patch number, parent state, decision id) whose defaults keep the P6a behaviour; the 17 P6a fixture tests still pass unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s them (Doc 09 §26-§27, §38-§40; C-41, C-163, C-164) 40 seeded chains of decisions (PASS, FAIL, UNKNOWN and ERROR results; commits, duplicate commits, second records) keep every rule: a non-PROMOTE never changes the trusted states or the lineage head, a replay writes no row, references cover the parent's invariants with no lower version, every reference carries a binding record that is proof for its state, one child per state, one commit per decision. check_database is shown to fire for each rule on a database that breaks it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the order of the commit writes (Doc 09 Appendix B; Doc 11 §44; C-164) A record that the adapter reads (its columns agree with its content) but whose state_hash is not the state's or the candidate's is refused by the binding check, for a VERIFIED source and for a baseline's carried source. A spy on the repositories pins the order of the writes: decision, its event, binding record, state and head, decision committed, candidate PROMOTED, STATE_PROMOTED last. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t is not committed (Doc 09 Appendix B; C-167) A mutation check showed that comparing the proof plan at revalidation, and not only the decision's content hash, was not tested: the decision hash does not cover the evidence ids of a carried reference. The new test decides against a copy of v0 whose INV-SEC-002 reference names other evidence and shows the commit escalates the candidate. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…c 09 §38-§40; Doc 11 §12, §38; C-163, C-164, C-166, C-167) Records the API as built against the prompt (factory, context, no evaluations), the unreadable-evidence rule, the loser's records, and opens C-169: a repeated failure of the same kind for one decision adds no second PROMOTION_FAILED event. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… (Doc 09 §26; C-163, C-164) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ommitted, as the prompt orders Appendix B (Doc 09 §26, Appendix B; C-163) The order test was changed first and failed on the previous order (decision committed, then candidate PROMOTED); the two writes are swapped in commit_promotion and in its docstring. Both stay in the one transaction, so the swap is visible only in the order of the checkpoints after_candidate_update and after_decision_commit, which now follows the order of DECISION_CHECKPOINTS. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ion for one candidate, tested with the triggers gone (Doc 05 §22; C-163, C-165) A mutation run showed the dropped index was killed only by the test that lists the index names. The new test drops the triggers the way an attacker with file access would and shows the index alone refuses the second commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P6b REPORT
Written for: the external reviewer, and the PR description (the P6b prompt's report format).
No gate or mini-gate is claimed to have passed. Nothing is pushed; no PR is open.
1. Branch and commits
Branch
feature/p6bfroma7ca8af(main). 17 commits. Each was checked in its own clean clone on Python 3.12:black --check,ruff check,mypy, thenscripts/dev.py test.dev.py test(3.12)commit.py,binding.py,record_decisionintegrity_ofagrees with the evidence serviceFinal tree (f30eade): Python 3.12 Ran 3941, OK (skipped=1); Python 3.14 Ran 3941, OK (skipped=2; the extra skip is
test_terraform_instances.py:221, the interpreter's Unicode version, not a P6b test). Python 3.13: NOT RUN, not installed (py -0lists 3.12 and 3.14).2. Files
Added:
core/promotion/{ids,proofs,newstate,binding,commit}.py;tests/promotion_commit_builders.py,tests/promotion_pipeline_commit.py;tests/unit/test_promotion_{c162,committed,carry,newstate,exit,persistence,commit,binding,idempotency,concurrency,faults6,properties6,pipeline6}.py(215 tests).Modified (code):
core/domain/{verification,promotion,lifecycle,invariant,repositories}.py,core/persistence/{schema,sqlite}.py,core/promotion/{decide,__init__}.py,core/application/health.py(one table name). Docs:docs/DECISIONS_REGISTER.md,docs/PHASE_GATES.md(step 0 only).Outside the prompt's expected-file list (each needed, none loosens anything):
core/application/health.py(EXPECTED_TABLES gainspromotion_decisions, or the health check fails on schema 6);core/promotion/__init__.py(docstring);tests/promotion_pipeline.py(new keyword parameterscandidate_id,parent_trusted,sequence,patch_n,decision_id, defaults keep P6a behaviour; the 17 P6a fixture tests pass unchanged);tests/promotion_builders.py(record()evidence carries the engine's keys, so C-68's subject can be checked).Existing tests whose literal, set or construction changed:
tests/unit/test_lifecycle.py: the pinned table gains(PROMOTABLE, ESCALATED);len(by_hand)10→11 (C-38).tests/unit/test_persistence_schema.py: version literals 5→6, "needs 6", the refused-versions loop ("6"→"5"), the table, trigger and statement-count sets,PROMOTABLE: {…, ESCALATED}.tests/unit/test_persistence_repositories.py: "schema version 6"; the checkpoint list gainsafter_candidate_update(saving the candidate's status is now a seam).tests/unit/test_repository_ports.py:DecisionRepositoryanddecisionsadded to the pinned port lists; a logic edit, not a literal:allowed_savenow also allowssaveonDecisionRepository(the port the prompt specifies hassave(decision)). I flag it for the reviewer.tests/contract/test_architecture_boundaries.py: additive only (R8_SOLE_CALLERS, two tests; nothing loosened).3. Acceptance criteria
test_promotion_c162.py(15): the three reproductions (TestTheReviewersReproductions), domain refusal, every matching result examined, property over seeded reports with decoys and order independence. Before/after in 5.test_promotion_committed.py(14:committed,with_new_state, DATA-INT-004 in the domain);test_promotion_exit.py(5: the new edge and every neighbouring forbidden edge).test_promotion_persistence.py(38): round trip, immutability, one-way commit, DATA-INT-003/004 refused by raw SQL, golden hashes (schema-5 prefix and full schema 6), schema-5 refusal, duplicate committed decision per candidate (unique index, with triggers gone). The prompt's "a trusted state with version > 0 and no decision is refused" is NOT DONE: C-168 decision (b), made by the user on 29 Oct 2026 (about 25 existing tests callsave_promoteddirectly). Enforced by construction incommit_promotion, by the order test, and by acheck_databaserule.test_promotion_commit.py(39),test_promotion_pipeline6.py(11): parent, version, resources, refs,commit_decision_id, binding record on every ref, one STATE_PROMOTED, order of the writes.TestRecordDecision;test_promotion_properties6.py(non-PROMOTE never changestrusted_statesorlineage_heads).test_promotion_idempotency.py(12),test_promotion_commit.py::TestIdempotency.test_promotion_concurrency.py: 66 schedules (section 7).test_promotion_faults6.py(9): section 8.TestRevalidation,test_promotion_binding.py(32),test_promotion_carry.py,test_promotion_newstate.py(C-45 incarry_forwardand at commit).test_promotion_pipeline6.py: section 9.core.domain.state,core.promotion.commit); R18 holds (core.promotionimports only the standard library andcore.domain); files outside the list named in section 2.4. Commands and real output
Test counts: 3724 at
a7ca8af→ 3941 (+217).NOT RUN: Python 3.13 (not installed);
pip-audit(no dependency or pin changed); Terraform checks of the README (no fixture or Terraform file touched, no Terraform CLI used). The per-commit table (section 1) was produced bypercommit_p6b*.shin the session scratchpad.Process facts about the commands: the first three-way parallel per-commit run covered commits 1–15; commits 16 and 17 were run on their own afterwards; a verification run started before commit 16 was killed and restarted on the final tree.
5. C-162 (target id has at most one result, whatever its type)
Before the fix (
tests/unit/test_promotion_c162.pyagainst the unfixed code; progress lineFF.F.FFFF.FFFFFF):TestTheReviewersReproductions.test_inv_sec_001,test_inv_func_001andtest_a_resource_baseline_itemeach failed with<DecisionType.PROMOTE> != <DecisionType.REJECT>; the property failed atseed 1 world 20with PROMOTE; the domain tests failed withDomainValidationError not raised. After the fix all 15 pass.Fix:
VerificationReportrejects two results with onetarget_id(core/domain/verification.py);decidelooks each required item up by(target_type, target_id)with the type its kind requires and examines every matching result (core/promotion/decide.py).Mutation proof (re-run on the final code): part (1) removed → 2 tests fail; part (2) removed →
test_a_result_of_the_wrong_type_alone_is_not_proof; part (3) removed → 8 tests fail (TestTheReviewersReproductionsand others). Unmutated: green.6. The commit, step by step (Doc 09 §26, Appendix B; C-163 to C-167)
commit_promotion(factory, outcome, candidate, impact, verification, policy, evidence_ids, context, *, now, …), in one unit of work (BEGIN IMMEDIATE, C-166):CommitResult(duplicate=True), other content →ConflictingDuplicateError(C-163); a stored REJECT of this loser → its stale result; another committed decision for the candidate →ConflictingDuplicateError.decideagain on the database's candidate and state; the decision hash, the proof plan and the evidence hash must equal the stored ones (C-167).binding_problems(C-164) over every plan entry.TrustedState.promote, committed decision,PromotionEvidence.with_new_state, binding record,mark_promoted.decisions.save(after_decision_insert) + PROMOTION_DECIDED if the decision was not stored; binding record append;save_promoted(after_trusted_state_insert,after_invariant_refs_insert,after_lineage_head_update; its compare-and-swap is the last line of defence);candidates.save_transitionPROMOTED (after_candidate_update);decisions.commit(after_decision_commit); STATE_PROMOTED with the derived id;before_commit, COMMIT.The order of the writes is pinned by
TestOrderOfWrites. It was changed first and failed against the earlier order (decision committed, then candidate PROMOTED); commit f666c76 swaps them to the prompt's order.Outcomes outside the commit: lost race → REJECT decision (STALE_PARENT, derived id), candidate REJECTED,
stale=True; revalidation failure → PROMOTABLE→ESCALATED, PROMOTION_FAILED and ESCALATED events, the PROMOTE kept uncommitted,escalated=True; any other failure → rollback, candidate stays PROMOTABLE, best-effort PROMOTION_FAILED,PromotionCommitError.7. Idempotency and concurrency
Idempotency (
test_promotion_idempotency.py, 12 tests): three repeats → the same state,duplicate=True, every table unchanged; a later clock and other context ids change nothing; conflicting duplicates (other policy version, other attempt number, other content under a stored uncommitted id, a stored REJECT under a PROMOTE's id) raise and change nothing; a second PROMOTE for a promoted candidate is refused; all four decisions recorded twice change nothing; two threads committing one decision at each of the seven checkpoints → one state, oneduplicate=True.Concurrency (
test_promotion_concurrency.py): 66 schedules, real threads, two connections to one file,Gateevents at the checkpoint seam, no sleep. 42 systematic (first runner A/B × interleaving {overlap, sequential, first-fails-after-the-head-moved} × all seven checkpoints) + 24 drawn withrandom.Random(20261029). Mix: 21 overlap, 23 sequential, 22 first-fails; winners A 33, B 33; every checkpoint at least 7 times. Every schedule: exactly one new state (version = parent + 1), head = the winner's derived state, winner PROMOTED, loser REJECTED with a stored REJECT/STALE_PARENT decision under the derived id, 8 references, 2 decisions, one binding record, one STATE_PROMOTED,check_databaseempty (no orphan row). A first-fails schedule leaves the failed candidate PROMOTABLE and the retry loses. A unit of work that believes the head has not moved still loses at the compare-and-swap (TestCompareAndSwap). SQLiteBEGIN IMMEDIATEserialises writers on one file: the MVP's single promotion worker (Doc 09 §39); nothing is claimed for several processes beyond what these tests show.8. Fault injection (
test_promotion_faults6.py)Injected
InjectedFaultat each point; read from a new connection after each.after_decision_insertafter_trusted_state_insertafter_invariant_refs_insertafter_lineage_head_updateafter_candidate_updateafter_decision_commitbefore_commitIn every row the only new data is the best-effort PROMOTION_FAILED event (one audit event and its payload artifact,
evidence_artifacts+1); the retry gives 2 trusted states. Run with the decision recorded before (all rows exceptafter_decision_insert, which is not reached) the decision row stays uncommitted; run without, it rolls back. When even the failure event cannot be written (every checkpoint raises) the caller still getsPromotionCommitErrorand nothing is recorded. Revalidation failures (candidate ESCALATED, nothing committed, a second attempt changes nothing): tampered evidence payload; wrongstate_hashon a source record; a source record produced for another candidate; a verification report and an impact report whose hash no longer recomputes.9. Canonical transitions through the database (real P6a pipeline,
test_promotion_pipeline6.py)canonical_safe_repair_promote_v1→ PROMOTE → v1: INV-FUNC-001, INV-SEC-001, INV-SEC-002 VERIFIED, INV-SEC-003 CARRIED_FORWARD from v0, all PROTECTED (what the engine returned; nothing edited).canonical_security_repair_breaks_function_reject_v1→ REJECT (blocking conditionINVARIANT:INV-FUNC-001:1), candidate REJECTED, v0 still current, a REJECT cannot be committed; a second candidate from v0 (safe repair, own id) then promotes to v1 (Doc 06 §25–§26).ec2_rds_5432_pass_v1(its parent directory is the safe repair), pipeline run against the promoted v1: INV-SEC-003 CARRIED_FORWARD through v1's binding record, the other three VERIFIED.Evidence context per reference (
usable_as_proofin each context):verification.resultpromotion.committed(binding)No evidence is valid in a context it was not produced for (C-52); validity for vN+1 comes only from the new STATE record bound to it.
10. Mutation checks and properties
22 mutations on a scratch copy outside the repository (
mut_p6b_step6.py); unmutated 33 modules green. Final run, all KILLED, none survived:test_a_commit_whose_parent_is_no_longer_current_losestest_a_commit_that_believes_the_head_has_not_moved_still_losesBEGIN)test_at_least_fifty_schedules_leave_exactly_one_winnertest_a_recorded_decision_is_committed_in_placetest_a_source_evidence_record_of_another_invarianttest_a_carried_reference_whose_parent_evidence_is_not_bound_to_the_parentstate_hashacceptedtest_a_readable_record_with_another_state_hash_is_not_prooftest_the_writes_are_made_in_the_order_of_the_committest_the_same_decision_again_returns_the_same_state_and_writes_nothingtest_the_ids_are_distinct_and_stabletest_a_verification_report_whose_hash_no_longer_recomputestest_a_carried_reference_whose_evidence_is_not_the_one_decided_ontest_a_report_that_does_not_mention_the_invariant_is_still_refusedtest_data_int_003_…test_only_a_promote_can_be_committed+3test_at_most_one_committed_decision_per_candidatetest_a_fault_leaves_the_previous_state_and_the_candidate_promotabletest_a_commit_whose_parent_is_no_longer_current_losestest_the_schema_5_statements_are_byte_identicalTwo weaknesses were found by the first run and closed with tests (commits e6b7632, f30eade): comparing the proof plan at revalidation was not tested (the decision hash does not cover a carried reference's evidence ids); the dropped unique index was killed only by the test that lists index names. The mutation text of the interpreted items: "wrong state_hash on the binding record accepted" =
evaluate_proofgiven the record's own hash; "duplicate returns a new state" = the duplicate branch falls through; "failed commit leaves the candidate PROMOTED" = a failed unit of work commits instead of rolling back.Properties (
test_promotion_properties6.py, 40 seeded chains; PASS/FAIL/UNKNOWN/ERROR draws; 1–3 states): non-PROMOTE never changestrusted_statesorlineage_heads; references cover the parent's invariants with no lower version; every reference carries the binding record and it is proof for its state; at most one child per state;commit_decision_idunique; a replayed commit or record changes no row of any table (full dumps compared). Coverage asserted: PROMOTE, REJECT, RETRY, ESCALATE, a duplicate and a length-3 chain all occur.check_databasefires for each of 14 broken rules (TestTheCheckerFires).11. P6b evidence map (Doc 15 §13, the part this package reaches)
TestCommitPromotion,TestSafeRepair,test_promotion_idempotency.py, concurrencyTestStaleParent, concurrency (66 schedules),TestCompareAndSwaptest_promotion_idempotency.pytest_promotion_concurrency.pytest_promotion_faults6.py,TestInfrastructureFailuretest_promotion_commit.py,test_promotion_pipeline6.py)P6c, not here: the orchestrator, the baseline flow, the full fixed-patch loop, M3 and the P6 gate. No gate is claimed.
12. Register, spec gaps
Rows: C-162, C-163, C-164, C-165, C-166, C-167 Proposed (P6b); C-164 amended after the user's decisions (extra evidence ids; STATE-kind record); C-168 Decided (user, option (b)); C-169 Open (a second failure of one kind for one decision adds no second PROMOTION_FAILED event; for the authors); C-38 Decided (prompt); C-44 one sentence. No Accepted row touched.
Contradictions and gaps (each stopped and put to the user, or recorded):
newstate.pycallsTrustedState.promoteand also that onlycommitmay: resolved for the second;newstate.promotion_argumentsreturns the arguments.commit_promotiontakes a factory and aPromotionContextand builds the evaluations itself;record_decisiontakes the context and the clock;integrity_of(evidence_repository, event);build_proofstakes the binding record id.scripts/dev.py:132still prints "schema 5" during bootstrap; the health readiness line says "schema version 1" (configuration). Both are outside the file list and unchanged: suggestions.13. What P6c needs from this package
record_decision(uow, outcome, candidate, context, *, now)thencommit_promotion(factory, outcome, candidate, impact, verification, policy, evidence_ids, context, *, now, attempt_number=None, oracle_verdicts=())returningCommitResult(decision, state, duplicate, stale, escalated);PromotionCommitErrorfor infrastructure failure (retry is the caller's, outside the core, Doc 09 §23). The orchestrator must (a) store each verification result withto_evidence_submission(…, parent_state_id=…)and hand oververification_id → evidence_id; (b) keep the impact and verification reports and the policy in memory, or persist them, until the commit (reports are not persisted: R2 of the prompt, a recorded limit for P8); (c) pass the sameattempt_number; (d) build thePromotionContext. Ids come fromcore/promotion/ids.py.tests/promotion_pipeline_commit.pyis the helper that does (a) and (d) for the tests only.14. Open questions and process lapses
For the reviewer: (1) the STATE-kind binding record
promotion.committedas the proof carrier (the prompt's risk R1); (2) C-168 (b): "no promoted state without its decision" is enforced by construction and bycheck_database, not by a trigger; (3) C-169; (4) the factory API (C-167); (5) a revalidation failure also appends ESCALATED and keeps the PROMOTE uncommitted; (6) a record the adapter cannot rebuild counts as a binding problem and so escalates, rather than as infrastructure failure; (7) the loser's own PROMOTE decision is not stored by the commit (only the REJECT with the derived id).Process lapses, plainly: (a) the binding, idempotency, concurrency, fault, pipeline and property tests were written after the code they test (the step-1 tests, the step-4 tests, the step-5 commit tests and the order test were written first and shown failing, with the red output kept in the scratchpad; for step 3 I kept no red run); for these the evidence that they can fail is the mutation table above, not a red run; (b) a shell lint gate that piped
ruffintotailhid its exit status, so commitfab89a8first contained two E501 docstring lines; I amended that unpushed commit (message unchanged) and gated the rest on the real exit codes; (c) a first mutation run left one survivor and one weak kill, closed by two later commits; (d) one background verification started on the pre-swap tree was killed and restarted.🤖 Generated with Claude Code