Skip to content

Milestone A: unified assembly and verified execution boundaries - #26

Merged
ShaneLogic merged 332 commits into
mainfrom
devel
Nov 1, 2026
Merged

ShaneLogic merged 332 commits into
mainfrom
devel

Conversation

@ShaneLogic

Copy link
Copy Markdown
Owner

devel → main at 3c88c4c. This is ready for the user's review. Milestone A's engineering criteria M1–M4 are met within their stated scope, and A6 closes under the agreed CI composition with conditions C1–C4. No scientific qualification is claimed; G9P remains open. Only the user merges.

The branch introduces solarlab, solarlab_server and solarlab_research alongside the retained legacy application. A shared layout/term/assembly layer now serves these routes by default: the admitted 1D steady, backward-Euler and AC routes, connected 2D steady/J-V, and the public electronic and single-positive-ion BDF equation adapters. Typed configuration, source identity, registry contracts, versioned artifacts and job-owned provenance support these execution boundaries.

Gap 2/2b removes the solver-to-experiment dependency, and PM-R1 guards the promoted public methods. The branch also adds:

  • the one-command root runner;
  • CI ownership and receipts;
  • registered legacy consumers;
  • BDF publication and readback;
  • runtime recovery.

The startup-identity correction (2af0467) keeps an unavailable optional ps observation from replacing a worker's real outcome.

The scheduled-suite watchdog (20a5dc4) runs the one stalled legacy coverage file node by node under a 1200 s cap. A stall now ends as a labelled timeout with complete receipts instead of missing outcomes.

The last two commits (6da1a07, 3c88c4c) change only the root CLAUDE.md. The density recovery seed remains default-disabled and unwired.

How verified

  • One-command root suite. python scripts/test_all.py ran on a fresh clean clone with editable frozen all extras, npm ci, CPython 3.13.15 and BLAS threads at 1. MeasuredRun3 at 6da1a07 passed: 7,509 passed, 0 failed, 0 errors, 198 skipped, 7 xfailed (static 222, core 4,776, packages 1,909, backend 551, frontend 51). 6da1a07 → 3c88c4c changes only CLAUDE.md, which no test or CI code reads. Earlier runs stay on record: MeasuredRun1 at 4c8617b failed (7,460 passed, 1 failed, 0 errors, 198 skipped, 7 xfailed), and MeasuredRun2 at 2af0467 passed (7,484 passed, 0 failed, 0 errors, 198 skipped, 7 xfailed).

  • Push CI (Refactor checks). All nine ordinary jobs passed at 3c88c4c (41291759093), 6da1a07 (41289807919) and 20a5dc4 (41286004910). Each static job ran 222 tests/ci tests. The conditional slow, coverage and ionic-jv-diagnostics jobs were skipped.

  • Scheduled coverage (A6). Criteria (a)–(d) pass under the agreed composition, per the independent audit CiCoverageV6Addendum1:

    • root shards from 41272529544 at 2af0467;
    • legacy shards from 41239577644 at 4c8617b;
    • the designated shard from the per-node watchdog replay 41286075732 at 20a5dc4 (3/3 passed, complete receipts).

    The legacy tree and both locks are byte-identical across those commits. No node among the 14,701 planned nodes in 270 shards lacks an outcome.

  • Reviews. CoverageWatchdogReviewV1 accepted the watchdog with one blocking test-margin finding, and Addenda 1–2 accepted R1 and the test-only R2. The landed bytes equal the reviewed R2 patch. Earlier landings keep their recorded review chains and evidence levels.

Disclosures

  • Flux-balance stall. The legacy file tests/integration/test_interface_flux_balance.py hit the 110-minute stall 2/2 under the old single-process harness at 4c8617b.

    • Under the per-node watchdog it did not recur in the targeted replay 41286075732 (3/3 passed, 15–18 s each).
    • It did recur in the push-triggered full scheduled run 41286004950 at 20a5dc4. All three nodes ended as labelled 1200 s timeouts with complete receipts.
    • The per-node record is therefore 1 of 2 runs stalled.
    • Two of the three stack dumps place it inside the legacy Radau integration of the module's ladder fixture. Its cause is not established. The watchdog bounds the stall and does not fix it.
    • The old failed runs stay failed history.
  • Run 41286004950 is supplementary and was still in progress. Its 11 root jobs passed. Its failed jobs so far are legacy shards: they are outside the green claim and await the auditor's Addendum2 classification.

  • C2. The 25 new tests/ci nodes have count-level push evidence. Per-node receipts exist only as supplementary samples: the MeasuredRun3 JUnit, and the 41286004950 coverage-root-static receipt (222/222 passed).

  • C4. These are disclosed, not repaired:

    • 99 composed legacy nonpass nodes: 45 Main-attributed, 47 of unresolved origin, 3 frozen captures, 2 watchdog timeouts and 2 dependency errors;
    • 37 truncated child coverage files in the root shards.

    No tolerance, cap, pin, skip or xfail changed. CiCoverageV5's separate historical 99 is a different set and is not added.

  • Evidence-variable regression. The 91a9323 regression was caught by cancelled run 41227286086, before any acceptance claim, and fixed in 4c8617b.

  • Ionic J-V allowance. The whole-job allowance remains 128 s, provisional under A-C4.

  • Legacy suite. The legacy perovskite-sim suite is not part of the green claim.

Not in this PR

  • The public time-method default remains backward Euler. The November 8, 2026 BDF default flip is separate and unapplied. It still needs its revised review and the FreezeV2 + Addendum1 conditions.
  • Duplicate original-class retirement remains later work.
  • U4 (BDF E/I reference evidence) and K3 (hysteresis originals and storage) await the user's decisions.
  • G9P scientific qualification remains open.

Run the supported root tests

uv sync --frozen --all-extras
npm ci --prefix web
uv run --frozen --no-sync python scripts/test_all.py

The runner derives separate root pytest job and isolation selections and pins BLAS threads to one. The legacy suites, the standalone web tests and build, and the non-pytest CI checks keep their own evidence scope.

The acceptance pack lives in the project archive, outside the repository: test/ArchitectureRefactor/MilestoneA/AcceptanceV5.md and AcceptanceV5.json, with every receipt and a verifying hash manifest. Only the user merges.

🤖 Generated with Claude Code

ShaneLogic and others added 25 commits October 30, 2026 12:19
…n slow runners

On devel fb18f7d, run 40986148907 failed once on backend 3.13 in
test_shared_bounded_queue_duplicates_and_both_cancellation_paths. The
signals were ['soft_stop', 'terminate'] where ['soft_stop'] was expected.
The cooperatively cancelled child had not exited within the test helper's
short default soft-stop grace on a loaded runner, so the supervisor
correctly escalated. A rerun passed.

The test now waits for the cooperative child's durable ready boundary
instead of assuming it is ready. It gives this test's supervisor a
startup-safe grace and wall budget; the uncooperative-worker test still
exercises the short escalation timers. The exact ['soft_stop']-only
assertion is kept, and the test also proves that the cooperative child
itself acknowledged the stop:
- same pid as the ready record;
- exit code 0;
- the child's durable terminal record carries the result token, state
  cancelled and reason cooperative_stop;
- the store reaches cancelled.

