Problem
PlayObservation carries live BasePlayer references inside its Play entries (completed_tricks / current_trick), so a card-play strategy reaching through play.player.hand can technically still see another seat's cards. The projection seals what is handed over, not every object path reachable from it. This is the "Observation seat-sealing" follow-up already noted in the trust-boundary comment in packages/contrai-core/src/contrai_core/play.py (around the Play/PlayObservation definitions).
Proposal
Reuse the planned Position enum as the opaque seat token: observation-facing Play records carry Position instead of the live player. That is exactly the public information a real player has at the table ("West played the jack") — nothing more.
- Depends on:
Position enum, and preferably TeamSide (strategies currently compare play.player.team is self.team; they would work from Position.team_side instead).
- Engine strategies (
model/player/rule_based/card_play.py void tracking, partner-master checks, opponents-still-to-play logic) migrate from player references to seat tokens.
Why (roadmap)
Prerequisite for honest supervised/RL training: if observations leak hidden state, a learned policy will find and exploit the leak, invalidating evaluation results. Should land before any learning work consumes observations.
Acceptance criteria
- No object path from a
PlayObservation reaches another seat's Hand.
- Rule-based AI behaviour unchanged (existing expert-AI scenario tests stay green).
- Unit tests assert observation contents expose seats only as
Position.
Problem
PlayObservationcarries liveBasePlayerreferences inside itsPlayentries (completed_tricks/current_trick), so a card-play strategy reaching throughplay.player.handcan technically still see another seat's cards. The projection seals what is handed over, not every object path reachable from it. This is the "Observation seat-sealing" follow-up already noted in the trust-boundary comment inpackages/contrai-core/src/contrai_core/play.py(around thePlay/PlayObservationdefinitions).Proposal
Reuse the planned
Positionenum as the opaque seat token: observation-facingPlayrecords carryPositioninstead of the live player. That is exactly the public information a real player has at the table ("West played the jack") — nothing more.Positionenum, and preferablyTeamSide(strategies currently compareplay.player.team is self.team; they would work fromPosition.team_sideinstead).model/player/rule_based/card_play.pyvoid tracking, partner-master checks, opponents-still-to-play logic) migrate from player references to seat tokens.Why (roadmap)
Prerequisite for honest supervised/RL training: if observations leak hidden state, a learned policy will find and exploit the leak, invalidating evaluation results. Should land before any learning work consumes observations.
Acceptance criteria
PlayObservationreaches another seat'sHand.Position.