Removes a duplicate placement authority for local-player portal arrival. Portalling worked before this change and works after it — this is not a bug fix, EXCEPT that it found and fixed one dead-code production bug. THE PRODUCTION BUG: TryExecuteCanonicalPortalPlacement re-read the accepted destination at Place time, but TryBeginPortalReveal already consumes that slot at Aim time — so the arm was 100% dead code and every real portal Place refused with host-token-unavailable. Found only because we refused to accept 7 skipped tests instead of chasing the count to zero. RETAIL IS THE GENERIC PATH FOR THE THIRD ROUTE RUNNING: SmartBox::TeleportPlayer @0x00453910 = SetPositionSimple(dest, 1) with flags 0x1012, followed by PlayerPositionUpdated. BOTH INVERSIONS, WITH THEIR ANCHORS: unlike route 2, the leash IS armed here (ConstrainTo @0x0045418A) and velocity is zeroed (set_velocity @0x004541B4); unlike route 4b-3, the local teleport_hook runs AFTER placement (@0x004538AE). THE THREE-ROUND DEFECT CHAIN, HONESTLY: - Round 1 released the player at the pre-teleport position while the anim stream marched on — the contract wrongly assumed Place re-fires (process rule 1's third occurrence this campaign). - Round 2's fix inferred commit from a global PendingCount, which three non-committing paths also clear — making the SAME bug complete cleanly and silently. Strictly worse than round 1: round 1 at least tripped portal-complete-before-materialized. - Round 3 latches the commit where it actually happens (ReconcileAndAcknowledgePortal), keyed on reveal generation and teleport sequence, via TryConsumePortalCommit. Two of the three required regression tests landed and are sabotage-verified on both hosts (ParkedPlace_ForgottenByOrdinaryMergeDoesNotLatchAsCommitted / HeadlessPortalPrepareDestinationForgottenByOrdinaryMergeDoesNotLatchAsCommitted). The third (force-arm-takes-the-slot) was judged unnecessary on review: with the inference gone, PendingCount is only a "don't ask yet" guard at both gates, so a force operation occupying or vacating the slot no longer changes an input the commit decision reads — the case collapses into what the landed test already discriminates. THE B2/P3 RESOLUTION: both round-2 reviews were right about different branches of the same synchronous call. RuntimePlacementProjectionSubscription .OnPlacement acknowledges the FIFO head only when TryApply returns true; a Place whose portal authority went stale (transit ended/superseded while parked) used to return false, wedging every later entity's placement receipt behind it forever. Both sinks (RuntimePlacementPresentationSink, HeadlessRuntimePlacementProjectionSink) now acknowledge-and-ignore a stale-authority Place instead of refusing it. The regression test (RuntimePlacementPresentationSinkTests .PortalPlace_StaleTransitHostOrSequenceIsAcknowledgedAndIgnored) had been asserting the old, wrong `false` behaviour; it now asserts and sabotage-verifies the fix. Also lands: AP-144 (register discipline — the portal movement-event send reuses the stricter UsePositionFromServer gate where retail's SendMovementEvent is the looser autonomy_level != 0 test, diverging only at level 1, currently unreachable), AP-145 + issue #318 (the local-player collision-shadow presentation write bypasses its own publisher's ShadowObjects write via a direct cache .Set(), self-healing only once dedup diverges — filed, not fixed, pending a composition test), AD-42 deleted (its last citation retired by the canonical portal arm), AD-2 updated (the wait-cue's trigger predicate now covers a second cause), and two documentation corrections: the enter_world misattribution (both call sites are in SmartBox::HandleCreateObject, only one in the player branch — portal arrival is TeleportPlayer, not enter_world) and the stale "local player never reaches this path" comment on the generic-remote-render-pose write. Suite: 11,090 passed / 4 skipped / 0 failed. No new skips, nothing weakened. STILL OWED: the connected two-client gate, with ACDREAM_PROBE_LOCAL_TELEPORT=1, scored only if [local-tp] lines actually appear in the capture — and explicitly NOT scored as covering issue #318 (no composition test yet asserts PhysicsEngine.ShadowObjects directly). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
24 KiB
C4 route 3 — architecture / adversarial review, ROUND 2 (delta) — 2026-08-05
Verdict: FAIL.
Reviewed: the uncommitted working tree at HEAD cd3129e9, +2,570/-232 across
16 files. Round-1 report:
2026-08-04-c4-route-3-architecture-review.md.
dotnet build AcDream.slnx -c Debug exits 0.
Round-1 findings closed: A4, A6, A9, A10(a). A1, A2, A3 were addressed with real design work that is directionally right — the A1 fix in particular (inverting the readiness feed instead of touching the sequencer) is the correct architectural answer to a hard constraint, and I want that stated plainly.
The FAIL is one defect, present symmetrically on both hosts, introduced by
the A1/A3 fixes themselves: both new "am I committed yet?" gates infer
commit from the drive controller's global pending slot being empty. That
slot empties on at least three paths that do not commit — including the one
the drive's own doc comment names as the expected outcome of a park. When it
does, the graphical controller latches _placementCommitted = true and the
headless projection reports IsCollisionReady: true, and both hosts then march
the full completion sequence against an unmoved body. That is round-1's A1/A3
restored, and on the graphical side it is now worse, because
AcknowledgePortalMaterialized succeeds where it previously failed its
invariant.
Numbering continues as Bn to avoid collision with round 1.
MAJOR — the FAIL
B1 — PendingCount == 0 is not "committed"; both hosts infer commit from a signal that is also set by three non-committing paths
Severity: MAJOR (FAIL basis). Both hosts. Uncovered by any test.
Graphical, src/AcDream.App/Streaming/LocalPlayerTeleportController.cs:646-658:
if (_awaitingDeferredWake)
{
if (_acceptedPositionDrive.PendingCount != 0)
return false;
_awaitingDeferredWake = false;
_placementCommitted = true; // <-- infers commit from "not pending"
return true;
}
Headless, src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs:816-821:
if (_awaitingPortalWake)
{
committed = _acceptedPositionDrive.PendingCount == 0;
if (committed)
_awaitingPortalWake = false;
}
PendingCount is
RuntimeAcceptedPositionDriveController.cs:330 — _pending is null ? 0 : 1.
It is (a) global, not portal-scoped, and (b) cleared by every terminal path,
committing or not. Advance() clears _pending at five sites; three of them
run without a portal commit:
| site | condition | committed? |
|---|---|---|
:975 |
"The watch died — most likely a subsequent accepted Position's merge-time Forget" |
no |
:920 |
A2's new abandon-at-wake (!IsPortalAuthorityCurrent) |
body moved by RetryDeferred, suffix skipped |
:998 |
A2's new abandon at the prepare-retry branch (CancelToken) |
no — nothing ever placed |
The :975 path is the modal case, not a corner case. The method's own
doc comment (:880-892) says it verbatim:
RuntimeEntityObjectLifetime.TryApplyPositioncallsForgeton EVERY accepted Position for this entity … ACE broadcasts at 5-10 Hz, so aDeferredCellpark surviving past one broadcast interval is cancelled before its collision generation can ever commit it — the exact far-destination case the park exists to serve.
So: park → within ~100-200 ms an ordinary broadcast Forgets it → Advance
clears _pending → PendingCount == 0 → both gates declare success.
Concrete failure scenario (graphical). Portal to a landblock whose
collision generation has not committed. TryExecuteAcceptedPortalArrival
returns DeferredCell; _awaitingDeferredWake = true. One ACE broadcast
later the park is Forgotten and _pending clears. Next Tick:
_placementCommitted = true → placementReady = true → the sequencer leaves
Tunnel and fires Place → the if (!_placementCommitted) return; guard at
:557 passes → _placement.Place(_pendingRotation) writes the render
entity from the unmoved controller.Position and rebuckets to the source
cell → ObserveMaterialized(_pendingRevealGeneration, sequence, _pendingCell)
succeeds (the reveal is still active and current) → PlayExitSound reveals
the world viewport → FireLoginComplete sends LoginComplete, and
_transit.Complete(generation) now passes its
portal-complete-before-materialized check because materialization was falsely
acknowledged. The player is released into the world standing at the
pre-teleport position, the transit reports a clean completion, and nothing logs
an invariant failure. Round 1's A1 at least tripped
FailInvariant("portal-complete-before-materialized"); this does not.
Concrete failure scenario (headless). Identical shape:
PrepareDestination returns IsCollisionReady: true, so
RuntimeLiveEntitySessionController.TryAdvancePortalCompletion runs
AcknowledgeDestinationReadiness → AcknowledgePortalMaterialized →
Complete → TerminalProjected → LoginComplete → EndTeleport, and sets
controller.State = PlayerState.InWorld, all with the body unmoved. That is
round-1 A3 verbatim.
Secondary hazard from the same root: because the slot is global, a portal
park killed by a merge-time Forget can be immediately replaced by the force
arm's own RetainPending from that same merge
(TryExecuteAcceptedLocalPosition → RetainPending). The portal gate then
polls a ForcePosition operation, waits for it, and latches "portal
committed" when the force operation settles.
Why no test caught it. Both new park tests
(PortalDeferredCell_ParksThenCommitsExactlyOnceOnTheCollisionGenerationWake,
HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake)
commit the destination's collision generation so the park resolves by
committing. PortalDeferredCell_WakeAbandonsInsteadOfReconcilingWhenAuthorityWentStale
asserts the drive's behaviour (Assert.Empty(gameActions)) and stops
there — it never asks what the host gate concludes from the resulting
PendingCount == 0. The seam between "the drive retired the park without
committing" and "the host decides the portal is placed" is exactly where the
defect lives and is exactly what no test crosses.
Fix direction. Stop inferring. The drive already knows the answer with
certainty — ReconcileAndAcknowledgePortal runs on commit and only on commit.
Publish that fact:
- add a portal-commit observable to
RuntimeAcceptedPositionDriveController— e.g.bool TryConsumePortalCommit(long revealGeneration, ushort teleportSequence)latched inReconcileAndAcknowledgePortaland cleared on consumption, or anAction<RuntimePortalPlacementAuthority>commit callback supplied at construction alongsideisPortalAuthorityCurrent; - have both host gates consume that, keyed on the reveal generation/sequence they are waiting for, so a force pending, a Forgotten park, and an abandoned park are all correctly "still not committed";
- give the park a terminal "abandoned" outcome the host can see, so it can
either re-attempt cleanly (
_awaitingDeferredWake = falseand try Begin again next tick) or converge through the existing transit cancellation rather than silently succeeding.
Required tests (both must fail against the current code):
- Graphical: park, then kill the park with a merge-time
Forget(an ordinary accepted Position — the fixture'sOfferDestinationalready performs the merge) instead of committing the collision generation; drive 100 ticks; assertPlacement.Called == false,Movement.Controller.Positionunchanged,Reveal.PortalMaterializationCount == 0,Session.LoginCompleteCount == 0,Controller.IsActive == true. - Headless: same, asserting
PrepareDestinationkeeps returningIsCollisionReady: falseandcontroller.StatestaysPortalSpace. - Graphical: park, then let the force arm take the pending slot; assert the portal gate does not latch when the force operation settles.
MEDIUM
B2 — A2's re-validation does not close the FIFO wedge it was written for; it only narrows reachability
The implementer's own note is accurate and I confirm it: RetryDeferred →
CommitCanonical publishes the Place receipt asynchronously, and
IsPortalAuthorityCurrent runs only afterwards, inside Advance, gating the
suffix. But the wedge was never in the suffix — it is in the receipt:
Advance:920's abandon branch is reached only afterTryPeekAcknowledgedPlacementsucceeded, i.e. the sink already accepted the receipt. On that path there was never a wedge to prevent.- The wedge path is the one where the sink refuses:
RuntimePlacementPresentationSink.TryApply→RuntimeWorldTransitState.IsCurrentPlacementAuthorityfalse → return false →RuntimePlacementProjectionSubscription.OnPlacementleaves it at the FIFO head → every later placement receipt for every entity is blocked and the drive's pending never converges.IsPortalAuthorityCurrentnever runs on that path, becauseTryPeekAcknowledgedPlacementnever yields.
What the A1 fix does buy is reachability: with placementReady false during
a park, the sequencer cannot reach FireLoginComplete, so the transit no
longer ends underneath an outstanding park in the ordinary flow. The remaining
entries are mid-transit supersession (a second F751 while parked →
OnTeleportStarted → ResetTransit → EndTeleport), ResetSession, and
ResetGenerationPresentation — i.e. exactly the §9 "honest gap" cases.
That is a genuine narrowing and I credit it. It is not closure, and the consequence remains unbounded (whole-FIFO stall + non-convergent shutdown), so contract stop condition 4 ("P3 finds a portal receipt no mechanism can consume or retire — the FIFO-wedge shape changes the design, not the test") still applies.
Fix direction. The retire path has to exist at the receipt, not the suffix.
Either (a) let the sinks treat a Place whose entity/versions are current but
whose portal authority is dead as acknowledge-and-ignore (the same shape
Discard/ExecutorCompleted/WithdrawalRestored already use, and for the
same stated reason — refusing wedges the ordered stream), or (b) have
CommitCanonical drop a portal suffix it can already see is not current rather
than publishing a receipt nothing can consume. (a) is smaller and matches the
existing precedent in both sinks' own doc comments.
B3 — Headless _awaitingPortalWake is not reset across teleports within one session
HeadlessSessionWorldProjection.cs:762. The graphical twin
(_awaitingDeferredWake) is cleared in ResetTransit:932, which
OnTeleportStarted calls — clean. The headless field has no equivalent: it is
cleared only when the poll declares success, and HeadlessSessionWorldProjection
is constructed per session, not per teleport.
Scenario. Teleport 1 parks → _awaitingPortalWake = true. Before the next
PumpPortalCompletion, a new F751 arrives; TryCompletePortal overwrites
_pendingPortalCompletion with reveal 2. The next pump calls
PrepareDestination(reveal 2), which takes the poll branch left over from
reveal 1 — so reveal 2's placement is never even attempted, and if
PendingCount happens to be 0 it is immediately declared committed. This is
B1's inference bug plus a stale latch, on a path that does not require a
Forgotten park.
Fix direction. Key the latch to the reveal generation (or clear it in
BeginTeleport, which already runs per teleport on this host).
B4 — Both hosts' retry loops are unbounded and undiagnosable
A refusal loop (Contention, stale reveal, or a park that never converges)
now retries forever with no timeout and no terminal path.
- Graphical: the player holds in the tunnel with the retail wait cue after
5 s. This is a modelled end state — AD-2 already documents "predicate never
satisfies → portal transit remains in the authored tunnel and presents the
centered wait cue" — so an infinite stall is strictly better than round 1's
silent release, and I do not consider it blocking. Two gaps, though: AD-2
attributes that state to streaming/DAT failure only, and now a placement
refusal produces the identical user-visible state; and
_holdSeconds/ObserveWaitare now driven by!placementReadyrather than!dataReady, which is a real semantic change to the wait cue's meaning. The[tp-probe] REFUSED cause=…line does distinguish them in the log — good — but AD-2 should say so. - Headless: worse, because there is no cue and no bound. A bot whose
destination collision never becomes resident sits in
PlayerState.PortalSpaceindefinitely, connected and healthy-looking;PumpPortalCompletionis entirely silent. Under K4's 30-session envelope this is an invisible stuck session. At minimum emit one probe line on the first N retries; ideally bound the wait and fail loudly.
B5 — §8 items 8/9/10 (the committed-receipt presentation suite) — does NOT block, with conditions
Answering the coordinator's direct question.
Does not block route 3. Reasons, in order of weight:
- The render-entity half now has a proven mechanism, not a claim.
LiveEntityRuntime.TryApplyRuntimePlacementPlace:1394-1402performsentity.SetPosition(projection.WorldPosition),entity.Rotation = …,entity.ParentCellId = token.ExactCellIdandRebucketLiveEntity, synchronously insideCommitCanonical, beforeTryPublishPlacesnapshots. That path is shared with route 2's force arm and C3c's first entry and has existing coverage. Round 2 correctly rewrote the class doc to say so (round-1 A10(a) closed). - The restored
Movement.Controller.Position/CellIdassertions prove the canonical body resolved the offered destination — which is the half route 3 actually changed ownership of. - The shadow half is a pre-existing route-2 defect, not a route-3
regression:
LocalPlayerShadowState.Set(written by the sink) updates only the dedup cache, neverPhysicsEngine.ShadowObjects, whileLocalPlayerShadowSynchronizer.SyncPosededups against that same cache. It self-heals on the player's first >1 cm move. Route 3 widens the window (viaLocalPlayerProjectionController.Project:102's PortalSpace early return) but does not change its kind.
Conditions — all three, or it does block:
- It is recorded as an open issue with a number (#312's layer / route 2's B2 gap, now two campaigns old) and carried explicitly into C5's parity-test scope. Not a comment; a tracked item.
- The connected gate is not scored as covering it. The probe fields
(
leash,autorun,hookTail) say nothing about the shadow; there is no visual for it. If the user's session passes, the shadow claim remains test-verified-nowhere and must be reported that way (the §9 "honest gap" discipline). - The register gets one line under AD-2 or a sibling row naming the
cache-without-publish asymmetry, so the next reader does not assume
LocalPlayerShadowState.Currentmeans "published".
Concretely, what the test needs (in
tests/AcDream.App.Tests/World/RuntimePlacementPresentationSinkTests.cs, whose
fixture already owns BeginPortal and a LocalShadow):
- Committed local-player portal Place through the real sink. Pre-seed the
render
WorldEntityat a different (wire) pose — this is also §8 item 9's T8-ordering half. Drive aPlacereceipt for the local player carrying a VALID portal authority. Assert, after:entity.Position/Rotation/ParentCellIdequal the receipt'sWorldPosition/Orientation/ExactCellId(the wire pose did not survive); the spatial bucket moved to the destination landblock;LocalShadow.Currentequals the resolved pose; andPhysicsEngine.ShadowObjectsactually holds a row for the player at the destination cell — that last assertion is the one that discriminates cache-only from published, and is the whole point. - Discrimination half. The same receipt with a stale/superseded portal authority must be refused and then retired — not left at the FIFO head. This is also B2's regression test.
- Refused-Place presentation (§8 item 10). Already partly covered by
RefusedPlace_HoldsTheStreamAndConvergesOnlyAfterContentionClears; extend it to assert the pre-teleport pose is still the presented pose (entity + world snapshot store), not only that the body is unmoved. - Sabotages that must fail: remove the sink's
entity.SetPosition→ (1) fails; leave the shadow write as cache-only → (1)'sShadowObjectsassertion fails; make the stale-authority receipt returnfalseforever → (2) fails.
MINOR
B6 — isPortalAuthorityCurrent should be a required constructor parameter
The coordinator's specific concern, checked: both production sites wire it —
SessionPlayerComposition.cs:597-603 and HeadlessSessionHost.cs:665-671,
both to RuntimeWorldTransitState.CanPlacePortalDestination. Four test sites
do not, which is fine.
The residual is that a null default silently restores the round-1 defect, and
nothing catches a future production site that forgets it — there is no
architecture guard for this the way
RuntimePhysicsOwnershipTests.ProductionHostsUseSharedPlacementSubscriptionWithoutDirectChannel
guards the placement channel. With only 2 production + 4 test constructions,
making the parameter required (tests pass static _ => true) converts a silent
regression into a compile error for ~6 lines of churn. Same argument will apply
to B1's commit observable.
B7 — _placementCommitted is checked once and never re-validated
LocalPlayerTeleportController.cs:557. Round 1's Place handler ran
CanPlacePortalDestination immediately before mutating; round 2 moved that
check into TryAdvancePortalCommit's non-deferred branch only
(:668-676). Once _placementCommitted latches, the only guard before
_placement.Place() / ObserveMaterialized is IsCurrentLifetime. If the
reveal is cancelled between the commit and the Place event,
ObserveMaterialized refuses (IsCurrentPortalDestination) but the
presentation suffix has already run and the stream continues to
FireLoginComplete with an unmaterialized reveal. Much less severe than B1 —
the body genuinely is at the destination — but it is the same family. Cheap
fix: keep the CanPlacePortalDestination re-check in the Place handler
alongside _placementCommitted.
B8 — A8 (round 1) confirmed still open and confirmed non-blocking
Five portal tests still call ConvergePortalHost as the last statement of the
test body rather than in a finally, so an assertion failure is still masked
by a Dispose() throw during unwinding. Correctly flagged rather than silently
dropped. Test hygiene only — no production effect. It does mean that when B1's
new tests are written, a genuine failure may again present as a teardown throw;
fixing the try/finally first would save that debugging round.
B9 — round-1 A11/A12 unchanged
PhysicsDiagnostics.LocalTeleportHostKind process-global (accepted);
AddSyntheticIndoorCell geometry-free (accepted — and less load-bearing now
that A6's position assertions are restored).
Closed since round 1
| round-1 finding | status | evidence |
|---|---|---|
| A4 — route facts unread, inversions hardcoded | CLOSED | RuntimeAuthoritativePositionRoute.RunsTeleportHook:164 / .ConstrainAfterRouting:170; ReconcileAndAcknowledgePortal reads both; CommitCanonicalTeleportFrame(bool zeroVelocity, bool rearmConstraintLeash) branches on them. ConstrainPhase.None sabotage now fails PortalCommitted_MovesBodyArmsLeashOnceCancelsAutorunAndSendsExactlyOneMovementEvent's stale-anchor assertion, as the contract's §8 item 11 intended. |
| A6 — superseded-teleport discriminator removed | CLOSED | NewerStart_ReplacesOldDestinationWithoutReusingIt asserts Movement.Controller.Position.X/Y == 2 and the cell; SameLandblockDestination_… asserts (20,30,4) + 0x20210123; the Z-vs-X/Y reasoning in the comment is sound. |
| A9 — two sequence sources | CLOSED | The authority now uses destination.TeleportSequence, with a Debug.Assert pinning it equal to the caller's copy. |
| A10(a) — class doc claimed the suffix was the render entity's only mover | CLOSED | Doc rewritten to state the writes are redundant repeats of the canonical receipt's mutation, with the ordering cited. |
| A1 — refused Place did not stop the anim stream | partially — the mechanism is right (readiness inversion, sequencer untouched), and the Contention path is now correctly held and tested (RefusedPlace_HoldsTheStreamAndConvergesOnlyAfterContentionClears). B1 reopens it for the DeferredCell path. |
|
| A2 — no wake re-validation | partially — D-T2.4's re-validation now exists and is wired in both hosts and tested; B2 shows it does not close the FIFO wedge. | |
| A3 — headless discarded the status | partially — status now honoured, throws on Rejected/NotApplicable and on a missing drive, retryable pump added. B1/B3 reopen it. |
|
| A5 — presentation suite | still open — see B5 for the definite blocks/does-not-block answer. | |
| A7 — dual-host parity | CLOSED | HeadlessPortalPrepareDestinationParksThenCommitsOnCollisionGenerationWake drives a real wired drive end to end and asserts body position, cell, and PlayerState. |
| A8 | open, non-blocking (B8). | |
| A11 / A12 | unchanged (B9). |
Also verified this round
_pendingDestinationlifetime — round-1 judgment stands, not re-litigated.- The sequencer's own invariants survive the late
worldReady:Tunnelis explicitly a hold state ("Hold here until worldReady"),TickTunnelstill runs on the hold path soCurrentAnimationFramekeeps advancing intoTunnelContinue's exit window, andmaxForceat 5 s covers a stale frame.worldReadyhas exactly one consumer (case TeleportAnimState.Tunnel), so no other transition changed meaning. - No double-Begin headless:
TryCompletePortal's ownTryGetAcceptedTeleportDestination/TryBeginPortalRevealprefix cannot succeed twice for one reveal (the destination slot is consumed), and_awaitingPortalWakesuppresses a second concurrentBeginwhile parked. (The stale-latch problem is B3, a different failure.) _placementCommitted/_awaitingDeferredWakeare both cleared inResetTransit:931-932, so the graphical latches do not survive a new F751, a session reset, or a generation reset.- Build green.
Summary
| # | Severity | Finding |
|---|---|---|
| B1 | MAJOR | PendingCount == 0 inferred as "committed" on both hosts; three non-committing paths clear it, including the drive's own documented modal outcome. Reintroduces A1 (graphical, now with a successful false materialization) and A3 (headless). Untested. |
| B2 | MEDIUM | A2's re-validation gates the suffix, not the receipt; the FIFO wedge is narrowed (supersession/reset only) but not closed. |
| B3 | MEDIUM | Headless _awaitingPortalWake is not reset per teleport; a stale latch can skip reveal N+1's placement attempt entirely. |
| B4 | MEDIUM | Unbounded, undiagnosable retry loops on both hosts; headless has no cue, no bound, and no log. |
| B5 | — | §8 8/9/10 presentation suite: does not block, subject to three stated conditions; required test spelled out. |
| B6 | MINOR | Make isPortalAuthorityCurrent (and B1's commit observable) required constructor parameters. |
| B7 | MINOR | _placementCommitted never re-validated before the presentation suffix. |
| B8 | MINOR | A8 still open (5 tests), confirmed non-blocking. |
| B9 | MINOR | A11/A12 unchanged. |