Authored by Engineering (ExecutionCancellation.patch 134d66cb): 50/50
repetitions under four CPU busy loops and 459 backend selections. Root
validation at 29b329c: backend runtime 377 passed and 10/10 repeats.

Constraint: Test-only. Production supervisor timeouts are unchanged; the signal assertion is not loosened
Rejected: Accept ['soft_stop', 'terminate'] | would stop proving that cooperative workers honour soft stop
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard against new ones

Milestone A M4. Main's legacy-boundary audit (archive MilestoneA
AcceptanceV2 a9f1f6c3, UnregisteredLegacyConsumersV1 31c7bbbf) found 129
registered rows and 16 direct legacy consumers with no registration. They
are now registered in RefactorTransitionBoundariesV1.json in three groups:
- legacy-notebooks: 4 retained .ipynb workflows. Owner WS5; exit P06-06,
  P09-02, P09-03.
- retained-research-benchmark-tools: 10 scripts/benchmarks tools. Owner RS;
  research-retained. Exit P09-03, only after an explicit RS consumer-exit
  review.
- legacy-browser-wire: 2 browser clients.
Existing rows and the historical planning record are unchanged. No
downstream hash pin of this file needed replacing.

A solver-free guard (tests/refactor/test_legacy_consumer_registration.py,
about 1.5 s) re-runs the audit scan over non-test Python files, notebooks
and browser transport files. It fails when a direct legacy consumer outside
the W03 facade and the legacy package is unregistered, and when a
registered path disappears without being marked historical. LREG-R1 from
the first review is closed. The guard now covers every refinement lane and
executor that targets legacy, whatever its module prefix, so retargeting a
lane to backend.main:run_jv, or adding a new backend lane, fails.

The old perovskite-sim application remains the disclosed, registered
legacy product surface. It stays read-only until P06-06 (PlanV2 section
5.5, D19). The new packages reach legacy only through W03.

Reviews: Performance LegacyRegistrationReviewV1 4fb998ac rejected the
first version on LREG-R1; LegacyRegistrationReviewV2 accepted
RegistrationV3 (defa25be).

Constraint: M-class; registrations only, with existing rows and history untouched
Confidence: high
Scope-risk: narrow
Directive: Any new direct legacy consumer must be registered with an owner and exit gate, or routed through W03; the guard enforces this

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cap each slow node

Run 2 of the scheduled suite (40986149015) exposed three defects in the
planner and runner.

1. Node identity drift. tests/unit/discretization/test_stable_bernoulli_flux.py
   parametrizes over np.geomspace values. Their decimal reprs in pytest node
   IDs differed between the planning runner and the execution runner, so the
   strict selection-drift check refused the shard (exit 4). Planning now
   verifies by test-function identity with an exact per-function node count.
   Each worker then selects its locally collected parameter cases. The drift
   check stays strict for everything else, and the test is unchanged.
2. Two shards hit the 110-minute job timeout. They are split using measured
   durations from both runs: coverage is split per integration file, and
   slow shards by function.
3. A stalled solve could consume a whole shard. Scheduled slow runs now go
   through a subprocess-per-node watchdog. A node over its measured wall
   cap is recorded as an explicit "timeout" failure, never a skip or xfail.
   The runner continues with the next node and writes incremental named
   receipts, so failures and the active test survive an outer job timeout.
   pytest-timeout is not in the lock, so no dependency was added.
   Motivating case: the legacy CIGS notch slow cases took 273 s in run 1,
   timed out in run 2, and took 210-220 s locally. Main's investigation
   (results/ScheduledSuite/CigsNotchStabilityV1) finds uncapped zero-bias
   Robin preconditioning of up to 250,136 RHS calls. It is probable legacy
   numerical stagnation, not yet attributed on Linux.

Two-run classification (CiCoverageV3.json acd8a899):
- the 51 earlier unknowns are now 6 mitigated environment cases, 33 passing
  but environment-sensitive cases and 12 repeated failures;
- 0 confirmed refactor regressions.
Per-push ci.yml, every test, threshold and marker are unchanged.

Constraint: Scheduled-only; a timeout is a failure outcome, and no test or threshold changed
Confidence: medium
Scope-risk: narrow
Not-tested: native Linux timing of the new shard plan (next scheduled run)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er by default

Milestone A M1. These production entry points now build their systems
through the unified layout/term/assembly layer by default:
- steady DC, operating-point admission, DC backward-Euler transients and
  DC AC;
- ionic steady, ionic initialization, ionic J-V, ionic transients (Core's
  assembled steppers) and ionic AC.

The selection lives in solarlab.equations.implementation. The previous
equation classes stay available through the explicit opt-out
equation_implementation(LEGACY) or NEWCORE_EQUATIONS=legacy for one
release. Invalid values fail closed and nothing falls back silently. 2D
steady is unswitched in this commit and binds legacy explicitly; its
switch is a separate, separately reviewed patch.

Conditions closed by this landing:
- PW-C1, provenance. Every production admission site (13 in scope) binds
  the resolved implementation, the actual Assembler identity and an
  analysis digest before any equation is evaluated, including nested
  initialization. Legacy carries an explicit legacy binding.
  Assembled-produced and legacy-produced artifacts are distinguishable.
- PW-C2, worker selection. The selection is captured and forwarded with
  the job/environment binding before engine imports. Parent and worker
  agree under both selections, including spawned and queued execution.
- PW-C3, reference paths. Core's parity tests run their reference side
  under LEGACY. A one-ulp assembled mutation fails 9/9.
- PW-C4, rebase. The rebase is pinned and uses Q6's admitted-source
  mechanism.
- PW-C5, evidence. The example audits derive the exact expected bindings
  independently and check them before any masking. A permanent negative
  control rejects resealed wrong identities.

Reviews:
- Science ProductionWiringReviewV1 30bc958d (accepted_with_conditions);
- Performance ProductionWiringDeltaReviewV1 133d8f79 plus Addendum
  bc311676 (PW-C1..C5 closed).
Patch ProductionWiringV2 c0c48147, 33 files. All per-file pins verified.

Evidence:
- Architecture: every ci.yml job green under default and
  NEWCORE_EQUATIONS=legacy; test_all 7008/7008; examples 50/50 (1D) and
  139/139 (2D).
- Root, at 8a0bf11: core 4699 (legacy 4699), packages 1772 (legacy
  1772), static 90, scan 26, frontend 47+4; mypy, ruff and imports clean.
  Backend runtime 382 passed. One failure is the pre-existing ps sampler
  defect on the base (RuntimeControl fix pending), which this landing does
  not touch.

Constraint: M-class, bitwise; no gate, tolerance or physics change
Rejected: Delete the duplicated legacy Problem/Step/SmallSignal classes now | the deletion plan waits one release, with frozen-baseline digests as the post-deletion proof
Confidence: high
Scope-risk: broad
Directive: New production entry points must bind provenance through solarlab.equations.implementation before evaluation; BDF stays original-only until migrated with its own parity proof

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…without solvers

CND3 for the 2026-11-08 BDF development-mainline flip.

Publication: experiments/bdf_results.py records
- the requested and effective method;
- the requested and effective BDF options, including clipped caps and the
  remaining wall budget;
- physical controls, drive knots and both sides of every discontinuity;
- the event/restart policy;
- accepted, rejected, auxiliary and sample work;
- the requested sample clocks;
- the last accepted time and the termination;
- the equation-implementation binding;
- source, runtime, device and initial hashes;
- qualification=false, labelled experimental and unqualified.
The requested end, the last accepted time, optional samples and a failed
candidate state stay distinct, so a failed run can never present a reached
requested end.

Readback: the solver-free reader uses only the saved historical options and
arrays. It never reconstructs a missing accepted end, and it refuses
tampered and mixed-method records. The restart, sample and work-history
invariants (CND3-R1) are enforced at capture, at publication and at
readback.

BDF capability: BDF is original-equation-only until it is migrated onto the
assembler with its own parity proof. Two original-only BDF operator labels
are registered in the wiring's provenance helper and bind
assembled=false. Initial capture and admission inside BDF run under the
explicit legacy scope, and BE still follows the selector. Forcing an
assembled BDF run is refused rather than silently run as original.

Reviews (Performance):
- BdfPublicationReviewV1 43ab4a74 rejected CND3-R1;
- V2 9895b90d accepted it, conditional on the post-wiring hook rebase;
- V3 accepted the hook rebase and the capability set (patch 9e0fdcbf).

Engineering evidence: test_all passes 7124 in both modes, and all 40
Readiness histories read back (16 no-history cases are refused).

Root validation at 0585f53 with this commit, Activation V2 and
RuntimeControl V3 stacked, CI-equivalent:
- core 4752 (legacy 4752); packages 1872 (legacy 1872);
- static 90, backend 383 + control 56, scan 26, frontend 47 + 4;
- mypy, ruff and import contracts clean.

Constraint: BE publication bytes unchanged; no physics, budget or default change; qualification=false
Rejected: Let BDF follow the equation selector now | BDF readiness and the frozen allowlist were proven only on original equations
Confidence: high
Scope-risk: moderate
Directive: Do not flip any BDF default until CND2 and a new freeze are done (CommanderBdfDefaultFreezeV1); readers must stay solver-free

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dark-to-light steps

E_dark_to_light_step failed for every integrator (BE, BE-h/4 and BDF). Core
diagnosed a numerical initialization/globalization defect in the near-empty
minority-hole log-coordinate basin. There, whole-direction damping near
3.19e-19 trips the original stagnation guard. A full-transient density seed
converges the shared BE/BDF1 target in two Newton iterations at both h and
h/4, with physical and Newton controls unchanged.

This commit adds the seed (solvers/time/carrier_seed.py) and its bounded
trigger. The seed is DEFAULT-DISABLED and unwired: no production caller
uses it yet. The trigger was frozen before any evidence run (TriggerNote
16904671). It fires only after the existing seed's Newton attempt fails with
the recorded basin signature, at most once per affected step. Seed and retry
work are debited separately and stay visible in the accounting. ACT-C1 is
fixed: the deadline type BE derives from RuntimeError is now labelled
correctly in the ledger.

Evidence (ReadinessV5, via test-only injection):
- 73 protected histories are numerically bitwise, with zero activations;
- BE completes at 4/0/1 and BE-h/4 at 16/0/1, with one recovery each;
- adaptive BDF still fails at 2/13/16 and stays ineligible.

Reviews: Science DarkToLightReviewV1 b639d916, ActivationReviewV1 b55cdf03
and its Addendum accepted it as default-disabled source (ACT-C1 closed).
Patch b3076ae2.

Constraint: Default-disabled; no production caller, gate, profile, budget or allowlist change
Confidence: high
Scope-risk: narrow
Directive: BE dark-to-light becomes supported only after reviewed wiring through HookPoints.md, publication/parity validation and a new scope freeze; BDF dark-to-light stays ineligible

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lure

The defect, reproduced on fb18f7d: `ps -axo` in worker._process_info took
longer than its 0.5 s cap and raised TimeoutExpired. The supervisor then
killed and reaped the child with supervisor_control_failure, but left the
store attempt `running`. HTTP/SSE consumers waited forever for a terminal
event.

What changes:
- Every supervisor control-failure path, including the claim and audit
  paths, now drives the run to exactly one terminal store state with one
  terminal event.
- RSS sampling retries a timed-out sample a bounded number of times and then
  fails closed. The budget is never left unenforced.
- When storage cannot commit, a fenced recovery intent is kept.
- Recovery isolation (RTC-B1): if one intent's audit or publication fails,
  for example because of a stale receipt.json.part, the other intents are
  still reconciled. Publication is idempotent and safe against stale .part
  files.
- Fallback intent discovery (RTC-B2): on reopen, every attempt that has a
  nonterminal failure receipt or control evidence is discovered and
  terminalized, even if control-failure.json was never published.
  Resubmitting never returns a stale nonterminal receipt.
The 0.5 s sampler cap and the 768 MiB pool budget are unchanged.

Test isolation. Under load (load average 8-12), the new supervisor tests
failed intermittently. They were legitimate shared_rss_limit stops before
child readiness, because the pool budget counts the whole pytest process
family (CI lesson class 2). So tests/runtime/test_supervisor_control_failure.py
now runs as its own isolated CI group (supervisor_control_failure), like
scan_execution and run_client. scripts/test_all.py and the scheduled suite
keep that isolation. The tests arm faults only after durable child
readiness, and they allow 30 s observation windows. No assertion or limit
was weakened. TestChanges.md lists every changed test and the reason.

Reviews: Science RuntimeControlReviewV1 f1ba9f11 rejected V1 (RTC-B1/B2).
Performance RuntimeControlReviewV2 4bff94ea accepted V2. V3 (patch 4c82a53e)
keeps the reviewed V2 production bytes exactly (sha256-verified) and changes
only tests and CI isolation.

Evidence:
- Engineering: 30/30 loaded repetitions (1680 cases) and the 521-case
  backend, under 6 busy loops.
- Root: an independent load check (4 busy loops, load average 15-20) ran the
  isolated file 10x at 56/56, and the backend at 383 passed. On the full
  stack, static 90, solvers/time 270 and control 56 all pass.

Constraint: Sampler cap and RSS budget unchanged; fail closed rather than run unmeasured
Rejected: Raise the ps timeout or the pool budget | would hide, not fix, the missing terminal transition and the test-family RSS coupling
Confidence: high
Scope-risk: moderate
Directive: Any new supervisor exception path must end in exactly one terminal state and event; RSS-budgeted runtime tests belong in an isolated CI group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re failure

On ed97ac3 (run 41044476030), backend 3.11.16 failed its first attempt. All
five tests that share the real ionic J-V HTTP module fixture errored in
setup: observe_http_case found result["artifacts"] == [] where a completed
run should have 15. The rerun passed, and so did 3.13 and 0585f53.
Engineering ruled out the shared 768 MiB pool hypothesis: the fixture runs on
a separate 3 GiB service pool with a 1536 MiB worker cap. Loaded macOS runs
on 3.13 and 3.11 all produced 15 artifacts. The runner tmp holding the raw
response is gone, so the cause remains unresolved.

This commit adds no fix, only the means to get native evidence:
- On failure, the unchanged 15-artifact assertion now records the raw
  result, the run and attempt store state, the terminal reason and the
  receipt, both in the message and in a saved evidence file. No behaviour
  or assertion change.
- A workflow_dispatch-only ci.yml job (run_ionic_jv_diagnostics, default 20
  repetitions) runs the module on Linux 3.11.16 and 3.13.15 and uploads the
  per-attempt evidence directories. Per-push jobs are unchanged.

Authored by Engineering (IonicJvDiagnostics.patch b9049f5a). Root checks:
tests/ci 97 passed; ruff clean; ci.yml parses.

Constraint: Diagnostic only; the 15-artifact assertion and all production budgets are unchanged
Rejected: Isolate the module now | no native evidence ties the failure to RSS coupling (the fixture has its own 3 GiB pool)
Confidence: medium
Scope-risk: narrow
Not-tested: the Linux diagnostic job itself (dispatched right after this push)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sembly layer by default

Completes milestone A M1 for 2D. solve_steady_state_2d, the 2D J-V entry
points and the 2D process/HTTP steady worker now build their systems through
Core's assembled DC2D binding by default. The explicit LEGACY opt-out works
as in the 1D landing (0585f53). Invalid selections fail closed, and nothing
falls back silently.

Conditions carried over from the 1D landing:
- PW-C1: provenance binds the actual implementation, the Assembler identity
  and the analysis before execution.
- PW-C2: workers receive the selection explicitly.
- PW-C5: the 2D example audit derives exact expected bindings in the
  producing clone and checks them before any masking. A negative control
  rejects resealed wrong identities (3/3).

PW2-C1 (from Science's 2D review) is fixed. Explicit J-V continuation now
only parses and hash-checks its prefix before the job, without evaluating
any equation. The unchanged numerical readmission of the original initial
state and every prefix row then runs inside the tail job, immediately after
the selected Q6 binding is admitted.

Evidence:
- The new two-mode ordering tests pass on V2 and fail on V1 at event 0.
- Science's probe sees 0 callbacks before admission (V1: 2).
- An archived bc3->e5->endpoint continuation replay keeps all 52
  diagnostics and 183 evaluations under the exact binding, with 57/57
  arrays byte-identical.
- 2D examples: 139/139 files, 1754 checks, 0 failures.

Reviews: Science ProductionWiring2DReviewV1 9388c7fb (accepted_with_conditions)
plus Addendum (PW2-C1 closed). Patch ProductionWiring2DV2 2a4be8bc, 10 files.
It is composed onto c0f06bd. The equations/implementation.py change set is
identical to the patch hunk, merged with CND3's BDF capability labels.

Root validation at 340553c, together with the public-methods patch and the
PM-R1 guard:
- core 4766 (legacy 4766), packages 1879 (legacy 1879);
- static 97, backend 386, scan 26, control 56, frontend 47+4;
- mypy, ruff and import-linter clean.

Constraint: M-class, bitwise; the 2D 20-state / 500-readout / ordered-port parity is unchanged
Confidence: high
Scope-risk: moderate
Directive: The duplicated original 2D classes stay behind LEGACY for one release; delete them only with the frozen-baseline digests as proof

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd guard their originals

Milestone A follow-up from AssemblyMigrationInventoryV1. Part B (ae7a21f)
promoted 14 private helpers to public names. Three cross-module private
METHOD dependencies were left outside that count. They are now public, and
every cross-module caller uses the public name:
- CompiledMultipleIonicDevice1D._values      -> values
- IonicSmallSignal1D._moving_voltage_partial -> moving_voltage_partial
- DC1DProblem._contact_exchange              -> contact_exchange
Two protected runtime callers keep thin private aliases that only delegate,
until their own migration. An AST guard in
tests/refactor/test_numerical_public_imports.py forbids new cross-module
calls of these private methods.

PM-R1 is closed. The parity tests' no-original-evaluation guards
(ORIGINAL_EVALUATIONS in test_assembly_ionic_drivers.py and
test_production_wiring.py) now also cover the public contact_exchange.
Before this change, the substituted context blocked the alias but still
allowed entry into the new public body. A witness and a negative control
prove that refusal now happens.

Authors:
- Main: PublicMethods.patch 7e468e2d.
- Architecture: PM-R1 guard, PublicMethodsGuardV1.diff 381d7458.
Reviews: Performance PublicMethodsReviewV1 d03d51cf (accepted_with_conditions)
plus Addendum (PM-R1 accepted).
Evidence: 40 factories / 160 states and the ionic/AC digests are bitwise
unchanged. Root validation is in the previous commit's stack: core 4766 in
both modes, packages 1879, static 97 and the full backend.

Constraint: M-class, bitwise; aliases are pure delegation
Confidence: high
Scope-risk: narrow
Directive: Remove the two private aliases when their protected runtime callers migrate; the AST guard keeps new private cross-module calls out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Close P03-01 gap 2. solarlab.solvers no longer imports solarlab.experiments:
experiment admission, event/history orchestration and the four BDF entry
functions move to solarlab.experiments.bdf, while storage, stepping, seeds
and accounting stay in solarlab.solvers.time. A new forbidden import-linter
contract keeps the edge from coming back.

Moved symbols (old solarlab.solvers.time.* -> solarlab.experiments.bdf.*):
bdf_1d._events -> _events
bdf_1d._adapter_step_budget -> _adapter_step_budget
bdf_1d._generation_density_context -> _generation_density_context
bdf_1d.solve_electronic_bdf -> solve_electronic_bdf
bdf_1d.solve_ionic_bdf -> solve_ionic_bdf
assembled_ionic_bdf._events -> _assembled_events
assembled_ionic_bdf._solve -> _solve_assembled
assembled_ionic_bdf.solve_assembled_ionic_bdf -> solve_assembled_ionic_bdf
assembled_ionic_bdf.solve_assembled_multiple_ionic_bdf -> solve_assembled_multiple_ionic_bdf
Lower interfaces added: bdf_1d.BDFProtocol and bdf_1d.bdf_seed.

PublicAPI.json is byte-identical and public transient entry points keep their
paths; the four direct backend paths carried no external compatibility
promise, so no upward re-export is retained. New publication provenance names
experiments.bdf; historical module identifiers stay readable unchanged.

Evidence (archive Stage03/P03CloseoutV1/Gap2V1): 40 factories / 160 states
identical in both equation modes, BDF 74/74 pairs bitwise, historical pins
1297/1297, CND3 readback 76 packets / 365 arrays unchanged; test_all 7239/0
in both modes at ed97ac3. On 3ffd21f every CI selection passes (core 4770,
packages 1884, backend, frontend, mypy, ruff, imports 10/10, legacy mode);
the only conflict was the test module docstring, kept with both sentences.
Science accepted the implementation proof with one condition
(Gap2ImplementationReviewV1, dea242ce): G2I-C1, the pre-existing runtime
leak through package initialization, closes separately in gap 2b before
P03-01 promotion or milestone A closure.

Constraint: M-class, bitwise; Architecture-owned 2D files untouched
Rejected: lazy upward imports or backend re-exports | they would keep the solvers -> experiments edge alive
Confidence: high
Scope-risk: moderate
Directive: Keep experiment orchestration in solarlab.experiments; the forbidden contract fails on any new solvers -> experiments import

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g tests

CI run 41057960755 (backend, Python 3.13.15) failed
test_bounded_missing_samples_retry_before_launch_and_recover[1]: the test
injected one sampler timeout and then called the real _process_info, so a
second, natural miss on a loaded runner raised missing_samples_total to 2.
Production accounting was correct; the test's count assumption was not.

After the injected misses, the affected policy tests now feed controlled
successful samples, so every counted miss is one the test injected. The
production supervisor loop, sample bookkeeping, launch gating, child process,
readiness barrier, store transitions, termination and reaping all still run.
New regression cases exercise a natural sampler timeout explicitly.

Test-only, one file. Production code, CI selections, the 0.5 s ps cap, the
three-consecutive-miss / two-second sampling bound and the 768 MiB shared RSS
budget are unchanged. Engineering's 30/30 loaded receipt; Main's independent
review accepted it with no conditions (IonicJvWallAndSamplerReviewV1, 4b5ba4a9;
production hunks 0). On cbec478 the isolated group passes 58/58 three times.

Constraint: test-only; no production limit or selection change
Confidence: high
Scope-risk: narrow
Not-tested: native Linux repeat under load (next push CI is the first)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s below experiments

Close P03-01 gap 2b (Science condition G2I-C1). Importing a solver module
no longer loads solarlab.experiments at runtime. The leak was pre-existing:
solvers.nonlinear.{ionic_1d, multiple_ionic_1d, steady_1d, steady_2d}
-> equations.implementation -> solarlab.reproducibility.__init__ -> registry
-> sources -> config.identity -> behavior -> legacy_defaults -> device.defaults
-> experiments.jv.inputs. import-linter cannot see this edge, because
package __init__ execution is not part of its graph.

solarlab/reproducibility/__init__.py now resolves its six public exports
lazily (PEP 562), keeping __all__, the defining objects and TYPE_CHECKING
exports intact. A fresh-process guard in the ordinary core selection imports
every solarlab.solvers module as the first requested module and fails if any
solarlab.experiments module is loaded. An injected-import negative control
and export-identity coverage come with it.

No numerical code, equations selector, frozen fixture or public registry
changes. Evidence (archive Stage03/P03CloseoutV1/Gap2bV1): test_all 7273
passed / 0 failed in both equation modes, 320/320 frozen states identical,
0 leaks in 60 fresh solver imports and in all five forbidden contracts, 85
declared-export lists equal __all__, and the release pin is verified. Performance's
independent review accepted it without conditions (Gap2bImplementationReviewV1,
cad97560); the raw dir() namespace delta (TYPE_CHECKING visible, undeclared
submodule attributes deferred) is disclosed as informational.

Constraint: M-class; public names, __all__ and PublicAPI.json unchanged
Rejected: moving device input declarations down now | larger change; the device -> experiments inversion is reported separately
Confidence: high
Scope-risk: narrow
Directive: Keep package __init__ modules free of eager imports that cross layers; the runtime guard catches what import-linter cannot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d ionic J-V example

The installed research_1d_shared ionic J-V v1 example needs more than its 30 s
whole-job budget on Linux Python 3.11 runners. Native diagnostic run
41062040632 failed 2 of 20 HTTP attempts, with wall_limit and the supervisor
correctly discarding the result. Success-only owned wall was p95 29.97 s on
3.11 against 21.4 s on 3.13. Solver counts are identical, and the extra time
is in the curve. The 30 s value is both a production admission ceiling and
the example's own request, so a test-only change could not fix it.

A new optional field, wall_profile_sha256, binds one IonicJVRegistration to
the content of the approved prepared device (schema
solarlab.ionic-jv.wall-profile.v1, approved digest b051cb0c) and to the exact
v1 recipe. Only that registration may admit up to 128 s. Catalog
construction, HTTP preparation and registered child admission each enforce
the same content authorization before any numerical work. ExecutionCatalog
ceilings are unchanged, so composing catalogs in any order, or calling the
direct constructor, cannot widen or narrow another route. Direct
IonicJVJob.submit and the raw worker binding keep 30 s. Numerical, RSS,
output, counter and sampler limits, and the 15-artifact assertion, are
unchanged. The manual diagnostic workflow now also measures uncensored child
timing on both Pythons. It fails if the allowance is below
ceil(max(128, 4 x p95, 2 x max)). The value stays provisional until that
native run exists.

This replaces V2 (1264aa73). Main rejected V2 for a catalog-composition hole
and an over-broad device opt-in (IonicJvWallAndSamplerReviewV1, 4b5ba4a9).
Main authored this version. Science reviewed it independently and accepted
it with no pre-landing conditions (IonicJvWallV3ReviewV1, d2675157): zero
unauthorized changes over 1120 composition cells, content binding
reproduced across clones at different paths, and every negative refused at
the child boundary. Author evidence (patch 2c92bd2b): 720-case baseline
comparison, Python 3.11 runs 20/20, backend, scan and control groups on
both Pythons (1022 tests), and the refactor, API, CI and static gates. On
c815a07: static 111, the module 66, packages 1884 non-editable, mypy clean.

Constraint: every non-authorized request must match the base outcome exactly
Rejected: catalog-level default wall clamp (V2) | composition order changed other routes' ceilings
Rejected: raising the fixture's request only | 30 s is also the production admission ceiling
Confidence: high
Scope-risk: moderate
Directive: 128 s is provisional; confirm or revise it from the native uncensored diagnostic before calling it validated
Not-tested: native Linux uncensored timing (dispatch the diagnostic workflow after this push)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es by readiness, not wall time

Five root backend tests failed in scheduled run 41008968887 under subprocess
coverage, and every failure was a race. Engineering's local reproductions
in the exact CI coverage mode show the mechanisms:
- test_execution malformed-terminal [not_mapping] and [missing_name]: the
  serial cold start of two workers let the independent worker cross its
  3 s cap (wall_limit at 3.139 s, with a succeeded child discarded).
- test_transient_worker failed-payload guards [missing-counter],
  [publication-error] and [publication-wall]: process startup used up the
  2 s guard window, so wall_limit pre-empted the intended branch.

A now starts both cold interpreters together, waits for both ready
boundaries, confirms the independent worker is running and the following
worker is queued, then releases the malformed writer. B's failure emitter
moved into the lightweight worker fixture module, so a spawned child no
longer imports pytest and the transient graph. A readiness/release
handshake orders cancellation and each failed return. The existing 2 s
guard window starts at the readiness boundary, and fixture-clock.json
records the excluded startup and any intentional publication offset. The
3 s and 2 s caps, the 2 s readiness deadline, the 8 s wait bound and every
assertion are unchanged: exact reasons, store states, terminal isolation,
artifact references and reaping. A new diagnostics helper preserves receipts,
terminals, signals, samples, logs and store events on failure.
pyproject.toml adds tests/runtime/runtime_evidence.py to the sdist list.

Evidence: each case passed 30/30 under CI subprocess coverage with four
CPU hogs and 30/30 uncovered. The backend, scan and control groups passed
468/468, and ruff, mypy and 10 import contracts passed. Performance
accepted it with condition RRFR-C1 (RootRuntimeFixReviewV1, 53dcd5bf): the
next complete scheduled replay must show all five targets and the
cancellation case natively, with full receipts. On b4463aa: backend 429,
the six target cases passed 5 times, refactor packages green.

Constraint: no production code, default or numeric limit changes
Rejected: raising the 3 s and 2 s fixture caps | censored timings cannot justify a larger limit
Confidence: medium
Scope-risk: narrow
Directive: The historical native A reason remains unproven; RRFR-C1 requires the scheduled replay's native evidence before A6 can close
Not-tested: native Linux covered replay (next scheduled run)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fail loudly on gaps

Scheduled run 41008968887 aborted one coverage shard with pytest INTERNAL_ERROR
(exit 3). Its recorder hook called the shared json.dumps, and
test_r1_streaming_digest deliberately replaces that function until teardown. That
left 1,021 of the shard's 3,231 planned nodes unexecuted, while tests.json still
said status=completed. The regression came in with 8a0bf11; fb18f7d was fine.

The recorder now binds every callable it uses inside pytest to private names at
import: json encode/decode and string quoting, time.monotonic, os.getcwd, open,
os.replace, os.fsync, os.write. A failure inside the recorder becomes an
explicit recorder_error event. The run goes on with exit 1, KeyboardInterrupt and
SystemExit still propagate, and a collection that cannot be verified is refused
(UsageError, exit 4) before any test body runs. No status can claim completion
when planned nodes are missing:
- tests.json reports incomplete with the nodes that never started;
- a coverage shard now checks its planned-versus-executed nodes, so os._exit(0)
  fails and names them;
- a failed slow preflight no longer writes completed;
- slow-node recorder errors are labelled as such.
Selection, sharding, caps, node identities and the journal start/report rows are
unchanged.

Evidence (archive CiCoverageV4RecorderFix V1-V3, author Performance): the 1,022
previously unexecuted cases now run under the real recorder with coverage. That
gives 3,066 complete phase receipts, 984 passed, and the 38 failures are exactly
the Main-attributed legacy set. tests/ci passes 160, ruff and mypy are clean, and
51/51 one-line mutants are killed. The negative control runs the real 8a0bf11
recorder. An independent reviewer (RecorderFixReviewV1, 1be0d914, plus its
addendum a79b1617) accepted it once finding 1 restored the collection refusal,
which V3 does.

This push starts the integrated scheduled replay for milestone A (A6), which
covers the root runtime replay condition RRFR-C1.

Constraint: no change to selection, sharding, caps or planned node identities
Confidence: high
Scope-risk: narrow
Directive: Recorder code must never resolve shared stdlib attributes while tests run; extend the private bindings when adding hooks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efault

Close milestone-A blocker A3, BDF equation-adapter closure. Gap 2 (cbec478)
and gap 2b (c815a07) closed the import boundary. Until now, the public BDF
routes still executed only the original equation classes, and they refused
an explicit assembled selection.

Admitted electronic public BDF now runs AssembledDC1DProblem through
ElectronicStorage1D, on the landed assembled storage facade. Single
positive-ion public BDF runs AssembledIonic1DProblem through
IonicStorage1D, with the matching assembled BE seed and accounting adapter.
The original classes stay reachable through the one-release opt-out,
equation_implementation(LEGACY) or NEWCORE_EQUATIONS=legacy, under the
existing refusal and no-silent-fallback rules. Dual/shared public BDF stays
refused, and multiple-domain public stays BE-only. In
equations/implementation.py only the two BDFStep capability rows change.
experiments/bdf_results.py keeps the historical original reads and the
process-bound manifest refusal, and records true assembled identities.

Evidence (archive AdaptiveBDFV1/AssemblyV2, author Core):
- 114/114 pairs are bitwise identical between assembled and original: the
  74 production pairs, the 20 FreezeV2 envelopes and the 20 ReadinessV2
  helpers. That covers 2,782,858 array entries and 41,450 scalars, with
  zero differing words and zero signed-zero exceptions.
- 40/40 frozen histories and 40/160 in both modes are unchanged, and CND3
  readback (76 packets, 365 arrays) is unchanged.
- test_all passes 7300/0 in both modes.
Science accepted the implementation proof with no conditions
(A3AssembledBdfReviewV1, 09000912): 45/45 comparisons over 15
configurations, 12/12 publication byte comparisons, and the 8 historical
signed-zero words explained and reproduced by a negative control. Because
the November 8 BDF evidence was produced with the original equations, the
FreezeV2 envelopes are bitwise identical under both implementations.

Constraint: M-class, bitwise; selector change limited to the two BDFStep rows
Rejected: keeping BDF original-only | milestone A requires existing new-core capabilities on the unified layer
Confidence: high
Scope-risk: moderate
Directive: CND2 rebases onto this commit; the time-method axis and the equation-implementation axis stay separate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed curve phase

The native diagnostic run 41190084645 left the A-C4 validation of the 128 s
ionic J-V wall allowance censored on Python 3.11. One uncensored sample was
cut by the unchanged 25 s numerical curve cap: 25.001 s, 50 of 51 points.
To decide the shipping curve cap with evidence, the manual diagnostic
workflow gains an input, ionic_jv_measure_curve_64s, which is off by
default. When it is set, scripts/ci/ionic_jv_timing.py runs its direct
diagnostic children with only the curve-phase ceiling raised to 64 s. The
30 s point cap, the 25 s initialization cap, every iteration, evaluation and
tolerance control, and the exact device, grid and voltage sequence are
unchanged. The output is labelled a measurement, not a limit
recommendation.

The override exists only inside the diagnostic harness, through an isolated
source-bound adapter. No production module imports it. The HTTP route, the
registered child admission and the raw worker binding refuse it, and the
shipping recipe and its wall-profile hash are unchanged. No push or
pull_request job changes behaviour.

Main's independent review accepted it (IonicJvCurveMeasurementReviewV1,
86107c2f): a 37-field comparison of diagnostic versus shipping child policy,
and 15 child-boundary plus 4 HTTP refusal probes. Condition ICM-C1 (a
combined-mypy FunctionType boundary) is fixed with a Callable boundary and
runtime narrowing, without casts or ignores. My pre-landing check found
ICM-C2: the static job installs only the dev extra, so the new tests/ci
module failed to collect without fastapi. The 15 service-dependent cases
moved unchanged to tests/runtime, which the backend job runs, and 5
import-safe cases stay in tests/ci. On 88339c4: dev-only static 167, the
moved runtime module 15, backend 444 + scan 26 + control 58, combined mypy
on 255 files, and 10 import contracts.

Constraint: measurement only; no production, shipping-recipe or numerical-cap change
Confidence: high
Scope-risk: narrow
Directive: Dispatch with run_ionic_jv_diagnostics=true and ionic_jv_measure_curve_64s=true; any shipping cap change needs its own reviewed decision and a new wall profile
Not-tested: native Linux measurement (dispatch after this push)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e to json internals

Integrated scheduled run 41201981031 (devel 31f43bd) failed shard
coverage-root-static even though all 160 tests passed. The outer recorder logged
recorder_error at stage journal: "shared string quoting was called". The cause:
test_error_serializer_keeps_bound_string_quoting patches
json.encoder.encode_basestring_ascii process-wide, and the recorder's privately
bound json.dumps still looks up json.encoder module globals (the string
encoders, c_make_encoder, _make_iterencode) at call time. Only the error-line
serializer was immune. The recorder did fail loudly as designed, but receipts
must not depend on what a test patches.

Every serialization the recorder performs while tests run (journal rows,
tests.json snapshots and error lines) now goes through one private encoder. It
is built at import from privately bound primitives and produces output
byte-identical to the previous json.dumps calls. Selection, sharding, caps, node
identities and journal row bytes are unchanged.

Evidence (archive CiCoverageV4RecorderFix/V4, author Performance): plain pytest
and the real scheduled coverage-root-static command both pass all 166 tests
with zero outer recorder errors, 62/62 mutants are killed, and 664 actual
journal rows match V3 bytes. The independent re-check accepted it
(RecorderFixReviewV1Addendum2, 25785b90):
- a module-scoped probe patching 40 json, json.encoder, json.decoder and
  json.scanner attributes gives 0 recorder errors on V4 and 31 journal errors on
  V3;
- 61,692 byte comparisons over 10,282 values show 0 mismatches;
- an independently built shard plan passes with receipt_complete and an empty
  error sink.
On cf2abf6, with dev-only static: tests/ci 173 passed and ruff is clean.

Constraint: byte-identical recorder output; no plan, shard or cap change
Confidence: high
Scope-risk: narrow
Directive: Recorder serialization must never route through the shared json module while tests run; read() still uses the shared decoder and must stay out of test-time paths
Not-tested: native scheduled replay with V4 (this push triggers it)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…overage shard

The landed root-runtime fixture fix (c20eb58) carries review condition RRFR-C1.
The next complete scheduled replay must retain native worker evidence for the
five fixed targets and the cancellation case, including successful attempts.
Scheduled run 41201981031 passed all six natively, but uploaded no worker
evidence. The workflow never set SOLARLAB_ROOT_RUNTIME_EVIDENCE, so
tests/runtime/runtime_evidence.py wrote its bundles to pytest's temporary
directory, outside the uploaded coverage-result directory (CiCoverageV5, 16ee2e52).

The coverage shard step now sets SOLARLAB_ROOT_RUNTIME_EVIDENCE to a per-shard
directory inside the uploaded path:
coverage-result/runtime-evidence/<shard id>. Two tests/ci guards cover it:
- the workflow keeps the destination inside the upload;
- evidence files never count as node receipts or coverage data in the run,
  combine and receipt logic.
The helper already retains successful attempts, so no test code changed.

Engineering authored it. Locally: dev-only static 175 passed, and the six target
cases under subprocess coverage wrote 6 bundles, 10 worker receipts and terminals,
and 4 fixture clocks. Performance reviewed it independently and accepted it with no
conditions (RootRuntimeEvidenceUploadReviewV1, 2ca35bf4). On 528da42 the dev-only
static selection passed 175.

This push starts the scheduled replay that should close A6 together with the
recorder V4 check and RRFR-C1.

Constraint: no change to test assertions, caps, selection or node accounting
Confidence: high
Scope-risk: narrow
Not-tested: native upload contents (this push's scheduled replay)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…AB_ override namespace

91a9323 set SOLARLAB_ROOT_RUNTIME_EVIDENCE for every coverage shard. The legacy
research capability gate in perovskite_sim/solver/mol.py rejects any non-empty
SOLARLAB_* variable as a product override. So scheduled run 41227286086 failed
six root-core legacy-parity tests, and failed the legacy unit physics, models
and validation shards, with "environment overrides=SOLARLAB_ROOT_RUNTIME_EVIDENCE".
That run was cancelled.

The harness variable is now SCHEDULED_RUNTIME_EVIDENCE_DIR. It is non-empty only
for coverage-root-backend* shards, the only ones that run the six root-runtime
targets; every other shard sees "". tests/runtime/runtime_evidence.py reads the
new name. A new tests/ci guard (test_workflow_environment.py) fails if any
workflow step sets a non-empty SOLARLAB_* variable. It catches step env, inline
assignments, export, GITHUB_ENV writes and os.environ, and allows only the
justified command-local SOLARLAB_PYTHON in the frontend Vitest step. The legacy
gate and all production code are unchanged.

Engineering authored it. The old name reproduces the six failures. With the fix
and a non-empty value, the three parity files pass 290/290 and the six runtime
targets still write six evidence bundles, successes included. Dev-only static
passes 197. Performance reviewed it independently and accepted it with no
conditions (RuntimeEvidenceEnvironmentReviewV1, 86be6d2a). On 91a9323, dev-only
static passes 197, and the parity files pass 290 with the new variable set
non-empty in an all-extras non-editable install.

Constraint: perovskite-sim legacy code is read-only; never touch its override gate
Rejected: exempting the variable inside the legacy gate | modifies retained legacy product code
Confidence: high
Scope-risk: narrow
Directive: Harness and CI variables must not use the SOLARLAB_ prefix; that namespace is product overrides
Not-tested: native scheduled replay (this push triggers it)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tcome

The single measured test_all at 4c8617b failed one case,
test_real_worker_rejects_internally_wrong_registry_schema_or_facade[job_schema_sha256].
The worker ended failed/worker_exception as expected, but its detail was
"Command ['ps', '-p', ..., '-o', 'pid=,ppid=,lstart=,rss='] timed out after 0.5
seconds" instead of the registry refusal. _child_main probes its own startup
identity with one ps call before writing started.json and before registry
validation. That call sits outside the supervisor's tolerant RSS sampler, and the
broad child exception handler turned its TimeoutExpired into worker_exception.
On a loaded machine, one slow ps could therefore fail a user's job, or relabel its
failure, before the job's real outcome was reached.

The child now retries its startup identity within its own private bounds, which
mirror the supervisor RSS-sampler bounds: 3 misses within 2 s, with the 0.5 s ps
cap unchanged. A test keeps the two pairs equal. If every attempt fails, it records
an unavailable startup identity and continues, so the worker reaches its real
outcome. The supervisor carries a degraded observation into the final
ExecutionReceipt and the persisted RunStore result as
child_start_identity_observation, scoped apart from its own sampled parent
identity. The field appears only in the degraded case, so normal receipt and
result bytes are unchanged. It never replaces the worker's reason or detail.
Reconciliation stays uncertain on a missing identity. No signal or reaping
decision trusts an unverified identity. JobProvenance, RSS budgets and the sampler
policy are unchanged.

Engineering authored the fix. Core wrote the two follow-up amendments while
Engineering's model was suspended: 90 s caller waits, 7x the measured loaded
maximum, and restoring execution.py's local RSS constants so that no new private
cross-module import is added.
- Performance reviewed it: accepted with condition PSID-C1
  (PsStartupIdentityReviewV1, 363005e6). Core's independent recheck closed PSID-C1
  (Addendum1, e0db2ec1) with 3/3 normal byte comparisons and 10 safety controls.
- My full CI-equivalent check on 4c8617b caught the private-import guard in the
  packages job. The final bytes pass every selection in both equation modes:
  core 4776, packages 1906, static 197, backend 467, scan 26, control 58,
  frontend 47 + 4, mypy 254, ruff, and imports 10/10.

Constraint: the 0.5 s ps cap, miss policy, RSS budgets and assertions are unchanged; no JobProvenance schema change
Rejected: widening the supervisor sampler to cover startup | the startup probe runs in the child before any fence exists
Confidence: high
Scope-risk: moderate
Directive: Measurement failures must never overwrite a worker's own terminal reason or detail
Not-tested: native Linux loaded repetition (push CI is the first)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er node

Scheduled coverage runs 41239577644 and 41266788155 both stopped at the
110-minute step limit inside coverage-legacy-integration-interface_flux_balance.
The first node started and never reported setup, so three planned nodes ended
without outcomes and A6(b) (complete receipts) could not close. The stall comes
from the file's module-scoped ladder fixture on ubuntu-24.04. The slow sibling
already exhausts the 1200 s slow-shard cap with the same fixture. Locally on
macOS the three nodes pass in about 10 s each.

LEGACY_COVERAGE_NODE_SECONDS now designates only
perovskite-sim/tests/integration/test_interface_flux_balance.py, at 1200 s per
node. That shard reuses the slow-shard runner. A bounded collection preflight
runs first. Then each node runs in its own process group under
`coverage run --parallel-mode`, with the unchanged patch = subprocess config.
A node that overruns is killed and recorded as a timeout. It fails the shard
in tests.json, the journal and JUnit, and later nodes still run. Completed
coverage is kept, and each data file stays at the artifact root, where
combine() already looks. The worst case is 80 minutes inside the 110-minute
step. The planner refuses a mixed-file designation, and any growth past a
90-minute collection-plus-node budget. The other 269 shard plans are
byte-identical on the native 41239577644 inventory, and no node ID changes.

A designated node also dumps every thread's stack at 1140 s, just before its
kill, using pytest's faulthandler_timeout. A stalled fixture then leaves its
location in the node log. A new receipt check certifies the shard only when:
- every planned node has a start row and one outcome row;
- each timeout carries exactly one timeout label, a JUnit timeout failure and
  a nonzero exit;
- every other node passes the ordinary child receipt;
- the aggregate JUnit equals the sum of the child JUnit testcases plus one
  per labelled node. A failed call plus a teardown error writes two
  testcases for one node.

Performance authored the watchdog. Review CoverageWatchdogReviewV1 (92446a17)
accepted it with one blocking finding: the synthetic test's 5 s cap left too
little margin on contended runners. I wrote R1 while gpt-6-astra was suspended:
- the synthetic test cap is now 30 s;
- the JUnit count is exact per node (N2);
- the stack dump (N4);
- a 17-case receipt matrix plus an escalation test (N1). 15 of 16
  single-check mutants are killed; the survivor is the redundant final gate.
The reviewer accepted R1 in CoverageWatchdogReviewV1Addendum1 (652af36c). It
confirmed the survivor is equivalent and found 24 of 27 of its own mutants
killed. I then added the two guards it suggested, as test-only R2: a missing or
unparseable child JUnit never counts as a testcase, and slow shards get no stack
dump. Addendum2 (3524086a) accepted R2; production bytes are unchanged from R1.
Validation:
- tests/ci in the exact static-job environment: 222 passed.
- The real designated shard at the production cap: 3/3 passed, with
  complete receipts and 3 coverage files.
- An end-to-end call-failure-plus-teardown-error probe: 2 child testcases,
  receipt complete, shard failed.

This bounds and labels the legacy stall; it does not fix it. Expect three
disclosed timeouts on ubuntu-24.04.

Constraint: perovskite-sim legacy code is read-only; a timeout is never a pass or skip
Rejected: raising the 110-minute step limit | the stall never reported setup, so a longer limit only delays the same gap
Rejected: excluding the file from coverage | hides planned nodes instead of giving them outcomes
Confidence: high
Scope-risk: narrow
Directive: Designate a legacy coverage file only when it has its own shard and fits the 90-minute budget
Not-tested: the native ubuntu stall itself (the push-triggered scheduled run is the first)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-11-01 the user replaced the commanded Codex worker sessions (Main,
Science, Performance, Engineering, Core) with claude-opus-5-5 at effort max and
had the original sessions closed. That day gpt-6-astra was suspended for the
account twice (403 ModelAccessTemporarilySuspended), stalling both active
workers. The old rule that existing workers keep gpt-6-astra now named only
closed sessions. CLAUDE.md now states the current policy. The retired Codex
sessions' resume ids are kept in the archive execution state (revision 168).

The deputy commander and every other governance rule are unchanged. No test
or CI step reads the root CLAUDE.md.

Constraint: user decision, quoted in ExecutionStateV2 user_records (revision 167)
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…commander

On 2026-11-01 the user replaced the Codex deputy (session 01a180c3, terminal
term_3c65f4db) with a Claude Code session: d6a3ed06, claude-opus-5-5 at effort
max, terminal term_ac765e70. CLAUDE.md now names the new deputy. The old
session has been closed; its codex resume id is kept in the archive execution
state.

The deputy's rules are unchanged: it makes no Git mutations, dispatches or state
writes, and it takes over only after the commander has been unreachable for more
than 2 hours. The new deputy runs a read-only liveness watchdog on the
commander's transcript. It must also get no reply to an acknowledgement request
before it may take over. No test or CI step reads the root CLAUDE.md.

Constraint: user decision in the commander session, 2026-11-01
Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, we are unable to review this pull request

The GitHub API does not allow us to fetch diffs exceeding 300 files, and this pull request has 946

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T10:10:23.391488Z 3c88c4c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ShaneLogic

Copy link
Copy Markdown
Owner Author

Supplementary evidence after this PR opened: independent audit CiCoverageV6 Addendum 2.

The full push-triggered scheduled run 41286004950 at 20a5dc4 has finished. That commit has the same code as this PR's head 3c88c4c; the only difference is CLAUDE.md. The run had 272 jobs: 257 passed and 15 failed. All 11 root jobs passed, and all 15 failures are legacy shards, which are outside the green claim.

The independent auditor reviewed the run (CiCoverageV6Addendum2, kept in the project archive). The A6 verdict stands. This single run meets A6 (a)–(d) by itself:

  • (a) 7,714 root nodes ran with 0 failures, including named receipts for the 25 new tests/ci nodes.
  • (b) All 102 coverage receipts are complete and every node has an outcome. 5 nodes ended as labelled watchdog timeouts.
  • (c) All 801 recorder error logs are empty.
  • (d) Every RRFR-C1 check passes.

Updated disclosures

  • Flux-balance stall. Under the per-node watchdog, the legacy stall in tests/integration/test_interface_flux_balance.py has now occurred in 1 of 2 runs. Receipts stayed complete both times. Its cause is not established; the watchdog limits the stall but does not fix it.

  • Failures new since the earlier full run 41239577644. Two legacy slow shards failed here but not in that run. Both are recurrences of failures already in V6's history:

    • slow-legacy-7433cd8051: setup error caused by the inner 300 s subprocess timeout.
    • slow-legacy-a9b2612d94: CIGS registry case killed at its 1200 s cap.

    Nine nodes that failed in 41239577644 passed here.

  • Legacy nonpass count. The disclosed legacy nonpass set is now the union over both runs: 104 nodes. No observed failure is dropped. Nothing was relabelled, and no threshold, cap, skip or xfail changed.

No code changes. Only the user merges.

🤖 Generated with Claude Code

@ShaneLogic
ShaneLogic merged commit 99eb8a7 into main Nov 1, 2026
25 checks passed
@ShaneLogic
ShaneLogic deleted the devel branch November 1, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant