test(mosaic): qualify CORE recovery and document OOM and DCC gates

This commit is contained in:
fy59 2026-10-14 21:12:39 +02:00
parent 70954e26f6
commit c9a35ce137
79 changed files with 8209 additions and 65 deletions

View file

@ -4,6 +4,27 @@
### STEP20C experimental evidence
- Record a separate CORE recovery campaign: nine new runs/102 output PNG,
comparable12-state standard RGB controls, two-chain horizontal return,
Y600 vertical clipping/input, same-chain hidden return and saved phased
timeout/ACK/normal-close/SHM3/3. All runner/client/compositor exits0, nine
terminal IPC1 and raw architecture INCONCLUSIVE remain distinct.
- Keep fullscreen crossing NOT_EXERCISED and comparison INCONCLUSIVE for
non-equivalent trees. Preserve the raw vertical X/0 field while documenting
the executed Y/600 predicate. No historical failure or UX policy is rewritten.
- Record private OOM A/B/C studies:40 expected C commands/six rejected mutants,
bounded independent review ACCEPTED, A abort/P1 and production policy
UNDECIDED. Modifier-aware DCC plane repair passes its isolated check, while
effective halfpixel/read-success/GL-error gates remain INCONCLUSIVE and G2
NOT_RUN. No new compositor/GPU build, staging, dependency or production edit.
- Add V3 and the exact-CORE integration draft, retaining native lifetime,
coherence, complete damage/effects, whole-field GPU, maintenance and legal
gates. Recommendation PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE, ticket
PARTIALLY_ADVANCED; final audit/publication pending at this edition.
Document prior immutable-archive whitespace exit2 and reversible EOF-only
normalization of new public fixture copies. STEP20C NEXT, production
multi-output NOT_YET_SUPPORTED, PRODUCTION_AUTHORIZED FALSE.
- Record the new minimal-core campaign separately from historical source-only
evidence: exact replay, six actual C UB reproductions, private camera guard,
extracted C unit normal/UBSan/ASan, OOM abort/P1, static build/local ABI and

View file

@ -651,3 +651,40 @@ Scoped evidence delta review ACCEPTED; final dossier review pending and
documentary publication PREPARED at this edition. STEP20A DONE; STEP20B
EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED; production multi-output
NOT YET SUPPORTED. Production integration is not authorized.
## Private CORE recovery and OOM/DCC evidence — 2026-10-14
The [V3 recovery decision](design/STEP20C_CORE_QUALIFICATION_DECISION_V3.md)
records a separate campaign on exact CORE SHA29baf31, without rebuilding it.
Nine new runs,102 output PNG/120 reserved slots, no retries, no compositor/GPU
builds or staging. Pristine/core standard has12 exact homologous whole-RGB
images. Two-chain horizontal Mosaic→splith, Y600 vertical clipping/input,
same-chain hidden return and phased saved timeout/normal-close are sampled
PARTIALLY_FEASIBLE. Fullscreen cross-output is NOT_EXERCISED and its comparison
INCONCLUSIVE: popups remain wholly H1 and the ancestor child counts/owner
heights differ. The raw vertical X/0 crossing field is preserved; executed
Y/600 predicates and measured bounds establish the separate vertical result.
Private OOM A/B/C extracted C studies pass their expected40 commands and six
negative mutants. A still aborts on sidecar OOM: P1 availability blocker.
B masks synchronously with owner admission; C reserves before publication,
without adding an installed struct field. These are bounded private options,
not adopted architecture. Production OOM policy remains UNDECIDED. Native
session survival/damage/effects/saved/reparent/GPU qualification is missing.
The per-plane AMD/DCC fixture correction passes locally, but the full real G1
epoch rejects effective halfpixel; read_success NOT_PROVEN and GL error queue
UNOBSERVED prevent a defensible new runtime under the zero-build contract.
Whole-field GPU remains INCONCLUSIVE, G2 NOT_RUN. CORE lacks PI hooks; no native
buffer identity or atomic generation is transferred from other diagnostics.
Applied rendering/input coherence remains required; no particular atomic
counter is selected. Sampled damage extents/RGB do not prove complete damage,
frame presentation or GPU lifetime. Local ELF/static provider and historical
five-file stage are rechecked, not future ABI/distribution qualification.
Recommendation PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE, ticket PARTIALLY_ADVANCED.
Scoped reviews ACCEPTED for bounded evidence; final audit/publication pending
at this edition. STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT;
STEP20D–G PLANNED; production multi-output NOT_YET_SUPPORTED;
PRODUCTION_AUTHORIZED FALSE. Compositor/tree authority and Row0 borrowing
remain unchanged; no UX, package, public ABI or production dependency adopted.

View file

@ -595,3 +595,39 @@ Recommendation PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE, scientific status
PARTIALLY_ADVANCED. Evidence delta review ACCEPTED; final dossier review pending,
publication PREPARED at this edition. Production multi-output NOT YET SUPPORTED;
STEP20C NEXT. No production, UX, package or historical evidence change.
## CORE recovery limits — 2026-10-14
The [V3 recovery dossier](design/STEP20C_CORE_QUALIFICATION_DECISION_V3.md)
adds nine runs/102 output captures without reopening historical failures.
Comparable standard controls, complete sampled two-chain horizontal return,
vertical Y600 clipping/input, same-chain hidden return and saved normal-close
now have targeted PARTIALLY_FEASIBLE evidence on exact CORE29baf31. All runner
exits0, client/compositor exits0, scientific and mechanical cleanup pass;
all nine terminal IPC exits1 and raw architecture INCONCLUSIVE remain separate.
Fullscreen comparison is still INCONCLUSIVE: both actual non-leaf fullscreen
ancestors are observed, but no popup crosses H2 and the trees differ. No UX
policy defect follows. The vertical raw crossing field tests default X/0;
executed Y/600 bounds establish vertical crossing without rewriting that field.
S7 omits optional destructive dismissal; historical E5 remains historical.
S8 qualifies timeout/ACK/normal-close/SHM3/3/final H2, but native saved identity
and retirement remain NOT_OBSERVABLE. Buffer identity, GPU completion, atomic
generation, complete damage and full effects coverage remain NOT_PROVEN.
OOM A abort remains an integration P1 availability blocker. B/C are accepted
only as private C studies; full-session survival, actual reparent/saved/damage
and recovery are not proved, and production OOM policy is UNDECIDED. GPU DCC
per-plane repair does not close the full gate: effective halfpixel rejects,
read_success NOT_PROVEN, GL queue UNOBSERVED, G2 NOT_RUN and whole-field
causality INCONCLUSIVE. No fitted tolerance or shader correction is adopted.
Exact-pair ELF/static SceneFX and the existing five-file stage are checked;
future ABI/maintenance, full package, PARTIAL/LEGAL_NOT_APPROVED notices and
release/security ownership remain gates. Scoped reviews ACCEPTED do not imply
final deep audit/publication success, still pending at this edition.
Recommendation PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE, ticket PARTIALLY_ADVANCED.
STEP20C NEXT; production multi-output NOT_YET_SUPPORTED; production authority
FALSE. Historical immutable archive whitespace warnings remain documented;
new public EOF-only fixture normalization is reversible and does not change
executed LAB evidence or CORE identity.

View file

@ -479,3 +479,30 @@ dossier review pending and documentary publication PREPARED at this edition.
Actual publication refs belong to the private post-push receipt/final handoff.
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED;
production multi-output NOT YET SUPPORTED. No production/UX/package change.
## Private CORE recovery, OOM and DCC gate — 2026-10-14
The new EXPERIMENTAL campaign is PARTIALLY_ADVANCED, recommendation
PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE. Nine new runs consume102 output PNG and
120 reserved capture slots, no retry/compositor build/GPU build/stage. Targeted
standard/return/vertical/hidden/saved evidence is PARTIALLY_FEASIBLE; fullscreen
comparison and GPU whole-field remain INCONCLUSIVE. OOM production policy is
UNDECIDED; no production, UX or package contract is adopted.
| Report | Scope and limits |
| --- | --- |
| [Standard, vertical and fullscreen recovery](design/STEP20C_CORE_STANDARD_VERTICAL_FULLSCREEN_RECOVERY.md) |12 homologous standard RGB exact, two-chain return and Y600 crossing; fullscreen NOT_EXERCISED/non-equivalent |
| [Hidden and saved lifecycle recovery](design/STEP20C_CORE_HIDDEN_SAVED_LIFECYCLE_RECOVERY.md) | Same-chain hidden return and phased timeout/ACK/SHM closure; native identity NOT_OBSERVABLE |
| [Modifier-aware GPU validation](design/STEP20C_GPU_DCC_MODIFIER_AWARE_VALIDATION.md) | Per-plane repair, full epoch effective halfpixel rejection, GL/read limits, G2 NOT_RUN |
| [Mandatory clip OOM options](design/STEP20C_MANDATORY_CLIP_OOM_POLICY_OPTIONS.md) | A abort/P1, private B/C studies,40 expected commands/six mutants; production policy UNDECIDED |
| [Qualification decision V3](design/STEP20C_CORE_QUALIFICATION_DECISION_V3.md) | Identities, budget, raw exits versus targeted verdicts, native/ELF/stage gates, prepublication markers |
| [Current integration draft](design/STEP20C_PRODUCTION_INTEGRATION_DECISION_DRAFT.md) | CORE-specific matrix, OOM/coherence/UX/maintenance/legal decisions and rollback boundary |
The [new source fixture](../tooling/probes/step20c-core-recovery/README.md)
links provenance, scoped reviews and compact results. Nested submission
editions remain frozen; later review receipts clarify their limited acceptance.
Public EOF-only normalization is explicitly reversible; executed LAB sources
and historical archives remain unchanged. Final deep audit/publication pending
at this edition; actual postpublication identities belong to the private receipt.
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED;
PRODUCTION_MULTI_OUTPUT NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED FALSE.

View file

@ -581,3 +581,28 @@ actual refs in the private post-push receipt/final handoff. Private success
does not authorize integration, distribution, daily use or STEP20D.
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED;
production multi-output NOT YET SUPPORTED.
## CORE recovery handoff — 2026-10-14
The [V3 decision](design/STEP20C_CORE_QUALIFICATION_DECISION_V3.md) records
nine new runs/102 output captures, no retry/build/stage, and targeted progress
on comparable standard controls, two-chain horizontal return, actual Y600
vertical crossing, same-chain hidden return and saved closure. These are
PARTIALLY_FEASIBLE observations on exact CORE29baf31, not production milestones.
Fullscreen remains INCONCLUSIVE/NOT_EXERCISED for cross-output semantics;
non-equivalent owner/ancestor geometry cannot settle popup UX.
The next smallest step is exact final dossier review, then a separately scoped
human decision about equivalent fullscreen fixtures, native OOM availability
qualification or GPU observability/precision. A's abort remains P1; B/C harness
acceptance does not select a production policy. The per-plane DCC repair passes
its limited check, but effective halfpixel/read-success/GL-error limits keep
the whole gate INCONCLUSIVE and G2 NOT_RUN. No remaining token authorizes a
retry. Native coherence/lifetime, complete damage/effects, whole-field GPU,
exact-pair maintenance and legal/release/security remain independent gates.
Recommendation PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE; ticket PARTIALLY_ADVANCED.
Scoped reviews ACCEPTED; final independent audit/publication pending at this
edition, with actual identities in the later private receipt. STEP20A DONE;
STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED;
PRODUCTION_MULTI_OUTPUT NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED FALSE.

View file

@ -0,0 +1,43 @@
# STEP20C — retour du propriétaire caché et lifecycle saved
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
## S7 : même chaîne cachée puis revenue
S7-core-hidden-return consomme12 PNG, sans retry. H1/H2 font800×600, scale1, transform normal, origines(-800,0)/(0,0). Le CORE exact29baf31 conserve une chaîne XDG TOPLEVEL→A→B avec vrais grabs59/68. Le mouvement pendant la phase entièrement cachée n’envoie aucun outside click de dismissal ; aucun popup_done n’est observé. Au retour, les mêmes IDs et serials restent présents, H1 RGB entière retrouve exactement sa capture mappée d’origine et H2 reste EXACT. Le bouton visible atteint B, puis les fermetures normales/SHM se terminent sans erreur Wayland.
Cette séparation répond à la lacune historique E5 : un dismissal avait déjà détruit sa chaîne avant le retour. E5 reste NOT_EXERCISED pour ce retour et son dismissal reste une preuve historique, pas un nouveau run. La seconde chaîne destructive facultative est omise dans S7 ; elle n’est pas annoncée testée. Ce cas move-only n’adopte aucune politique de hide/dismiss/reposition ni click-through sous grab.
**CORE_HIDDEN_RETURN=PARTIALLY_FEASIBLE**, borné à cette chaîne, ces captures et ces callbacks. Les IDs complets et hashes de captures sont liés dans les [résultats](../../tooling/probes/step20c-core-recovery/evidence/results.json) et `LAB/graphics/runs/S7-core-hidden-return/run/`.
Les deux S7/S8 utilisent owner10, workspace nommé1/id6 sur H1. S7 observe scene appliquée[-320,0,640,600], origine surface(-318,25), destination636×573 ; le buffer SHM natif du toplevel nested reste420×320. Les buffers natifs A350×170/B300×130 portent les IDs de protocole A[12,11,14], B[15,13,17] et TOPLEVEL[3,9,10]. A est[-218,105,350,170] et B[-118,165,300,130] à l’ouverture/restitution. Le rect logique propriétaire[480,25,640,575] ne doit pas être confondu avec la scene translatée par la caméra. Les décorations restent25px/current_border_width2.
## S8 : justification et phases saved
Le replay historique E6 démontre déjà HOLD→timeout naturel→release→ACK/live, H2 entière exacte. Sa capture post-release n’était pas manquante. Son verdict INCONCLUSIVE reste inchangé ; manquaient surtout normal-close explicite, SHM release et restauration finale après fermeture. S8 est réservé pour ces critères distincts, et non pour remplacer le reçu E6 rejeté.
S8-core-saved consomme10 PNG sur le même CORE, outputs800×600 scale1 transform normal, origines(-800,0)/(0,0). Les assertions sont faites par phase : une phase HOLD ne doit pas exiger une future ACK ; une phase live exige la release puis son ACK/commit. Le timeout expérimental demandé est cinq secondes, pas une qualification du timeout ordinaire200ms.
| Événement S8 | Temps monotone ns | Portée |
| --- | ---: | --- |
| Configure39 retenu | 274133187912812 | HOLD client observé |
| Observation après timeout naturel | 274138196687791 | Transaction expirée avant release |
| Release client | 274138248180402 | Commande de libération après observation |
| ACK39 | 274138251383305 | Après release |
| Nouveau commit | 274138252420086 | Après ACK |
La référence H2 entière est EXACT avant HOLD, durant HOLD, au timeout naturel, après restauration live et après fermeture finale. Le client répond à normalclose, ses trois SHM buffers sont relâchés3/3, le code Wayland est0 et le processus sort0. Les captures H1 distinguent les phases ; cette chronologie est corrélée au fil client et au timeout du CORE exact.
Le CORE n’a pas les hooks PI/C3 : l’identité native du saved node et son retrait sont **NOT_OBSERVABLE**. Une disparition visuelle après timeout n’identifie ni l’instance saved, ni le buffer GPU, ni sa retraite. C3 historique ne se transfère pas au CORE ; **CORE_SAVED_EPOCH=PARTIALLY_FEASIBLE**, **CORE_BUFFER_IDENTITY=NOT_PROVEN**, **CORE_ATOMIC_GENERATION=NOT_PROVEN**.
S8 utilise un vrai client saved SHM : initial636×573, configure39 demande636×513 ; la génération held reste au buffer initial jusqu’à release/ACK, puis commit live636×513. Le workspace redimensionné est[-770,30,740,540], destination owner636×513 ; ce n’est pas une simple déduction depuis la géométrie420×320 initiale IPC. Aucun popup/grab n’est lancé dans S8. Les détails natifs/configure/destination sont liés dans saved-evidence.json, sans identité native saved node.
## Fermeture, input et damage
Les deux runners sortent0 ; tous leurs clients et compositeurs sortent0 ; scientific_cleanup et mechanical_cleanup passent. Chacun conserve séparément terminal IPC1. Les reçus bruts architecture INCONCLUSIVE demeurent archivés. La [revue runtime limitée](../../tooling/probes/step20c-core-recovery/evidence/runtime-evidence-accepted.json) vérifie les captures/fermetures et accepte seulement la fidélité des conclusions ciblées.
Les snapshots applied et les callbacks pointer sont corrélés dans leurs fenêtres temporelles ; une origine antérieure à une mutation caméra ne remplace pas l’origine compatible avec le callback. Damage conserve les extents soumis et les RGB stabilisées ; les périodes sans trace, l’identité du buffer présenté et l’union minimale de toutes les régions ne sont pas observables. Aucun contrat de génération atomique n’est établi.
La prochaine petite étape serait un scope explicitement autorisé d’observabilité native saved/lifetime sur un nouveau diagnostic, en conservant l’exigence que rendu et input correspondent au même état appliqué. Aucun compteur particulier ni nouveau run n’est adopté ici.

View file

@ -0,0 +1,115 @@
# STEP20C — décision de qualification CORE V3
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
## Décision et édition
La recommandation unique est **PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE**, ticket **PARTIALLY_ADVANCED**. Les contrôles standard, retour horizontal, vertical, hidden-return et fermeture saved progressent sur le CORE exact ; fullscreen traversant, disponibilité OOM, cohérence native/lifetime, whole-field GPU, maintenance et distribution empêchent une qualification d’intégration.
Cette édition est un snapshot **avant audit final/publication**. Les revues limitées sont acceptées dans leur périmètre ; CORE_FINAL_INDEPENDENT_REVIEW reste NOT_PROVEN et CORE_DOCUMENTARY_PUBLICATION PARTIAL ici. L’audit final, le commit réel, les remotes vérifiés et les marqueurs postpublication appartiennent au reçu privé ultérieur, sans promesse de PASS anticipée.
Le baseline est `main` à `70954e26f62fb3df25995e31b0c615762ff85a60`. Le LAB historique `/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/` reste fermé,12/14 runs,88 output PNG et90 PNG avec sources. Le nouvel audit vérifie4688 entrées/4250 fichiers, octets/modes/types et ensemble de chemins ; les digests de contenu de répertoires ne sont pas recalculés. Les trois hashes closeout/manifest/publication restent respectivement `5166d6442aae8dfa2a5e939eb5e83141f83c55e7be89f93670eb381e9956df0a`, `4fd7ccedf00a59754db447d015fdca35fbcdda5b7de3820c3655b6e433765ce8` et `45396f706af9374547bda0d76edcb704298bca10a4ea300ae69b4fdb076de655`.
## Identités exactes et absence de transfert
| Objet | SHA256 | Identité / portée |
| --- | --- | --- |
| CORE existant | `29baf31aa3eeed259825c87d0799fa5d3dc83710afb967154604eb1c6657f965` | Build ID1b42e557e38db3440ae8dcf7ac967e4132223365 ; SceneFX0.5 statique |
| Pristine | `8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494` | SwayFX0.6, basefd71a6bdc061bd633b488ae7b83e8a6981d22f86 |
| G1 historique | `1905a20fa72ebcc7ec47d1584a4653654280288096a20005199eb75673ac0383` | Diagnostic GPU distinct ; Build ID1505e31c3dc08d2a7799c2ee9e1b2705eb61f5fe |
| wlroots installé | `7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837` | libwlroots-0.20.so, version0.20.2 exacte |
/proc/PID/exe, maps et renderer sont relevés par lancement. Le renderer observé est FX OpenGL3.2 Mesa26.2.4-arch1.1 AMD Radeon780M. Les diagnostics PI/C3/G1 possèdent une observabilité et des SHAs distincts : leurs identités buffer/saved ne se transfèrent pas au CORE.
## Registre nouveau et qualification
| Runs nouveaux | PNG effectifs / réservés | Résultat ciblé |
| --- | --- | --- |
| S0-pristine-standard / S1-core-standard |24 /24 | Douze RGB homologues exactes ; overflow standard/input B/A/close |
| S2-core-horizontal |16 /18 | Deux chaînes, quatre grabs, splith vivant puis B-close/A-survie/final |
| S3-pristine-vertical / S4-core-vertical |16 /24 | Y600 réellement traversé ; overflow pristine et exclusion CORE |
| S5-pristine-fullscreen / S6-core-fullscreen |24 /28 | Ancêtre non feuille réel ; traversée NOT_EXERCISED, comparaison INCONCLUSIVE |
| S7-core-hidden-return |12 /14 | Même chaîne et serials conservés, aucun dismissal durant hide, retour RGB exact |
| S8-core-saved |10 /12 | Timeout avant release/ACK ; normal-close, SHM3/3 et H2 finale exacte |
| G2 GPU / native ordering optionnel |0 /0 | NOT_RUN : acquisition GPU non défendable / aucun discriminant natif |
| Total |102 /120 |9/11 runs ; cap output160 ; zéro retry |
Les neuf runners sortent0 et les neuf transports IPC terminaux1 ; tous les compositeurs et clients sortent0. Les fermetures scientifiques explicites et le nettoyage mécanique passent. Les reçus bruts architecture INCONCLUSIVE restent distincts de la qualification ciblée PARTIALLY_FEASIBLE. Chaque argv, durée, owner/workspace, protocole/grabs, hash de capture, code et reçu de fermeture est conservé dans les résultats compacts et les chemins per-run LAB. Chaque run reste sous150s et18 PNG. Aucun build compositeur/GPU, staging ni installation nouveau.
Les [contrôles standard/vertical/fullscreen](STEP20C_CORE_STANDARD_VERTICAL_FULLSCREEN_RECOVERY.md) et le [lifecycle hidden/saved](STEP20C_CORE_HIDDEN_SAVED_LIFECYCLE_RECOVERY.md) détaillent la matrice. La limite X/0 du champ brut vertical est conservée et expliquée ; le prédicat exécuté Y/600 et les bornes démontrent la traversée. S5/S6 restent non comparables, sans défaut UX déduit. Les cases non exercées ne sont pas reclassées.
## Tests et revues limitées
La [première revue](../../tooling/probes/step20c-core-recovery/evidence/preflight-initial.json) demandait correction sur la fausse réussite possible de fermeture SHM. Un seul delta R1 est [accepté](../../tooling/probes/step20c-core-recovery/evidence/graphics-delta-accepted.json), manifesteed0c2be50d067f273e884c338b607a47d066d3a290432df99a513c620346fbc7,565 entrées. Les20 tests graphics,36 GPU et5 native offline passent dans les reçus de cette campagne. La [revue runtime](../../tooling/probes/step20c-core-recovery/evidence/runtime-evidence-accepted.json) vérifie les102 captures et neuf fermetures ; la [revue OOM](../../tooling/probes/step20c-core-recovery/evidence/oom-initial-accepted.json) reproduit40 commandes attendues et six mutants. Les résumés publics sont des transcriptions structurées fidèles matérialisées par le parent, non des fichiers rédigés par les reviewers ni des verbatim.
La [validation GPU](STEP20C_GPU_DCC_MODIFIER_AWARE_VALIDATION.md) répare le prédicat par plan, mais le replay complet rejette effective halfpixel ; read_success NOT_PROVEN/queue GL UNOBSERVED empêchent G2 avec le même binaire sous zéro build. Les [options OOM](STEP20C_MANDATORY_CLIP_OOM_POLICY_OPTIONS.md) restent une étude source-only ; la politique de disponibilité demeure UNDECIDED. Les erreurs transitoires de setup/extraction sont archivées séparément des résultats finaux.
## Ordering, damage et lifetime
`LAB/audit/historical-native.json` conserve les tuples `(LAB,run,output,commit_seq)` et hashes des logs. Les groupes sont rejoués séparément, sans sommer les logs redondants :
| Groupe / LAB | Commits / presents appariés / sans present observé | Cause des absences |
| --- | --- | --- |
| Initial, swayfx-mosaic-overnight-mcolx0gq |57 /55 /2 | UNKNOWN |
| Targeted, swayfx-mosaic-targeted-7gge6h8b |821 /789 /32 | UNKNOWN |
| Saved/input/GPU, swayfx-mosaic-saved-input-gpu-pz4z428f |414 /400 /14 | UNKNOWN |
Les comptes par output et les seq absents sont conservés dans le reçu, y compris seq2 ; ce gap n’est pas automatiquement un bug ou un teardown prouvé. Les nouveaux S n’ont pas de hooks PI : aucun compte natif équivalent n’est inventé. L’ordering optionnel est NOT_RUN faute de nouveau discriminant ; CORE_NATIVE_ORDERING reste INCONCLUSIVE.
Les mutations demandées, état owner/workspace/current, scène applied, hits et damage sont corrélés seulement quand les identifiants et fenêtres temporelles existent. SPIKE_DAMAGE observe des extents soumis aux rings et des RGB stabilisées ; ni toutes les régions/frames, ni contenu exact présenté, ni GPU completion ne sont prouvés. CORE_SAMPLED_DAMAGE=PARTIALLY_FEASIBLE ; CORE_BUFFER_IDENTITY et CORE_ATOMIC_GENERATION restent NOT_PROVEN. L’exigence canonique rendu/input sur même état appliqué demeure ; aucun compteur atomique particulier n’est choisi.
## ELF, stage existant et décisions ouvertes
`LAB/audit/elf-stage.json` revérifie le CORE exact ELF64 PIE, SceneFX statique sans DT_NEEDED SceneFX ni RPATH/RUNPATH et sans relocation non résolue. Le Stage1 historique vérifié contient exactement `/usr/bin/swayfx-mosaic` mode755 et quatre notices mode644 sous `/usr/share/licenses/swayfx-mosaic/`, aucun lien/fichier supplémentaire ; il n’est pas recréé. Notices **PARTIAL/LEGAL_NOT_APPROVED**, full future package et bit-reproducibility non acquis. Les mesures historiques43 bindings/40 symboles scène, node112/tree128 et offsets parent8/enabled32/visible88/children112 sont locales à cette paire, pas une ABI future garantie. Les85 overlaps ne démontrent seuls ni85 défauts ni la sûreté.
L’intégration reste bloquée par OOM availability/P1, observabilité/cohérence native et lifetime, complete damage/effects/whole-field GPU, maintenance SceneFX/wlroots exacte et distribution/licences. Offscreen-popup et fullscreen UX restent UNDECIDED ; aucun click-through universel. Release/security owners et reviewer juridique humain restent à désigner. Le [draft d’intégration actualisé](STEP20C_PRODUCTION_INTEGRATION_DECISION_DRAFT.md) propose uniquement les étapes de décision.
Le contrôle post-runtime `LAB/audit/post-runtime-safety.json` vérifie hashes PKGBUILD, patch production, /usr/bin/sway et wlroots inchangés, Sway quotidien PID2325/start2833 inchangé,52 spawns possédés sans PID vivant et tous runtimes privés supprimés. Aucune session quotidienne ni IPC implicite utilisée ; aucun déploiement ou rollback réel.
## Validation documentaire et exception de provenance
Le ticket précédent signalait `git diff --cached --check` exit2 sur les archives byte-identiques. Le contrôle readonly `git diff 70954e26^ 70954e26 --check` reproduit exit2 avec huit lignes de diagnostic dans `LAB/audit/previous-published-whitespace-check.json`. Il compare un commit publié ; il ne reconstruit pas l’ancien index. Les archives historiques restent intactes.
Trois nouvelles copies publiques Python ont seulement leur suffixe de lignes vides final normalisé pour satisfaire le nouveau contrôle whitespace. Les octets exécutés et le manifeste LAB demeurent immuables ; `evidence/public-source-eof-normalization.json` lie hashes avant/après, suffixe réversible et équivalence AST. L’identité repo/LAB constatée par les revues s’applique à leur édition avant ce formatage. Cette exception permet de reconstruire les octets exécutés et ne constitue ni nouveau run, ni changement du SHA CORE, ni changement du corps source. Le contrôle final des nouveaux fichiers doit passer ; sa validation et la revue finale sont consignées dans le reçu ultérieur, sans préjuger leur résultat ici.
## Marqueurs de cette édition avant publication
```text
CORE_HISTORICAL_REPLAY=PASS
CORE_STANDARD_FIXTURE_OFFLINE=PASS
CORE_VERTICAL_FIXTURE_OFFLINE=PASS
CORE_FULLSCREEN_FIXTURE_OFFLINE=PASS
CORE_HIDDEN_FIXTURE_OFFLINE=PASS
CORE_SAVED_PHASED_FIXTURE_OFFLINE=PASS
CORE_STANDARD_CONTROL=PARTIALLY_FEASIBLE
CORE_HORIZONTAL_RETURN=PARTIALLY_FEASIBLE
CORE_VERTICAL_CROSSING=PARTIALLY_FEASIBLE
CORE_HIDDEN_RETURN=PARTIALLY_FEASIBLE
CORE_SAVED_EPOCH=PARTIALLY_FEASIBLE
CORE_SAMPLED_DAMAGE=PARTIALLY_FEASIBLE
CORE_FULLSCREEN_COMPARISON=INCONCLUSIVE
CORE_NATIVE_ORDERING=INCONCLUSIVE
CORE_BUFFER_IDENTITY=NOT_PROVEN
CORE_ATOMIC_GENERATION=NOT_PROVEN
CORE_OOM_C_TESTS=PARTIALLY_FEASIBLE
CORE_OOM_ALTERNATIVES_REVIEW=ACCEPTED
CORE_PRODUCTION_OOM_POLICY=UNDECIDED
CORE_PRIVATE_ABI_PACKAGE=PARTIALLY_FEASIBLE
GPU_MODIFIER_AWARE_OFFLINE=INCONCLUSIVE
GPU_DCC_DESIGN_REVIEW=NOT_PROVEN
GPU_DCC_RUNTIME=NOT_RUN
GPU_WHOLE_FIELD_CAUSALITY=INCONCLUSIVE
CORE_FINAL_INDEPENDENT_REVIEW=NOT_PROVEN
CORE_DOCUMENTARY_PUBLICATION=PARTIAL
SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1=PARTIALLY_ADVANCED
STEP20A=DONE
STEP20B=EXPERIMENTAL
STEP20C=NEXT
STEP20D_G=PLANNED
PRODUCTION_MULTI_OUTPUT=NOT_YET_SUPPORTED
PRODUCTION_AUTHORIZED=FALSE
```
La plus petite suite justifiée est la revue indépendante du diff documentaire/fixtures exact, puis la décision humaine sur un futur scope ciblé fullscreen équivalent ou OOM natif/observabilité GPU. Les deux jetons non consommés ne sont pas une autorisation de retry, ni une ouverture STEP20D.

View file

@ -0,0 +1,67 @@
# STEP20C — récupération des contrôles standard, vertical et fullscreen
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
## Périmètre et qualité des fixtures
Le CORE exact SHA256 `29baf31aa3eeed259825c87d0799fa5d3dc83710afb967154604eb1c6657f965` est réutilisé sans build. Le contrôle pristine SHA256 `8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494` reste distinct. Les deux utilisent la paire wlroots contrôlée et un renderer FX GLES réel ; aucun résultat des anciens diagnostics A/G/C3/G1 n’est attribué au CORE.
Le replay `LAB/graphics/historical-replay.json` conserve E0-pristine/E0-core/E2 sans PNG, le défaut d’ascendance de la seconde chaîne E1, l’ancêtre non feuille réel E4 et le dismissal E5 avant retour. E6 avait déjà un timeout réel : son verdict gelé reste INCONCLUSIVE. Les prédicats ne sélectionnent plus un propriétaire à partir d’un index arbitraire ; le workspace et l’ascendance tiled sont vérifiés explicitement.
La première revue a reproduit INITIAL-P1-01 : une fermeture SHM invalide pouvait laisser un statut global/scientifique PASS. Un seul delta R1 rend l’échec bloquant et conserve les reçus de fermeture explicite. La [revue du delta](../../tooling/probes/step20c-core-recovery/evidence/graphics-delta-accepted.json) accepte le manifeste `ed0c2be50d067f273e884c338b607a47d066d3a290432df99a513c620346fbc7`, 565 entrées vérifiées, aucun écart. Vingt tests offline couvrent notamment les chemins réellement appelés main/normal_remaining/cleanup, pas seulement un modèle de fermeture. Cette acceptation autorisait uniquement les neuf cas S prévus.
## Résultats nouveaux
Tous les outputs mesurés font 800×600, scale1, transform normal. S0/S1/S2/S5/S6 utilisent H1 à (-800,0), H2 à (0,0) ; S3/S4 H1 à (0,0), H2 à (0,600). La configuration privée et les snapshots de chaque run constituent l’autorité des coordonnées. Chaque lancement `python -B run.py <id>` se fait dans `LAB/graphics`, avec runtime 0700 et socket explicite. Les relations natives configure→ACK→map et les serials de vrais boutons sont archivés.
| Run | PNG | Résultat ciblé | Verdict scientifique |
| --- | ---: | --- | --- |
| S0-pristine-standard | 12 | Débordement standard A/B, input B puis B-close et input A survivant, H2 finale exacte | PARTIALLY_FEASIBLE |
| S1-core-standard | 12 | Même scénario standard, douze RGB entières homologues exactes avec S0 | PARTIALLY_FEASIBLE |
| S2-core-horizontal | 16 | Deux chaînes, quatre grabs, Mosaic→splith, B-close/A-survie et H2 finale exacte | PARTIALLY_FEASIBLE |
| S3-pristine-vertical | 8 | A/B traversent y=600, pixels/input sur H2 standard | PARTIALLY_FEASIBLE |
| S4-core-vertical | 8 | Même traversée Y, H1 visible, H2 protégée et input interdit échantillonné | PARTIALLY_FEASIBLE |
| S5-pristine-fullscreen | 12 | Ancêtre non feuille fullscreen réel, mais aucun popup traversant | INCONCLUSIVE |
| S6-core-fullscreen | 12 | Ancêtre non feuille fullscreen réel, comparaison non équivalente | INCONCLUSIVE |
Les sept runners sortent0, les compositeurs et clients sortent0, les fermetures explicites/SHM et nettoyages scientifique/mécanique passent. Les sept transports IPC terminaux sortent1 (`Unable to receive IPC response`) et sont conservés séparément. Les reçus bruts `architecture_verdict=INCONCLUSIVE` ne sont pas remplacés par les verdicts ciblés dérivés. La [revue runtime limitée](../../tooling/probes/step20c-core-recovery/evidence/runtime-evidence-accepted.json) accepte la fidélité des mesures, sans qualification production.
## Identités, destination et buffers natifs
Tous les propriétaires ci-dessous sont sur workspace nommé1/id6 et HEADLESS-1. Ils sont tiled (`floating=auto_off`) ; la frontière renderer/input est contrôlée après mutation par peinture et coordonnées pointer fraîches. Le champ rect IPC reste une géométrie candidate pending, pas un epoch natif : scene et origine appliquée peuvent différer sous caméra.
| Run | Owner | Scene appliquée `[x,y,w,h]` | Origine surface appliquée | Destination window_rect `w×h` | Grabs réels A/B |
| --- | ---: | --- | --- | --- | --- |
| S0 |9 |[-400,0,400,600] |(-398,25) |396×573 |29/38 |
| S1 |9 |[-400,0,400,600] |(-398,25) |396×573 |29/38 |
| S2, seconde chaîne au contrôle standard |10 |[-400,0,400,600] |(-398,25) |396×573 |31/40 puis83/92 |
| S3 |9 |[400,0,400,600] |(402,25) |396×573 |27/36 |
| S4 |9 |[160,0,640,600] |(162,25) |636×573 |29/38 |
| S5 fullscreen |9 |[-800,0,800,300] |(-798,25) |796×273 |50/59 |
| S6 fullscreen |9 |[-800,0,800,600] |(-798,25) |796×573 |49/58 |
Le toplevel du client nested garde son **buffer SHM natif420×320**, même lorsque le compositor lui configure une destination différente. L’IPC geometry420×320 ne doit donc pas être pris pour la taille de la cellule. Les popups natifs sont350×170 et300×130 en horizontal/fullscreen,250×170 et170×130 en vertical, avec leurs propres configure/ACK/buffers. Les bordures restent `normal`, current_border_width2, avec décoration25px : un état root border none n’annule pas ces décorations propriétaires. Les snapshots, wire logs et captures natifs distincts sont conservés par run.
## Standard et retour horizontal
S0/S1 ont A `[-298,105,350,170]` et B `[-198,165,300,130]`, notation `[x,y,width,height]`, avec grabs29/38. Les douze images RGB entières homologues (480000 pixels chacune) sont EXACT, y compris le débordement standard, la survie A après fermeture B et le retour final. Ce contrôle qualifie les états échantillonnés du code statique en standard ; il n’établit pas une neutralité universelle.
S2 exerce les grabs31/40 puis83/92, chacun issu du bouton réel correspondant. Le workspace1/H1 et son layout sont sélectionnés et vérifiés avant le spawn de la seconde chaîne, puis son propriétaire est validé par PID et ascendance après map : le défaut E1 n’est pas répété. A/B traversent x=0, respectivement `[-218,105,350,170]` et `[-118,165,300,130]`. H2 entière reste identique en Mosaic pour les deux chaînes. Après retour splith avec popups encore vivants, H2 change de16380 pixels, puis8840 après B-close, puis0 après fermeture finale. Input standard B puis A survivant est observé. La clôture préserve l’identité du propriétaire et les relations XDG ; aucun click-through universel n’en découle.
## Traversée verticale réelle et limite du champ brut
A couvre y505..675 ; B y585..715, de part et d’autre de y600. Le contrôle pristine mesure sur H2 A6000 pixels et B19550 pixels, avec input réel. Le CORE mesure0 pixel A/B sur H2 et conserve H2 entière EXACT, avec input visible sur H1 et absence de bouton dans la zone exclue échantillonnée.
Le fichier brut `geometry.crossing` dit `NOT EXERCISED` : son helper générique utilise X et la frontière0 par défaut. Ce champ n’a pas été corrigé après le run. Le runner a exécuté le prédicat Y/600 et les bornes numériques configurées ci-dessus démontrent séparément la traversée verticale. Il faut lire ces deux sources ensemble ; une assertion X ne suffit pas à qualifier Y. Pristine et Mosaic ont des origines X propriétaires différentes, ce qui explique des images H1 différentes ; on ne prétend pas une égalité entière H1 entre layouts.
## Fullscreen : contrôle présent, comparaison non close
S5/S6 observent tous deux l’ancêtre current con10, non feuille, fullscreen_mode1. Les popups restent entièrement dans H1 : A x[-698,-348], B x[-598,-298]. Aucun débordement vers H2 n’exerce donc le bypass de clipping visé : traversée fullscreen **NOT_EXERCISED**.
De plus, l’ancêtre pristine possède deux enfants et un owner haut de275 ; le CORE un enfant et un owner haut de575. Les RGB différentes ne sont pas un défaut Mosaic attribuable sous un contrôle non équivalent. Le retour standard et les fermetures passent, mais **CORE_FULLSCREEN_COMPARISON=INCONCLUSIVE**. Aucune politique offscreen/fullscreen n’est décidée et aucun retry n’est autorisé par ce rapport.
## Frontière
CORE_STANDARD_CONTROL, CORE_HORIZONTAL_RETURN et CORE_VERTICAL_CROSSING restent PARTIALLY_FEASIBLE. Les régions et événements non échantillonnés sont NOT TESTED/NOT_OBSERVABLE ; native buffer identity, atomic generation et complete damage restent NOT PROVEN. Le plus petit futur travail fullscreen serait de définir hors ligne un contrôle réellement équivalent et traversant, puis de demander un scope runtime distinct. STEP20C reste NEXT.

View file

@ -0,0 +1,42 @@
# STEP20C — validation DMA-BUF AMD/DCC par plan
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
## Verdict
La réparation isolée du prédicat par plan et36 tests offline passent. Le **gate complet reste INCONCLUSIVE** : la vraie époque G1 est rejetée sur `effective halfpixel`, read_success reste NOT_PROVEN et GL_ERROR_QUEUE UNOBSERVED. **GPU_DCC_DESIGN_REVIEW=NOT_PROVEN ; GPU_DCC_RUNTIME=NOT_RUN ; GPU_WHOLE_FIELD_CAUSALITY=INCONCLUSIVE.** Aucun G2 ni nouveau build GPU/compositeur n’est lancé. La revue initiale accepte la correction offline limitée ; elle n’accepte pas un préflight runtime complet.
G1 historique SHA256 `1905a20fa72ebcc7ec47d1584a4653654280288096a20005199eb75673ac0383`, Build ID `1505e31c3dc08d2a7799c2ee9e1b2705eb61f5fe`, reste distinct du CORE29baf31. Son runner1, son premier A seul et son résultat INCONCLUSIVE sont immuables. Aucun B ni A-return n’a été acquis.
## Contrat explicite du validateur copié
La [fixture GPU](../../tooling/probes/step20c-core-recovery/gpu-field/README.md) lie parents/hash/delta. L’autorité locale est `/usr/include/libdrm/drm_fourcc.h`, SHA256 `992f33804acda60b0b46f1a272942c3592484ea3217d469239f1cb12249de2eb`. La [source primaire Linux UAPI](https://linux.googlesource.com/linux/kernel/git/torvalds/linux/+/refs/heads/master/include/uapi/drm/drm_fourcc.h) corrobore la signification ; le SHA local fixe les octets effectivement utilisés.
Le modifier exact allowlisté `020000001046bb04` décrit AMD=2/GFX11=4, tile27, DCC1, RETILE0, PIPE_ALIGN1. Pour cette disposition, plane0 est l’image et plane1 l’auxiliaire DCC. Les neuf objets historiques800×600 annoncent `[[3584,0],[1024,2293760]]` pour `[pitch,offset]`. Exiger width×4 sur plane1 confondait auxiliaire et pixels image.
Le nouveau prédicat exige un FOURCC packed32 connu, image stride≥width×4 divisible par4, pitch auxiliaire non nul uint32, offsets/arithmetic bornés et nombre de plans cohérent. Son filtre image-before-auxiliary est une restriction de fixture, pas une règle universelle de layout AMD. Formats/modifiers inconnus et incohérences sont rejetés. Le contrôle linéaire un plan est synthétique ; aucun nouveau contrôle GPU non-DCC n’est qualifié.
Les FD et tailles d’allocation ne sont pas observés. Alignement DCC, taille physique tilée, étendue auxiliaire et bornes d’allocation restent NOT_PROVEN. Aucune formule universelle inventée ne transforme une metadata plausible en décodage physique prouvé.
## Replay réel et second blocage
`LAB/gpu-field/replay-result.json` relit les metadata/arm/payload immuables, neuf reads et8312048 octets bruts. Les offsets/ROI/hash, contextes/attachments/channels et les neuf champs GL/PACK sauvés/restaurés passent. L’enveloppe native vérifie11 writes, deux copies, source/destination, ping-pong, texture unit, samplers, matrices, ordre et domaines de composition. Cette enveloppe est **STRUCTURAL_ONLY** et ne remplace pas validate_epoch.
| Stages | Bits uniform observés | Bits float32 attendus par expression source |
| --- | --- | --- |
| 0–2 | 983818240 / 987398144 | 983815946 / 987395086 |
| 3–5 | 967041024 / 970620928 | 967038730 / 970617870 |
Les shaders déclarent mediump et le hook conserve les bits retournés par glGetUniformfv. Une quantification est une hypothèse, pas une cause établie. Le validateur strict rejette `effective halfpixel` ; aucune tolérance ajustée après observation n’est ajoutée et le modèle idéal float64 ne devient pas un modèle d’arithmétique GPU.
Le hook indique lui-même que son sentinel alpha ne prouve pas le succès du read ni la neutralité de la queue GL. Le test de la restauration déclarée ne peut démontrer l’absence d’erreur GL non observée. Corriger cette acquisition exigerait un nouveau contrat/hook/binaire, hors autorisation zéro build de ce ticket. Réutiliser G1 avec un runner neuf ne ferme donc pas ces gates.
Les36 tests couvrent plans/pitches/offsets/fourcc/modifiers inconnus, samples tronqués/corrompus, hash/read validity/sentinel, GL/PACK/context/attachment, ROI, writes/copies manquants, aliases, samplers/uniforms, régions, ordre blur et continuité ping-pong. Le reçu de réparation whitespace du parent-delta est un changement de fixture prospective, pas une réécriture de G1. Les premiers essais de setup non aboutis restent archivés ; ils ne sont pas des résultats GPU acquis.
## Suite bornée
Les16×16 RGBA U0/cache échantillonnées sont exactes ; cache/presubmit diffèrent. Sans whole-field natif ni B/A-return, cela ne détermine ni cause entière H1, ni précision effective, ni causalité DCC. Les FAIL RGB historiques, dont le CORE33789 pixels/max6/5/1, restent inchangés.
Un futur ticket devrait définir l’observabilité read-success/GL error, établir le contrat de précision effective à partir de spécifications primaires et rendre explicites les limites d’allocation/FD, puis faire revoir les octets et le préflight avant toute réservation. Aucun shader, clear, clamp ou sampler n’est changé. L’analyse offline n’empêche pas les campagnes CORE indépendantes déjà achevées.

View file

@ -0,0 +1,43 @@
# STEP20C — options de politique OOM pour clip obligatoire
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
## Résultat et portée
**CORE_OOM_C_TESTS=PARTIALLY_FEASIBLE ; CORE_OOM_ALTERNATIVES_REVIEW=ACCEPTED ; CORE_PRODUCTION_OOM_POLICY=UNDECIDED.** L’acceptation porte sur une étude C privée limitée, pas sur la disponibilité d’une session réelle. A reste OOM_ABORT_EXPERIMENTAL et PRODUCTION_AVAILABILITY_NOT_APPROVED. L’abort est un blocage P1 pour intégration, sans modifier le CORE29baf31.
La [fixture OOM](../../tooling/probes/step20c-core-recovery/oom/README.md) conserve les patches privés A/B/C, extraction, bindings et harnais. Le [reçu indépendant ultérieur](../../tooling/probes/step20c-core-recovery/evidence/oom-initial-accepted.json) accepte la portée limitée ; les mentions NOT_PROVEN de handoff/README antérieurs reflètent leur date de pré-revue. Ils ne sont pas réécrits pour simuler une approbation production.
## Comparaison A/B/C
| Option | Échec d’allocation | Disponibilité / ownership | Coût / ABI privée | Preuve restante |
| --- | --- | --- | --- | --- |
| A, arrêt | abort sur allocation sidecar ciblée | Fail-closed par mort du processus ; session perdue |56 octets par arbre clippé ; setter parent inchangé | Récupération session NOT TESTED |
| B, bool + masque | Setter désactive synchroniquement l’arbre et retourne false | Owner décide admission ; popup OOM reste masqué, aucune récupération automatique |56 octets par arbre clippé ; déclaration privée void→bool,11 callers audités | Damage complet et admission native NOT PROVEN |
| C, préallocation | Réserve sidecar avant publication ; libère arbre si réservation échoue | Create retourne NULL avant init/listener ; setter n’alloue plus |64 octets sur chaque arbre créé ; aucun champ struct installé ajouté, signature conservée | Tous callers create et survie session NOT PROVEN |
A utilise les vrais corps parent byte-identiques ; six injections premières/deuxièmes allocations donnent-6/SIGABRT en normal/UBSan/ASan, sans core dump (`RLIMIT_CORE=0`). Le fait de refuser un clip absent ne constitue pas une politique production acceptée de perte du compositeur.
B audite11 appels setter dans transaction.c : deux tiling et neuf popup. Chaque échec est traité. Le setter ne réactive pas l’arbre ; le propriétaire garde l’autorité d’activation. Le risque de réactivation fading-out est protégé par `probe_clip_enable_tiling(ws, desired)`, dont le vrai corps et la statement fading exacte sont compilés. NULL standard peut être admis ensuite par la demande upstream ; un clip vide reste valide et sans hit. Un popup OOM demeure désactivé même après succès/retour standard : recovery reste un contrat futur, pas un choix UX implicite.
Le vrai enabled extrait transmet l’ancienne région visible au callback réduit avant disabled/update. Cette transmission est testée, mais les supports scene_node_visibility/update ne sont pas le moteur SceneFX complet. Ni visibility/damage réel, ni pixels périmés, ni effets/animation/present n’en sont prouvés.
C réserve après allocation arbre et avant scene_tree_init : échec de la seconde allocation n’expose ni parent/listener ni arbre partiellement publié. Le setter active/réutilise la réservation, NULL la rend inactive, zéro reste actif et destroy la libère. Les walkers/get ignorent les réservations inactives. Les arbres externes/embarqués ne sont pas couverts ; le root embarqué ne passe pas par create et ne peut être donné à ce setter C. Aucun appel Mosaic actuel ne clippe ce root, mais la généralisation n’est pas acquise.
Les tailles56/64 viennent de la déclaration sidecar exacte compilée avec vrai wlr_box et wl_listener, pas des structs réduits du harnais. C ajoute une allocation et retient64 octets sur tous les arbres jusqu’au destroy, plus un overhead allocateur non mesuré ; A/B allouent seulement aux arbres clippés. Aucun sizeof struct publique futur ni ABI installée n’est changé ou garanti par ces mesures.
## Validation C et revue
Les40 commandes attendues comprennent neuf builds normal/UBSan/ASan,27 exécutions defined/injections1/2, deux builds et deux exécutions mémoire. B/C sortent0 ; A produit les six abort attendus. Wayland listes/listeners/signaux et régions Pixman sont natifs ; le compositeur complet et les bibliothèques ne sont pas compilés sous sanitizer ici.
Six mutants sont tous rejetés : admission ignorée, abort retiré, false sans masque, réactivation setter, publication avant réservation, clip actif supprimé. Codes bruts `-6,99,-6,-6,-6,-6`. Les assertions des mutants restent distinctes des abort A attendus. Une première extraction fading trop large échouait à compiler ; `LAB/oom/attempt-4/` conserve les erreurs, corrigées par resserrement du marqueur à la statement exacte. Aucun échec transitoire de setup n’est caché.
La revue indépendante reproduit40/40 et6/6 dans un nouveau répertoire privé. Le manifeste fixture est `4a6dfda4f61d9c9f5a13588fe7ed7ac6ec6d9720cf2167a82e5f7c39f52d0c35`, sources `59207af208d13c43d0bb0aa3a1669dae697d22d20ba1199ece12817d754d9c1c`, slices `6c7092197a5055136356f1fad113f213b37dfea189c024eaebd437d2819c592e`. Les25 bindings exacts et neuf sources prototypes complètes sont vérifiés. Les scènes natives saved/GPU ne sont pas exercées par ces tests.
## Recommandation technique et décision humaine
C mérite l’étude native future pour éviter une allocation à l’exposition, sous réserve de son coût sur tous les arbres et des obligations create/NULL. B reste une alternative si admission/recovery est qualifiable. Ce conseil n’adopte ni C ni B, et ne tranche pas disponibilité contre fail-closed. **La politique production reste UNDECIDED.**
Un futur scope unique devra tester les allocations réelles, session survival, old/new damage, hide/return/fullscreen, popups nested/grabs, saved timeout, reparent/destroy, animations, retour standard, input/applied transaction/present, pixels périmés/FX et shutdown. Les harnais ne prouvent ni complete native damage, ni native reparent/saved, ni lifetime GPU. Aucune intégration, aucun build compositeur ni run OOM natif n’est autorisé par ce rapport.

View file

@ -1,87 +1,55 @@
# STEP20C — décision d’intégration production, draft
> EXPERIMENTAL — édition du 2026-10-14. Ticket `SWAYFX_MOSAIC_STEP20C_SAVED_LIFETIME_INPUT_EPOCH_AND_GPU_CAUSALITY_BATCH_V1`. Revue finale indépendante du dossier complet : ACCEPTED / audit PASS pour la fidélité expérimentale ; publication PREPARED à cette édition, reçu final hors Git après push. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT YET SUPPORTED. Aucun contrat UX, package ou production adopté.
> EXPERIMENTAL — édition du 2026-10-14, avant audit final et publication. Ticket `SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`. STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE. Aucun contrat production, UX ou package adopté.
LAB désigne `/home/fy59/swayfx-mosaic-saved-input-gpu-pz4z428f`, laboratoire privé hors Git. Les chemins LAB sont des références locales, pas un kit distribué. Le [dossier V2](STEP20C_GATE_CLOSURE_DECISION_V2.md) fixe la provenance commune et les limites. Les rapports historiques restent immuables.
LAB désigne `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`, hors Git. Le [dossier V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md), la [fixture source-only](../../tooling/probes/step20c-core-recovery/README.md), ses [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) et sa [provenance](../../tooling/probes/step20c-core-recovery/provenance.json) fixent les identités, budgets et limites. Les chemins LAB sont des références locales ; les binaires, PNG et traces restent privés. Aucun résultat historique n’est réécrit.
La recommandation unique reste **PRIVATE_CANDIDATE_NEEDS_TARGETED_EVIDENCE**. Ce draft remplace les réponses devenues obsolètes du ticket antérieur, sans modifier ses rapports immuables. Les vingt questions ci-dessous explicitent le dossier de décision du nouveau ticket ; elles ne constituent pas des décisions production. Le [dossier V2](STEP20C_GATE_CLOSURE_DECISION_V2.md) lie résultats, budget et preuves, les rapports saved/input/GPU/native/core en détaillent la portée.
## Recommandation unique
## 1. Un saved buffer naturel est-il retiré sur C3/A/G ?
**PRIVATE_CORE_NEEDS_TARGETED_EVIDENCE.** La tranche est PARTIALLY_ADVANCED. Ce draft actualise les réponses du dossier de décision sur le CORE exact29baf31 ; les rapports source-only, A/G/C3 et minimal-core historiques restent immuables. Il ne sélectionne aucune politique OOM ou UX et n’autorise aucune intégration. Audit final/publication encore pendants à cette édition ; reçus réels hors Git après les étapes correspondantes.
C3 : instance 15 créée/dessinée puis SD_REMOVE/SD_NODE_DESTROY après timeout natif et avant post-capture. A/G : timeout wire/RGB propre validé, retrait natif NOT OBSERVABLE. Aucun transfert C3.
## Matrice du CORE exact
## 2. Le clipping saved et H2 entière sont-ils établis ?
| Question | État réellement établi | Gate restant |
| --- | --- | --- |
| Périmètre minimal | CORE privé existant, SceneFX0.5 statique, wlroots0.20.2 exact, hooks PI/C3/OPT retirés dans sa provenance propre | Aucun transfert de garanties d’autres SHAs |
| Standard pristine/CORE |12 RGB entières homologues exactes, overflow et input B/A/fermetures | Généralisation au-delà des captures NOT PROVEN |
| Horizontal Mosaic→splith | Deux chaînes/quatre grabs, H2 exacte en Mosaic, overflow standard/B-close/A-survie/final | Complete frame/damage et autres layouts NOT TESTED ici |
| Vertical | Traversée Y600 démontrée ; pristine H2 A6000/B19550 et CORE0 ; input ciblé | Champ brut crossing X/0 limité, matrices non mesurées NOT TESTED |
| Fullscreen non feuille | Ancêtre current10/fullscreen1 réel dans les deux contrôles | Popups wholly H1, arbres2/1 enfants et owners275/575 : NOT_EXERCISED / INCONCLUSIVE |
| Hidden owner | Même chaîne59/68 sans popup_done, H1 revenue exacte, input B visible | Dismissal destructif séparé non réexécuté ; politique UX UNDECIDED |
| Saved | HOLD39→timeout naturel→release/ACK/commit, normal-close et SHM3/3, H2 exacte | Saved node identity/retirement NOT_OBSERVABLE ; GPU lifetime NOT_PROVEN |
| OOM |40 commandes C attendues, six mutants rejetés, alternatives revues | A abort=P1 ; B/C natifs/session survival/damage NOT_PROVEN ; politique UNDECIDED |
| GPU DCC | Correction metadata par plan isolée et36 tests offline passent | effective halfpixel rejeté ; read_success NOT_PROVEN/GL queue UNOBSERVED ; G2 NOT_RUN |
| Rendu/input/present | Corrélations sampled applied/input/damage | Buffer identity/atomic generation/complete damage NOT_PROVEN |
| ABI/package | ELF exact/relocations/provider statique et Stage1 historique5 fichiers revérifiés | Maintenance future pair, full package, notices/legal/release/security ouverts |
Oui aux captures des trois runs : target crossing H1 réel, held avant deadline, H2 entière RGB exacte durant held/timeout/final. Cela qualifie le timeout expérimental5 000 ms et les états échantillonnés ; pas tous les frames ni timeout ordinaire 200 ms.
Les [rapports V3](STEP20C_CORE_QUALIFICATION_DECISION_V3.md) et [résultats compacts](../../tooling/probes/step20c-core-recovery/evidence/results.json) séparent runner brut0, terminal IPC1, clients/compositeur0, scientific cleanup et mechanical cleanup PASS, et architecture brute INCONCLUSIVE. Neuf nouveaux runs,102 output PNG/120 slots réservés,9/11 cap, aucun retry/build/stage/install nouveau. Un succès de fixture n’est pas un contrat architectural.
## 3. Les épisodes natifs restent-ils fiables malgré reuse ?
## Disponibilité du clip obligatoire
Oui pour le replay dérivé :23 instances non chevauchantes, cible15 ; adresse réutilisable, owner/tree/source/intervalle corrélés. Ce récit observé ne crée aucune génération native.
L’abort actuel est **un motif P1 de blocage d’intégration** tant qu’une politique humaine n’est pas acceptée et qualifiée. Il refuse fail-open mais peut tuer tout le compositeur. Les harnais C rendent le risque reproductible, sans rendre sa disponibilité acceptable.
## 4. Le lifetime CPU saved est-il entièrement clos ?
B propose retour bool et masque synchrone sous autorité owner, avec11 callers et garde fading audités ; il laisse volontairement le popup OOM désactivé, sans récupération automatique. C réserve64 octets pour tous les arbres avant publication, contre56 aux arbres effectivement clippés pour A/B, sans ajout de champ struct installé ; ses callers create/NULL et racines embarquées restent à qualifier. Préférer l’étude future de C pour enlever l’allocation à l’exposition est un avis technique, **pas son adoption**. Session survival, damage natif complet, saved/reparent/effects/GPU restent non prouvés.
Non : lock 3→4 et retrait cible observés,17 records release globaux non ambigus et12 signaux natifs distincts, conservés sous 24 rattachements ambigus conservés. Release de retraite cible NOT OBSERVED ; pas d’attribution arbitraire.
## Contrat transactionnel obligatoire
## 5. Le lifetime GPU est-il qualifié ?
Rendu appliqué et input doivent rester cohérents avec le même état de transaction. Les snapshots et fenêtres de callback ne suffisent pas à garantir une génération atomique jusqu’au buffer présenté. Aucun mécanisme particulier de compteur n’est imposé. Une tranche minimale d’intégration doit définir les mutations couvertes, leurs frontières pending/current/applied et la preuve native de leur cohérence ; les hooks historiques d’autres binaires ne dispensent pas ce travail sur le futur SHA candidat.
Non. Saved node_destroy/unlock/SHM release/readback ne démontrent pas GPU completion. Aucun fence ni identité exacte du buffer présenté : NOT PROVEN.
Les comptes historiques57/55/2,821/789/32 et414/400/14 restent séparés par LAB/run/output/commit_seq ; missing cause UNKNOWN. CORE_NATIVE_ORDERING=INCONCLUSIVE et le run optionnel est NOT_RUN faute de discriminant. Damage soumis et RGB stabilisées ne prouvent pas all-frame coverage ni GPU completion.
## 6. L’input reste-t-il fidèle après mutation focus/caméra ?
## Questions UX à soumettre
PARTIALLY_FEASIBLE sur A/G propres :full→empty→full, focus filler→owner, deux mutations caméra et six clics/treize moves chacun. L’origine temporellement compatible explique le replay historique (-314,30)→(324,5) avec origine(-638,25), sans rows[-1] arbitraire.
Offscreen hide/dismiss/reposition, input sous grab et comportement fullscreen restent UNDECIDED. S7 démontre un retour move-only sans dismissal ; il ne choisit pas une UX finale. S5/S6 ne permettent pas d’attribuer une divergence fullscreen à Mosaic ni de promettre la préservation d’un débordement non exercé. Un futur contrôle comparable doit précéder toute proposition de changement UX canonique. Aucun click-through universel, taille finale, animation ou binding n’est adopté.
## 7. Le retour standard est-il terminé ?
## ABI statique, maintenance et distribution
Oui pour les deux nouveaux runs input, ciblé splith, input standard puis close owner/error0 et H2 exacte. Runtime global INCONCLUSIVE/IPC1 et helper error32 teardown restent séparés.
Le CORE exact est ELF64 PIE, sans DT_NEEDED SceneFX/RPATH/RUNPATH ni relocations non résolues sur sa paire locale. Les mesures historiques43 bindings/40 symboles et node112/tree128 sont des observations locales, pas une promesse ABI ;85 overlaps ne sont ni85 bugs ni un certificat de sûreté. Un changement de wlroots/SceneFX doit refaire provider/layout/headers, callers et source ownership, build/C failure, clipping/input/fullscreen/saved/damage/lifetime sur le nouveau SHA. La responsabilité de maintenance embarquée appartient déjà au projet Mosaic selon le contrat privé accepté. Les responsables nommés de release et de sécurité restent à désigner ; aucun calendrier ni SLA nouveau n’est fixé. Aucun fork permanent ou ABI publique n’est accepté.
## 8. Une atomicité particulière est-elle nécessaire à une tranche minimale ?
Le stage historique vérifié contient `/usr/bin/swayfx-mosaic`755 et quatre notices644, aucune autre entrée. Notices PARTIAL/LEGAL_NOT_APPROVED, distribution interdite sans revue juridique humaine et preuve package propre. Release/security owners restent à nommer ; aucun SLA inventé. PKGBUILD réel et patch cumulatif inchangés, aucun nouvel install/stage ni modification Packaging/Upstream/UX canonique.
Le contrat exige même état appliqué pour rendu/input. Le mécanisme particulier compteur atomique reste UNDECIDED ; aucun design adopté, ATOMIC_GENERATION NOT PROVEN.
## Rollback et prochaine décision
## 9. La cause whole-H1 A/B/A est-elle identifiée ?
Aucun déploiement n’a lieu et aucun rollback réel n’est exécuté. Le chemin existant workspace-local `layout toggle mosaic` conserve le retour au layout standard ; le binaire distinct préserve la coexistence avec SwayFX ordinaire. Ce chemin ne constitue pas une récupération de session après abort et cette dernière demeure NOT TESTED. Un éventuel plan de livraison/retrait devra être soumis avec responsabilité release/security et notice juridique avant action système.
Le mécanisme local FBO history est PARTIALLY_SUPPORTED :down-1 queue hors domaine, première divergence fraîche down-2, cinq supports divergents/11 et contribution fraîche différentielle0. Whole-field et événements historiques restent INCONCLUSIVE/NOT PROVEN ; whole-H1 FAIL 34 323/max 6 conservé.
## 10. Pristine a-t-il été touché ?
Non. Résultats antérieurs pristine 37 252/max 6 immuables ; aucun run GPU/pristine optionnel. Le modèle froid positif/max33 n’est pas un historique natif : queues préchauffées natives plus sombres.
## 11. Un correctif SceneFX est-il justifié ou autorisé ?
Aucun correctif shader/cache autorisé par ce résultat local. Champ FBO entier/précision effective et requalification distincte sont nécessaires avant changement causal. Aucun seuil ou sampler ajusté.
## 12. Quels buffers sont réellement identifiés ?
SHM wire/source, instance saved dérivée C3 et ROI GPU avec identité intra-pass. Up0/result/cache RGBA exact local ; presubmitRGB formule exacte. Aucun outputbuffer présenté ni lifetime persistant complet identifié.
## 13. Quelle instrumentation résiduelle change la provenance ?
CORRECTION explicite :fx_pass_submit bind-before-failed était déjà pristineScene37, fichier SHA398d594ace7257f4b69319290be7c1f14dc1ded446545ceb337404f5caab0cd3 identique pristine/base/core. L’ancien draft prétendant un ordre différent est réfuté ; rapports historiques non réécrits. PI/listeners/logs/readback restent perturbateurs.
## 14. Quelle instrumentation peut être supprimée sans perte de garantie ?
Core privé omet PI/C3/OPT et conserve quatre logs intrinsèques. Retrait source revu, aucune garantie de neutralité ou qualification runtime héritée. A/G diffèrent dans sept fichiers source C, pas huit ; aucun nouveau counter adopté.
## 15. Le noyau source-only privé est-il acceptable ?
SOURCE_REVIEW ACCEPTED après deux cycles corrections allocation failure/arithmétique/fullscreen/intersection disjointe.417 fichiers Mosaic/85 SceneFX/30 hashes/3 forward checks. Aucun build/runtime/UBSan/OOM C effectué, archive statique future non sélectionnée par static:true.
## 16. Que prouvent commit/present et damage ?
Nouveaux six logs 414/400/14, missing cause UNKNOWN ; cinq logs 340/330/10. Damage 38 transitions :7 covered by sampled union/26 no change/5 NOT OBSERVABLE. Ni tous frames, ni régions minimales, ni présentation exacte. Ordering optionnel NOT_EXERCISED.
## 17. Quelles décisions UX empêchent encore intégration ?
Offscreen popup hide/dismiss/reposition et click-through sous grab UNDECIDED. Aucun universal click-through ni nouvelle règle focus/taille/animation ; UX canonique inchangée.
## 18. Quelles décisions release/security/notice/ABI restent ouvertes ?
Exact-pair seulement85 overlaps/34 bindings/17 structs/6 offsets/121 headers. Stages A/G5 fichiers, inventories895/894 ; notices nouvelle GPU non qualifiées. Full package NOT RUN, bit-reproducibility NOT PROVEN, legal HUMAN_REVIEW_REQUIRED ; release/security à nommer, sans SLA inventé.
## 19. Quelles requalifications sont obligatoires pour un nouveau SHA ?
Build/C/UBSan/OOM privés, modèle/ELF/ABI/pair exact/stage, pristine et matrice clipping/input/timeout/fullscreen/standard/damage/lifetime. Notices/package/legal avant distribution ; contrats canoniques avant production. Les résultats précédents ne se transfèrent pas.
## 20. Quelle décision et quelle plus petite suite retenir ?
PRIVATE_CANDIDATE_NEEDS_TARGETED_EVIDENCE ; global PARTIALLY_ADVANCED, P1 GPU whole-field/lifetime/génération/damage ouverts. Revue finale ACCEPTED / audit PASS ; publication PREPARED à cette édition, reçu final hors Git après push. Aucun scope production/STEP20D ; suite documentaire puis nouveaux scopes explicites pour champ FBO/précision et core C/headless.
La prochaine plus petite étape est la revue exacte du dossier et des fixtures publiques, puis le choix d’un scope futur borné pour OOM natif, fullscreen comparable ou observabilité GPU/précision. Native coherence/lifetime, complete damage/effects, whole-field GPU, maintenance et legal sont des gates distincts ; aucune preuve positive n’en annule une autre. STEP20C NEXT ; production multi-output NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED=FALSE.

View file

@ -0,0 +1,77 @@
# STEP20C CORE recovery, OOM and DCC qualification
EXPERIMENTAL, source-only evidence for
`SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1`.
Baseline: `70954e26f62fb3df25995e31b0c615762ff85a60`.
Private LAB: `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`.
This fixture adds no production dependency or implementation. STEP20A DONE;
STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED;
PRODUCTION_MULTI_OUTPUT NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED FALSE.
## Evidence and scope
- [Graphics sources](graphics/README.md) preserve the new S0–S8 runner and
shared offline predicates. Nine runs, 102 output PNG, 120 reserved capture
slots, no retries, no compositor/GPU builds or staging. All runner exits0;
terminal IPC exits1 remain distinct. Scientific and mechanical cleanup pass.
- [GPU sources](gpu-field/README.md) repair the per-plane metadata predicate
and replay historical G1 without altering its outcome. Thirty-six tests pass;
the complete epoch still rejects `effective halfpixel`, read success is
NOT_PROVEN and the GL error queue is UNOBSERVED. New GPU runtime NOT_RUN.
- [OOM sources](oom/README.md) preserve A/B/C private prototype patches,
extraction bindings and C17 harnesses. Forty expected command results and
six rejected mutants are independently reproduced. Session survival and
native damage remain unproved; production OOM policy is UNDECIDED.
- [Results](evidence/results.json), [provenance](provenance.json), the
[durable budget snapshot](evidence/budget.json) and scoped review summaries
bind compact public metadata. Reviewer receipts are faithful structured
transcriptions materialized by the parent, not reviewer-authored files or
verbatim transcripts. Final deep review and actual publication identities
are recorded separately in the private post-publication closeout.
The nested fixture READMEs and source manifests retain their submission
edition, including then-pending reviews. The later scoped review receipts and
this directory's results record completed runtime/review evidence separately;
the frozen pre-run source bytes are not rewritten to update lifecycle labels.
Three published Python copies have one final blank line removed solely for
the new-file whitespace check. The [reversible normalization receipt](evidence/public-source-eof-normalization.json)
records both hashes, the exact suffix and equal AST/compiled bytecode. The
executed LAB copies and their accepted manifest are unchanged; those three
published files are not claimed byte-identical to the executed sources.
The historical LAB and its 4,688 entries remain unchanged. New sources have
their own parent hashes/deltas; historical failures are never reclassified.
The residual two conditional tokens do not authorize a retry or another run.
Every S identifier is consumed. The archived runtime launcher is evidence,
not permission to reuse this closed campaign.
Fullscreen cross-output remains NOT_EXERCISED: S5/S6 popups stayed inside H1,
and their ancestor child counts/owner heights differ. The raw vertical
`geometry.crossing` field retains its generic X/0 result; the executed Y/600
predicate and configured Y bounds establish the separate vertical crossing.
No raw source or receipt is edited to hide that metadata limitation.
## Offline reproduction
The following commands read retained historical/private evidence and launch
no compositor. Use `-B` to avoid changing historical directories with caches.
Choose a new, absent output filename for each audit or collection receipt.
```sh
python -B -m unittest discover -s tooling/probes/step20c-core-recovery/graphics -p 'test_*.py' -v
python -B -m unittest discover -s tooling/probes/step20c-core-recovery/gpu-field -p 'test_*.py' -v
python -B -m unittest discover -s tooling/probes/step20c-saved-input-gpu-batch -p test_native.py -v
python -B tooling/probes/step20c-core-recovery/audit.py --output /tmp/new-core-recovery-audit.json
python -B tooling/probes/step20c-core-recovery/collect.py --lab /home/fy59/swayfx-mosaic-core-recovery-qmhckba5 --output /tmp/new-core-recovery-collection.json
```
See the OOM README for a new private extraction/test directory; never rerun
its generators into the frozen evidence directory. Full source forks,
generated protocol files/slices, compiled clients/harnesses, ELF files, PNG,
DMA-BUF data and large logs remain outside Git. This is not a standalone
redistributable build kit, an OOM policy adoption or a production release.
The [V3 decision](../../../docs/design/STEP20C_CORE_QUALIFICATION_DECISION_V3.md)
provides the scientific interpretation and open integration gates.

View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""EXPERIMENTAL read-only historical audit; receipts go to a new private LAB.
WHY: source hashes and native event keys must survive a new interpretation.
CONTRACT: never launch a compositor, rewrite historical evidence, or infer a
present, buffer identity or missing-event cause from a screenshot or teardown.
INVARIANT: each native key is scoped by LAB, run and output before aggregation.
"""
import argparse
import hashlib
import json
import stat
import sys
from pathlib import Path
OLD = Path('/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_')
EXPECTED = {
'closeout.json': '5166d6442aae8dfa2a5e939eb5e83141f83c55e7be89f93670eb381e9956df0a',
'closeout-manifest.json': '4fd7ccedf00a59754db447d015fdca35fbcdda5b7de3820c3655b6e433765ce8',
'publication-receipt.json': '45396f706af9374547bda0d76edcb704298bca10a4ea300ae69b4fdb076de655',
}
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def historical():
failures = []
for name, expected in EXPECTED.items():
if sha(OLD / name) != expected:
failures.append(name + ': identity')
manifest = json.loads((OLD / 'closeout-manifest.json').read_text())
for row in manifest['entries']:
p = OLD / row['path']
s = p.lstat()
if oct(stat.S_IMODE(s.st_mode)) != row['mode']:
failures.append(row['path'] + ': mode')
if row['type'] == 'file':
if not stat.S_ISREG(s.st_mode) or s.st_size != row['bytes'] or sha(p) != row['sha256']:
failures.append(row['path'] + ': file')
elif row['type'] != 'directory' or not stat.S_ISDIR(s.st_mode):
failures.append(row['path'] + ': type')
actual = {str(p.relative_to(OLD)) for p in OLD.rglob('*')}
expected = {r['path'] for r in manifest['entries']}
actual -= set(manifest['excluded_self_reference_outputs'])
if actual != expected:
failures.append({'added': sorted(actual - expected), 'missing': sorted(expected - actual)})
return dict(status='PASS' if not failures else 'FAIL', entries=len(expected),
files=sum(r['type'] == 'file' for r in manifest['entries']),
checks='all file bytes/modes/types and complete path set; directory content digests not recomputed',
identities=EXPECTED, failures=failures)
def native():
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'step20c-saved-input-gpu-batch'))
from native_replay import replay
groups = {
'initial': [Path('/home/fy59/swayfx-mosaic-overnight-mcolx0gq/matrix/F-mosaic/run/compositor.log')],
'targeted': sorted(Path('/home/fy59/swayfx-mosaic-targeted-7gge6h8b/runs').glob('*/run/compositor.log')),
'saved-input-gpu': sorted(Path('/home/fy59/swayfx-mosaic-saved-input-gpu-pz4z428f/runs').glob('*/run/compositor.log')),
'historical-core': sorted((OLD / 'graphics/runs').glob('*/run/compositor.log')),
}
result = {}
for group, paths in groups.items():
rows = []
for p in paths:
r = replay(p.read_text(errors='replace'))
by_output = {}
for item in r['rows']:
o = by_output.setdefault(item['output'], dict(commits=0, paired=0, missing=[]))
o['commits'] += 1
o['paired'] += item['present'] is not None
if item['present'] is None:
o['missing'].append(dict(commit_seq=item['commit_seq'], cause='UNKNOWN'))
rows.append(dict(path=str(p), sha256=sha(p), by_output=by_output,
counts=[r[k] for k in ('total_commits', 'paired', 'missing')],
unmatched_presents=r['unmatched_presents']))
result[group] = dict(logs=rows, counts=[sum(r['counts'][i] for r in rows) for i in range(3)])
for name, expected in [('initial', [57, 55, 2]), ('targeted', [821, 789, 32]), ('saved-input-gpu', [414, 400, 14])]:
if result[name]['counts'] != expected:
raise ValueError('historical count mismatch: ' + name)
return dict(groups=result, missing_causes='UNKNOWN: no exact-key cause trace',
CORE_BUFFER_IDENTITY='NOT_PROVEN', CORE_ATOMIC_GENERATION='NOT_PROVEN',
CORE_NATIVE_ORDERING='NOT_EXERCISED',
limitation='CORE lacks PI hooks; zero parsed events means NOT OBSERVABLE, not zero native commits. No new run without a discriminant.')
def main():
p = argparse.ArgumentParser()
p.add_argument('--output', type=Path, required=True)
args = p.parse_args()
result = dict(historical=historical(), native=native())
with args.output.open('x') as f:
json.dump(result, f, indent=2)
f.write('\n')
print(json.dumps(dict(historical=result['historical'], native={k: v['counts'] for k, v in result['native']['groups'].items()}), indent=2))
return int(result['historical']['status'] != 'PASS')
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""EXPERIMENTAL read-only collection of this ticket's completed S observations.
WHY: raw exits, measured pixels, scientific closure and architectural claims
have different meanings. CONTRACT: no launch, retry, image mask, tolerance or
inference of native identity. INVARIANT: every claim retains its input hashes;
comparisons use entire RGB images with matching prospective state names.
"""
import argparse
import hashlib
import json
import re
from pathlib import Path
import numpy as np
from PIL import Image
def sha(p):
return hashlib.sha256(p.read_bytes()).hexdigest()
def load(p):
return json.loads(p.read_text())
def pixels(a, b):
aa = np.asarray(Image.open(a).convert('RGB'), dtype=np.int16)
bb = np.asarray(Image.open(b).convert('RGB'), dtype=np.int16)
if aa.shape != bb.shape:
return dict(result='INCONCLUSIVE', reason='different full image dimensions')
diff = bb - aa
changed = np.any(diff != 0, axis=2)
return dict(result='EXACT' if not changed.any() else 'DIFFERENT',
pixels=int(changed.sum()), total_pixels=int(changed.size),
max_abs_RGB=np.abs(diff).max(axis=(0, 1)).tolist(),
input_sha256=[sha(a), sha(b)])
def collect(lab):
runs = []
for execution in sorted((lab / 'runtime-execution').glob('*/execution.json')):
rid = execution.parent.name
folder = lab / 'graphics/runs' / rid / 'run'
outcome = load(folder / 'outcome.json')
events = [json.loads(line) for line in (folder / 'events.jsonl').read_text().splitlines()]
pictures = [e for e in events if e['kind'] == 'capture']
for e in pictures:
if sha(Path(e['path'])) != e['sha256']:
raise ValueError('capture integrity: ' + e['path'])
chains = []
for p in sorted(folder.glob('*chain*.json')):
data = load(p)
if 'protocol' in data:
chains.append(dict(path=str(p), sha256=sha(p), evidence=data))
log = (folder / 'compositor.log').read_text(errors='replace')
damage = {}
for line in log.splitlines():
m = re.search(r'SPIKE_DAMAGE output=(\S+) extent=(-?\d+),(-?\d+)\.\.(-?\d+),(-?\d+)', line)
if m:
d = damage.setdefault(m[1], dict(records=0, extents=[]))
d['records'] += 1
box = list(map(int, m.groups()[1:]))
if box not in d['extents']:
d['extents'].append(box)
runs.append(dict(id=rid, execution=load(execution), execution_sha256=sha(execution),
outcome=outcome, outcome_sha256=sha(folder / 'outcome.json'),
captures=pictures, capture_count=len(pictures), chains=chains,
damage=damage,
native_PI_events=sum('PI_OUTPUT_COMMIT ' in s or 'PI_OUTPUT_PRESENT ' in s for s in log.splitlines()),
damage_limit='Recorded extents added to rings; includes startup modes before config. No exact region/frame/content coverage or present is inferred.'))
comparisons = {}
for name, ids in {
'horizontal-standard': ('S0-pristine-standard', 'S1-core-standard'),
'vertical': ('S3-pristine-vertical', 'S4-core-vertical'),
'fullscreen': ('S5-pristine-fullscreen', 'S6-core-fullscreen'),
}.items():
a, b = [lab / 'graphics/runs' / i / 'run' for i in ids]
if not (a / 'outcome.json').exists() or not (b / 'outcome.json').exists():
comparisons[name] = dict(result='NOT_RUN', ids=ids)
continue
# Identical prospective names permit a raw comparison, not a claim of
# equivalent geometry or identical transaction time across processes.
common = sorted({p.name for p in a.glob('*.png')} & {p.name for p in b.glob('*.png')})
comparisons[name] = dict(ids=ids, whole_RGB={n: pixels(a / n, b / n) for n in common},
limitation='Raw same-named sampled states; review owner/configure/output geometry before semantic equivalence. Vertical Mosaic intentionally differs from pristine where clipped.')
return dict(status='EXPERIMENTAL_DERIVED_OBSERVATIONS', lab=str(lab), runs=runs,
comparisons=comparisons, new_runs=len(runs),
output_PNG=sum(r['capture_count'] for r in runs),
CORE_BUFFER_IDENTITY='NOT_PROVEN', CORE_ATOMIC_GENERATION='NOT_PROVEN')
def main():
p = argparse.ArgumentParser()
p.add_argument('--lab', required=True, type=Path)
p.add_argument('--output', required=True, type=Path)
args = p.parse_args()
result = collect(args.lab)
with args.output.open('x') as f:
json.dump(result, f, indent=2)
f.write('\n')
print(json.dumps(dict(new_runs=result['new_runs'], output_PNG=result['output_PNG'])))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,167 @@
{
"ticket": "SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1",
"limits": {
"runs": 11,
"captures": 160,
"captures_per_run": 18,
"duration_s": 150,
"scene_builds": 0,
"mosaic_builds": 0,
"gpu_builds": 0,
"stages": 0
},
"runs": [
{
"id": "S0-pristine-standard",
"campaign": "S",
"token": "94df962143284a549a2f6cec5658db7f",
"started_ns": 273893896100094,
"capture_limit": 12,
"actions": {
"configuration": 10,
"chain": 1,
"move": 5,
"capture": 12,
"click": 4,
"grab": 2,
"mutation": 1
},
"status": "RESERVED"
},
{
"id": "S1-core-standard",
"campaign": "S",
"token": "fad2790efa5f42e2b02f532d1b4a9bdc",
"started_ns": 273967713087801,
"capture_limit": 12,
"actions": {
"configuration": 10,
"chain": 1,
"move": 5,
"capture": 12,
"click": 4,
"grab": 2,
"mutation": 1
},
"status": "RESERVED"
},
{
"id": "S2-core-horizontal",
"campaign": "S",
"token": "4ec6f81d3734459baccacc7f4fd3ce71",
"started_ns": 273990133836292,
"capture_limit": 18,
"actions": {
"configuration": 15,
"chain": 2,
"camera": 2,
"move": 9,
"capture": 16,
"click": 8,
"grab": 4,
"mutation": 1
},
"status": "RESERVED"
},
{
"id": "S3-pristine-vertical",
"campaign": "S",
"token": "2b88b7ef5d814ebb86f38993b61190b3",
"started_ns": 274016156729138,
"capture_limit": 12,
"actions": {
"configuration": 9,
"chain": 1,
"move": 5,
"capture": 8,
"click": 4,
"grab": 2
},
"status": "RESERVED"
},
{
"id": "S4-core-vertical",
"campaign": "S",
"token": "5b1b3f8a3c1148fe9f1f1fc2f25587fa",
"started_ns": 274037351032301,
"capture_limit": 12,
"actions": {
"configuration": 9,
"chain": 1,
"move": 5,
"capture": 8,
"click": 4,
"grab": 2
},
"status": "RESERVED"
},
{
"id": "S5-pristine-fullscreen",
"campaign": "S",
"token": "0693560f72d44b80919535c3eaa790fd",
"started_ns": 274056656303804,
"capture_limit": 14,
"actions": {
"configuration": 12,
"chain": 1,
"move": 4,
"capture": 12,
"mutation": 3,
"click": 3,
"grab": 2
},
"status": "RESERVED"
},
{
"id": "S6-core-fullscreen",
"campaign": "S",
"token": "d3c0e2f84bc64c41baca8f5a815898ac",
"started_ns": 274081200052839,
"capture_limit": 14,
"actions": {
"configuration": 12,
"chain": 1,
"move": 4,
"capture": 12,
"mutation": 3,
"click": 3,
"grab": 2
},
"status": "RESERVED"
},
{
"id": "S7-core-hidden-return",
"campaign": "S",
"token": "35da90506f6f468dba1b2fad795bd8a6",
"started_ns": 274103082013577,
"capture_limit": 14,
"actions": {
"configuration": 15,
"chain": 1,
"camera": 3,
"move": 6,
"capture": 12,
"click": 4,
"grab": 2
},
"status": "RESERVED"
},
{
"id": "S8-core-saved",
"campaign": "S",
"token": "530195ad8d3440408a3e9a09e26fcfa0",
"started_ns": 274130228318903,
"capture_limit": 12,
"actions": {
"configuration": 11,
"camera": 1,
"move": 1,
"capture": 10,
"mutation": 3
},
"status": "RESERVED"
}
],
"builds": [],
"stages": []
}

View file

@ -0,0 +1,140 @@
{
"status": "EXPERIMENTAL_OFFLINE_ONLY",
"historical_status": "INCONCLUSIVE_UNCHANGED",
"historical_hashes": {
"meta": "c8ca51bca694db9e21471356a4260064ed81ab7013a88d4edb4b8572044a825c",
"rgba": "a6dc7fd873e1ab7e52e04625bacb3251ba8e1aad2e766811efebde7ae7932a99",
"arm": "06f961b802bc46ee168555f066d28f4f0e9afdf3f88174c1ee6f9de004fc30a0"
},
"samples": 9,
"raw_bytes": 8312048,
"sample_protocol": "PASS",
"native_envelope": {
"writes": 11,
"copies": 2,
"status": "STRUCTURAL_ONLY"
},
"epoch_validator": {
"status": "REJECTED",
"reason": "effective halfpixel"
},
"plane_evidence": {
"layout": "AMD_GFX11_DCC_NO_RETILE",
"image_stride": "PASS",
"auxiliary_fields": "PLAUSIBLE_ONLY",
"auxiliary_extent": "NOT_PROVEN",
"allocation_bounds": "NOT_PROVEN",
"fd_identity": "NOT_OBSERVED"
},
"halfpixel": [
{
"stage": 0,
"observed": [
983818240,
987398144
],
"expected_float32": [
983815946,
987395086
],
"observed_float": [
0.0012502670288085938,
0.001667022705078125
]
},
{
"stage": 1,
"observed": [
983818240,
987398144
],
"expected_float32": [
983815946,
987395086
],
"observed_float": [
0.0012502670288085938,
0.001667022705078125
]
},
{
"stage": 2,
"observed": [
983818240,
987398144
],
"expected_float32": [
983815946,
987395086
],
"observed_float": [
0.0012502670288085938,
0.001667022705078125
]
},
{
"stage": 3,
"observed": [
967041024,
970620928
],
"expected_float32": [
967038730,
970617870
],
"observed_float": [
0.00031256675720214844,
0.00041675567626953125
]
},
{
"stage": 4,
"observed": [
967041024,
970620928
],
"expected_float32": [
967038730,
970617870
],
"observed_float": [
0.00031256675720214844,
0.00041675567626953125
]
},
{
"stage": 5,
"observed": [
967041024,
970620928
],
"expected_float32": [
967038730,
970617870
],
"observed_float": [
0.00031256675720214844,
0.00041675567626953125
]
}
],
"sampled_relations": {
"U0_cache_RGBA_exact": true,
"cache_presubmit_RGBA_exact": false
},
"GL_ERROR_QUEUE": "UNOBSERVED",
"read_success": "NOT_PROVEN",
"GPU_MODIFIER_AWARE_OFFLINE": "INCONCLUSIVE",
"plane_metadata_correction": "PASS",
"GPU_DCC_DESIGN_REVIEW": "NOT_PROVEN",
"GPU_DCC_RUNTIME": "NOT_RUN",
"GPU_WHOLE_FIELD_CAUSALITY": "INCONCLUSIVE",
"epochs_acquired": 1,
"causal_limit": "No B/A-return or whole-field output; no fitted precision tolerance; no physical DCC decoding.",
"runtime_prerequisites": [
"Authorized new diagnostic binary with defensible read-success and GL error observability contract",
"Resolve effective uniform precision validation from primary specification without fitted tolerances",
"Allocation/FD/auxiliary bounds observability or explicit limited contract",
"Independent review of exact new source and acquisition preflight before any run reservation"
]
}

View file

@ -0,0 +1,40 @@
{
"review_kind": "TARGETED_GRAPHICS_DELTA",
"reviewer": "/root/delta_oom_review",
"independent": true,
"decision": "ACCEPTED",
"manifest_sha256": "ed0c2be50d067f273e884c338b607a47d066d3a290432df99a513c620346fbc7",
"manifest_entries_verified": 565,
"manifest_mismatches": [],
"repo_lab_source_mismatches": [],
"resolved_findings": [
"INITIAL-P1-01"
],
"new_findings": [],
"authorized_runs": [
"S0-pristine-standard",
"S1-core-standard",
"S2-core-horizontal",
"S3-pristine-vertical",
"S4-core-vertical",
"S5-pristine-fullscreen",
"S6-core-fullscreen",
"S7-core-hidden-return",
"S8-core-saved"
],
"family_decisions": {
"HISTORICAL_REPLAY": "ACCEPTED",
"STANDARD_S0_S1_S2": "ACCEPTED",
"VERTICAL_S3_S4": "ACCEPTED",
"FULLSCREEN_S5_S6": "ACCEPTED",
"HIDDEN_S7": "ACCEPTED",
"SAVED_S8": "ACCEPTED"
},
"tests": {
"graphics": 20,
"result": "PASS"
},
"runtime": "NOT_RUN",
"reservations_consumed": 0,
"transcription": "Parent materialization of independent read-only reviewer scoped message; not reviewer-authored file or verbatim transcript."
}

View file

@ -0,0 +1,50 @@
{
"review_kind": "INITIAL_BOUNDED_OOM_SOURCE_ONLY",
"reviewer": "/root/delta_oom_review",
"independent": true,
"decision": "ACCEPTED",
"manifest_sha256": "4a6dfda4f61d9c9f5a13588fe7ed7ac6ec6d9720cf2167a82e5f7c39f52d0c35",
"source_manifest_sha256": "59207af208d13c43d0bb0aa3a1669dae697d22d20ba1199ece12817d754d9c1c",
"slice_bindings_sha256": "6c7092197a5055136356f1fad113f213b37dfea189c024eaebd437d2819c592e",
"commands_sha256": "1c867b9a4d9a013a78ebda7ad3e5cb24b606480071afde9bbc94d26bf6eed0b5",
"negative_results_sha256": "c67f1d5902224f7d4a2b299d710222e439439389383523d0dca2a4b9217f5384",
"fixture_files_verified": 15,
"full_prototype_sources_verified": 9,
"exact_slice_bindings_verified": 25,
"findings": [],
"CORE_OOM_C_TESTS": "PARTIALLY_FEASIBLE",
"CORE_OOM_ALTERNATIVES_REVIEW": "ACCEPTED",
"CORE_PRODUCTION_OOM_POLICY": "UNDECIDED",
"A_status": "OOM_ABORT_EXPERIMENTAL",
"production_availability": "PRODUCTION_AVAILABILITY_NOT_APPROVED",
"independent_reproduction": {
"lab": "/tmp/swayfx-oom-independent-review-vb_o8bvr",
"run_exit": 0,
"commands_expected": 40,
"commands_pass": 40,
"negative_exit": 0,
"mutants_rejected": 6,
"mutant_returncodes": [
-6,
99,
-6,
-6,
-6,
-6
],
"sidecar_bytes": {
"A": 56,
"B": 56,
"C": 64
}
},
"native_session_survival": "NOT_PROVEN",
"complete_native_damage": "NOT_PROVEN",
"native_saved_buffer_oom": "NOT_TESTED",
"native_reparent_damage": "NOT_TESTED",
"production_adoption_authorized": false,
"authorized_runs": [],
"compositor_builds": 0,
"runtime": "NOT_RUN",
"transcription": "Faithful structured summary parent-materialized from independent read-only review final; not verbatim."
}

View file

@ -0,0 +1,59 @@
{
"review_kind": "INITIAL_BOUNDED_PREFLIGHT",
"reviewer": "/root/preflight_review",
"independent": true,
"decision": "CORRECTION_REQUIRED",
"baseline": "70954e26f62fb3df25995e31b0c615762ff85a60",
"manifest_sha256": "075724665dde40fdd00571229a2b33490634a01d39b28c97b2a64188386ee7f1",
"manifest_entries_verified": 562,
"manifest_mismatches": [],
"repo_lab_source_mismatches": [],
"authorized_runs": [],
"runtime": "NOT_RUN",
"family_decisions": {
"HISTORICAL_REPLAY": "ACCEPTED",
"STANDARD_S0_S1_S2": "CORRECTION_REQUIRED",
"VERTICAL_S3_S4": "CORRECTION_REQUIRED",
"FULLSCREEN_S5_S6": "CORRECTION_REQUIRED",
"HIDDEN_S7": "CORRECTION_REQUIRED",
"SAVED_S8": "CORRECTION_REQUIRED",
"GPU_OFFLINE_CORRECTION": "ACCEPTED",
"HISTORICAL_NATIVE_AUDIT": "ACCEPTED"
},
"findings": [
{
"id": "INITIAL-P1-01",
"severity": "P1",
"symbols": [
"main",
"Session.close_client",
"Session.cleanup"
],
"evidence": "Real cleanup reproduced status PASS and scientific_cleanup PASS despite failure ValueError: client SHM release incomplete/unobserved. main retains successful measurement status when normal_remaining raises; exited invalid client is skipped by cleanup.",
"required_repair": "Failure must prevent overall success; archived explicit closure validation must be retained and qualify scientific cleanup; actual main/normal_remaining/cleanup adversarial regression."
}
],
"tests": {
"graphics": 15,
"gpu": 36,
"historical_entries": 4688,
"historical_files": 4250,
"native_receipt": "PASS"
},
"transcription": "Faithful structured summary materialized by parent from independent read-only reviewer final response; not a verbatim transcript or reviewer-authored file.",
"gpu_limits": {
"plane_metadata_correction": "PASS",
"GPU_MODIFIER_AWARE_OFFLINE": "INCONCLUSIVE",
"GPU_DCC_DESIGN_REVIEW": "NOT_PROVEN",
"GPU_DCC_RUNTIME": "NOT_RUN",
"read_success": "NOT_PROVEN",
"GL_ERROR_QUEUE": "UNOBSERVED",
"real_epoch_rejection": "effective halfpixel"
},
"source_bindings": {
"graphics/run.py": "63396e8265cdbf2b89625792460b383cf9a4e8bd9c429e25d0a4f8c0736a2adf",
"graphics/engine.py": "1336a83e521c47a1800b21185ef1dcf340d2a543bbecb4a3552879469f04294c",
"graphics/test_recovery.py": "4862d08ff25644f4c526ef1f045e3bb49ef2efea50b4ab590cbb68b7ab5f771e",
"audit.py": "eee5163e4aa13720fbfc18764f198ef592ded1d544dd01de49e545c33ee15966"
}
}

View file

@ -0,0 +1,52 @@
{
"status": "publication-format normalization; executed LAB fixtures unchanged",
"files": [
{
"public_path": "tooling/probes/step20c-core-recovery/graphics/cases.py",
"executed_path": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/graphics/cases.py",
"executed_sha256": "95652d9b9499c9e7bed77aa6600a6fb99d5bfb4e1b8d465d1c76e98b49778977",
"public_before_sha256": "95652d9b9499c9e7bed77aa6600a6fb99d5bfb4e1b8d465d1c76e98b49778977",
"public_after_sha256": "7ef309e39b7ac814246a0822428334cfce35d0b5c641ca7b220b0f4475709e4b",
"before_length": 11311,
"after_length": 11310,
"reconstruction_sha256": "95652d9b9499c9e7bed77aa6600a6fb99d5bfb4e1b8d465d1c76e98b49778977",
"reconstruction_matches_executed": true,
"ast_dump_equal_with_attributes": true,
"compile_marshal_equal_same_filename": true,
"original_final_suffix_escaped": "\\n\\n",
"append_suffix": "\n"
},
{
"public_path": "tooling/probes/step20c-core-recovery/graphics/ipc_predicates.py",
"executed_path": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/graphics/ipc_predicates.py",
"executed_sha256": "6d5a25c225685adf24978a8bd9b4a6de44a369af6620d10db95accacc892b6f8",
"public_before_sha256": "6d5a25c225685adf24978a8bd9b4a6de44a369af6620d10db95accacc892b6f8",
"public_after_sha256": "5354ba53e1b3f8d0026f4475beebee58d093a81db7d26304cad7085ae3efd352",
"before_length": 691,
"after_length": 689,
"reconstruction_sha256": "6d5a25c225685adf24978a8bd9b4a6de44a369af6620d10db95accacc892b6f8",
"reconstruction_matches_executed": true,
"ast_dump_equal_with_attributes": true,
"compile_marshal_equal_same_filename": true,
"original_final_suffix_escaped": "\\n\\n\\n",
"append_suffix": "\n\n"
},
{
"public_path": "tooling/probes/step20c-core-recovery/graphics/predicates.py",
"executed_path": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/graphics/predicates.py",
"executed_sha256": "d1d3505d9553444a3c6ec4b6c47eed817adac0451db1518ee15e33e0683c12fb",
"public_before_sha256": "d1d3505d9553444a3c6ec4b6c47eed817adac0451db1518ee15e33e0683c12fb",
"public_after_sha256": "222a94764bfe6d76f0b7bcdab4b28b4d90cccb479af49b87e778813f0bdc3a71",
"before_length": 7711,
"after_length": 7710,
"reconstruction_sha256": "d1d3505d9553444a3c6ec4b6c47eed817adac0451db1518ee15e33e0683c12fb",
"reconstruction_matches_executed": true,
"ast_dump_equal_with_attributes": true,
"compile_marshal_equal_same_filename": true,
"original_final_suffix_escaped": "\\n\\n",
"append_suffix": "\n"
}
],
"reconstruction_rule": "Append append_suffix (JSON string decoded as ASCII) to public file bytes; verify executed_sha256. original_final_suffix_escaped describes the entire original newline suffix, not the bytes removed.",
"initial_mechanic_receipt_sha256": "ce7ef7860afed6515a2277fb76f5a5d790a0066641f3ca0b5770ec5b8157c45a"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
{
"review_kind": "BOUNDED_RUNTIME_EVIDENCE",
"reviewer": "/root/runtime_evidence_review",
"decision": "ACCEPTED",
"independent": true,
"manifest_sha256": "ed0c2be50d067f273e884c338b607a47d066d3a290432df99a513c620346fbc7",
"manifest_entries_verified": 565,
"manifest_mismatches": [],
"captures_verified": 102,
"new_runs": 9,
"reserved_capture_slots": 120,
"runner_codes": [
0,
0,
0,
0,
0,
0,
0,
0,
0
],
"terminal_ipc_codes": [
1,
1,
1,
1,
1,
1,
1,
1,
1
],
"scientific_cleanup": "PASS",
"mechanical_cleanup": "PASS",
"findings": [],
"limitations": [
"Fullscreen popups wholly H1, noncomparable ancestor child counts2/1 and owner heights275/575; cross-outputNOTEXERCISED",
"Vertical raw geometry.crossing defaultsX0; executed Y600predicate and actualYbounds505..675/585..715 establish crossing",
"CORE lacks native PI/atomic generation/buffer identity; submitted damage extents plus sampled RGB only",
"No universal grab click-through policy"
],
"markers": {
"CORE_STANDARD_CONTROL": "PARTIALLY_FEASIBLE",
"CORE_HORIZONTAL_RETURN": "PARTIALLY_FEASIBLE",
"CORE_VERTICAL_CROSSING": "PARTIALLY_FEASIBLE",
"CORE_HIDDEN_RETURN": "PARTIALLY_FEASIBLE",
"CORE_SAVED_EPOCH": "PARTIALLY_FEASIBLE",
"CORE_SAMPLED_DAMAGE": "PARTIALLY_FEASIBLE",
"CORE_FULLSCREEN_COMPARISON": "INCONCLUSIVE",
"CORE_NATIVE_ORDERING": "INCONCLUSIVE",
"CORE_BUFFER_IDENTITY": "NOT_PROVEN",
"CORE_ATOMIC_GENERATION": "NOT_PROVEN"
},
"transcription": "Parent-written faithful structured summary of independent read-only reviewer final; not verbatim or reviewer-authored file."
}

View file

@ -0,0 +1,86 @@
# STEP20C GPU DCC recovery — EXPERIMENTAL, offline only
This versioned validator copies the executed historical `validate.py`,
`analysis.py` and `test_fixture.py`; exact parent paths/hashes are recorded in
`provenance.json`, and `parent-delta.patch` records their complete source delta.
The historical runner remains byte-identical and is deliberately not copied:
this scope has no admissible runtime acquisition gate or launcher. The two
runner-dependent controller tests are omitted from the copied test module;
31 inherited validator/model tests and five actual-G1 test methods run offline.
Generated reports, logs and payload remain in the new private LAB.
## Exact bounded contract
The local authority is `/usr/include/libdrm/drm_fourcc.h`, SHA256
`992f33804acda60b0b46f1a272942c3592484ea3217d469239f1cb12249de2eb`.
Its AMD section assigns the main image to plane 0 and DCC to plane 1 when
DCC is enabled without RETILE. The same meaning was independently checked
against the [primary Linux UAPI header](https://linux.googlesource.com/linux/kernel/git/torvalds/linux/+/refs/heads/master/include/uapi/drm/drm_fourcc.h).
That moving upstream reference corroborates the semantics; the local SHA pins
this fixture's actual source authority. The exact allowlisted modifier
`020000001046bb04` has vendor AMD=2, tile-version GFX11=4, tile=27,
DCC=1, RETILE=0 and PIPE_ALIGN=1. Other nonzero modifiers are rejected.
For the four known packed 32-bit RGB formats, the image plane must have a
stride of at least `width*4`, divisible by four. The auxiliary DCC pitch is
nonzero uint32; it is not an image stride. Offsets and arithmetic are bounded
and the fixture restricts the metadata to image-before-auxiliary offsets.
This latter restriction is a bounded sanity filter, **not a universal modifier
layout rule**. Exported FD identities and allocation lengths are not observed.
No DCC alignment, tiled physical size, auxiliary extent or allocation bounds
are proved. An accepted plane record returns those explicit limitations.
The linear one-plane case is a synthetic regression control only; no new
non-DCC GPU run or qualification is claimed.
## Actual G1 replay and second blocker
The replay reads only immutable epoch1 metadata/arm/payload and verifies their
fixed hashes. Nine native reads, offsets/ROIs, hashes, attachment channels,
context and all nine saved/restored GL/PACK state fields validate. The added
G1 envelope checks all 11 native writes and two copies, source/destination,
texture unit, sampler/matrix types, composition domains and native order.
A successful envelope is only structural evidence: it never replaces the
strict `validate_epoch()` result.
After the plane repair, **the real epoch still rejects `effective halfpixel`**.
For stages 0–2, `glGetUniformfv` published bits `[983818240,987398144]`, while
float32 source expressions predict `[983815946,987395086]`. For stages 3–5,
observed bits are `[967041024,970620928]` versus `[967038730,970617870]`.
Both shaders declare `precision mediump float`; the private hook queries
active uniforms with `glGetUniformfv` then preserves their bits. Quantization
is a possible explanation, not a proven diagnosis of these readings. No
post-observation tolerance or shader change is introduced, and the float64
ideal model is not upgraded into a GPU arithmetic model.
The hook itself explicitly says its alpha sentinel is not proof of GL read
success or restoration of the GL error queue. `GL_ERROR_QUEUE=UNOBSERVED` and
`read_success=NOT_PROVEN` remain immutable. Fixing those acquisition limits
would require a separately authorized diagnostic contract/build; this ticket
allows zero new compositor/GPU builds. Hence `GPU_DCC_RUNTIME=NOT_RUN`, no
runtime reservation and no new GPU runner. `GPU_MODIFIER_AWARE_OFFLINE` is
`INCONCLUSIVE` for the complete gate even though the isolated plane correction
and adversarial tests pass. Independent review remains pending.
Only A exists: no B or A-return is acquired. The sampled 16×16 U0/cache RGBA
bytes are exact; cache/presubmit differ. Neither observation establishes the
cause of a whole-output inequality, physical DCC correctness, precision loss,
or an A/B/A difference. `GPU_WHOLE_FIELD_CAUSALITY=INCONCLUSIVE`.
The original G1 outcome, exit1, cleanup limits and INCONCLUSIVE remain unchanged.
## Reproduce without starting anything
From repository root (historical private evidence must still exist):
```sh
PYTHONDONTWRITEBYTECODE=1 python -m unittest discover \
-s tooling/probes/step20c-core-recovery/gpu-field -p 'test_*.py' -v
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-core-recovery/gpu-field/replay.py
```
Mutation tests cover unknown formats/modifiers, wrong plane count, image/aux
pitches, duplicated/overflowing offsets, missing fields, truncated/corrupt
samples, read validity/sentinel, GL restoration/context/attachments, ROI/hash,
missing copies/writes, source aliases, samplers and duplicate uniforms. The
unchanged inherited suite additionally covers all nine restoration fields,
clear/external write/lifetime events, blur order, region holes, ping-pong
continuity, reconstruction and ideal-model negative predictions.

View file

@ -0,0 +1,96 @@
"""Pure read-only native-field float64 ideal analysis. No fitted tolerances.
WHY/CONTRACT/INVARIANT: the ideal bilinear coordinates are a source-derived
model, never a measurement of mediump interpolation or linked GPU arithmetic.
"""
import numpy as np
from validate import need, FRESH, reconstruct
def field(epoch,p):
s=epoch['samples'][p];x,y,w,h=s['roi']
return np.frombuffer(epoch['raw'][s['offset']:s['offset']+s['bytes']],dtype=np.uint8).reshape(h,w,4)
def deltas(a,b):
need(a.shape==b.shape,'homologous field dimensions')
d=b.astype(np.int16)-a.astype(np.int16);changed=np.any(d!=0,axis=2);yy,xx=np.where(changed)
return dict(pixels=int(changed.sum()),bbox=[int(xx.min()),int(yy.min()),int(xx.max()+1),int(yy.max()+1)] if len(xx) else None,
min=d.min(axis=(0,1)).tolist(),max=d.max(axis=(0,1)).tolist(),maxabs=int(np.abs(d).max()),
signed_histograms=[{str(int(k)):int(v) for k,v in zip(*np.unique(d[:,:,c],return_counts=True))} for c in range(4)])
def bilinear(src,x,y):
# Physical texture edge clamp, never clamp to its fresh domain.
x=np.clip(x,0,src.shape[1]-1);y=np.clip(y,0,src.shape[0]-1)
x0=np.floor(x).astype(int);y0=np.floor(y).astype(int);x1=np.minimum(x0+1,src.shape[1]-1);y1=np.minimum(y0+1,src.shape[0]-1)
tx=(x-x0)[...,None];ty=(y-y0)[...,None]
return src[y0,x0]*(1-tx)*(1-ty)+src[y0,x1]*tx*(1-ty)+src[y1,x0]*(1-tx)*ty+src[y1,x1]*tx*ty
def ideal(src,w,h,down,fresh):
src=src.astype(np.float64);yy,xx=np.mgrid[:h,:w]
if down:
x,y=2*xx+.5,2*yy+.5;taps=[(0,0,4/8),(-5,-5,1/8),(5,5,1/8),(5,-5,1/8),(-5,5,1/8)]
else:
x,y=xx/2-.25,yy/2-.25;taps=[(-2.5,0,1/12),(2.5,0,1/12),(0,-2.5,1/12),(0,2.5,1/12),(-1.25,-1.25,2/12),(1.25,1.25,2/12),(1.25,-1.25,2/12),(-1.25,1.25,2/12)]
mask=np.zeros(src.shape[:2],dtype=np.float64);mask[:fresh[1],:fresh[0]]=1
inside=src*mask[...,None];outside=src*(1-mask)[...,None]
a=np.zeros((h,w,4));b=np.zeros_like(a)
for dx,dy,weight in taps:a+=bilinear(inside,x+dx,y+dy)*weight;b+=bilinear(outside,x+dx,y+dy)*weight
return a+b,a,b
def residual_stats(d):
return dict(min=d.min(axis=(0,1)).tolist(),max=d.max(axis=(0,1)).tolist(),maxabs=float(np.abs(d).max()),nonzero_pixels=int(np.any(d!=0,axis=2).sum()))
def composition(rgb,coefficient=119):
need(coefficient==119,'prospective composition coefficient')
return np.floor(np.array((68,32,16))+rgb[:,:,:3].astype(np.float64)*(119/255)+.5)
def whole_hypothesis(epochs):
a,b=epochs[0],epochs[2];stats={}
for p in range(7):
aa,bb=field(a,p),field(b,p);fresh=FRESH[p];stats[str(p)]=dict(full=deltas(aa,bb),fresh=deltas(aa[:fresh[1],:fresh[0]],bb[:fresh[1],:fresh[0]]))
first=next((p for p in range(1,7) if stats[str(p)]['fresh']['pixels']),None)
return dict(SOURCE_MAIN_WHOLE_EQUALITY='EXACT' if not stats['0']['full']['pixels'] else 'REFUTED',
FIRST_FRESH_DIVERGENCE_WHOLE=first,H_FRONTIERE_CHAMP_ENTIER='COMPATIBLE_EXACT_PREDICTIONS' if not any(stats[str(p)]['fresh']['pixels'] for p in (0,1,2)) and first==3 else 'REFUTED',fields=stats)
def ideal_residuals(epochs,output=None):
results=[];native=[]
for e in (epochs[0],epochs[2]):
r=reconstruct(e);arr=lambda b:np.frombuffer(b,dtype=np.uint8).reshape(600,800,4)
srcs=[field(e,0),field(e,1),field(e,2),arr(r['P2']),arr(r['Q2']),arr(r['P1'])]
predicted=[];measured=[];freshparts=[];tails=[]
for i,src in enumerate(srcs):
w,h=FRESH[i+1];v,a,b=ideal(src,w,h,i<3,FRESH[i]);dst=field(e,i+1)[:h,:w]
predicted.append(v);measured.append(dst);freshparts.append(a);tails.append(b)
native.append((predicted,measured,freshparts,tails))
for i in range(6):
pa,ma,fa,ta=[native[0][k][i] for k in range(4)];pb,mb,fb,tb=[native[1][k][i] for k in range(4)]
pd=pb-pa;md=mb.astype(float)-ma.astype(float);res=md-pd
results.append(dict(stage=i,absolute_A=residual_stats(ma-pa),absolute_Areturn=residual_stats(mb-pb),differential=residual_stats(res),fresh_difference=residual_stats(fb-fa),outside_difference=residual_stats(tb-ta),ideal_source_exact_but_destination_different=int((np.all(pd==0,axis=2)&np.any(md!=0,axis=2)).sum())))
if output:
np.savez(output/f'stage-{i}-ideal-and-residuals.npz',ideal_A=pa,ideal_Areturn=pb,absolute_A=ma-pa,absolute_Areturn=mb-pb,differential=res,fresh=fb-fa,outside=tb-ta)
return dict(model='SOURCE_DERIVED_FLOAT64_IDEAL_NO_TOLERANCE',effective_precision='NOT_MEASURED',residuals=results)
def glass_relation(epochs,run,states):
from PIL import Image
run=__import__('pathlib').Path(run)
pngs=[np.array(Image.open(run/(tag+'-H1.png')).convert('RGB')) for tag in ('optimized-A','optimized-B','optimized-A-return')]
need(all(p.shape==(600,800,3) for p in pngs),'whole H1 PNG dimensions')
aa=pngs[0];ar=pngs[2];changed=np.any(aa!=ar,axis=2)
# Fixed prospective H1 domain, every pixel accounted for; unknown predicates
# stay unknown. CORE IPC pending geometry cannot prove atomic applied order.
relation=[]
for e,png in zip((epochs[0],epochs[2]),(aa,ar)):
u=field(e,6);pred=composition(u)
direct=png.astype(float)-pred
flipped=png.astype(float)-pred[::-1]
relation.append(dict(raw_direct_all_H1_residual=residual_stats(direct),raw_flip_y_all_H1_residual=residual_stats(flipped),admissibility='NOT_PROVEN',orientation='SOURCE_PROJECTION_PREDICTS_GL_LOW_TO_HIGH; constant-y witness does not prove orientation'))
sampled=[]
for e in epochs:
cache=field(e,7);pre=field(e,8)
if e['rows'][0]['epoch']==2:u=field(e,6)
else:u=field(e,6)[292:308,784:800]
sampled.append(dict(epoch=e['rows'][0]['epoch'],U0_cache_RGBA=deltas(u,cache),cache_presubmit_RGBA=deltas(cache,pre),ideal_glass_presubmit_RGB_residual=residual_stats(pre[:,:,:3].astype(float)-composition(u))))
return dict(WHOLE_VISIBLE_GLASS_RELATION='INCONCLUSIVE',WHOLE_H1_DEFECT_REPRODUCED='EXERCISED' if changed.any() else 'NOT_EXERCISED',
whole_H1_RGBA_equivalent_RGB=dict(pixels=int(changed.sum()),min=(ar.astype(int)-aa).min(axis=(0,1)).tolist(),max=(ar.astype(int)-aa).max(axis=(0,1)).tolist()),
exhaustive_partition=dict(total_pixels=480000,admissible=0,other=0,predicate_unknown=480000,changed_admissible=0,changed_other=0,changed_predicate_unknown=int(changed.sum())),
candidate_geometry=states,all_H1_relation=relation,CACHE_COPY_SAMPLED=sampled,
meaning='Raw formula residuals cover every H1 pixel and do not define admissibility. No precision tolerance or anomaly mask.',GPU_FULL_FIELD='OBSERVED_UNDER_VALIDATOR_GATES',COMPLETE_DAMAGE='NOT_PROVEN',ATOMIC_GENERATION='NOT_PROVEN')

View file

@ -0,0 +1,128 @@
--- historical/validate.py
+++ recovery/validate.py
@@ -1 +1 @@
-"""EXPERIMENTAL G1 runtime validator; importable offline, no launch or writer.
+"""EXPERIMENTAL modifier-aware OFFLINE G1 validator; importable offline, no launch or writer.
@@ -43 +43 @@
- need(o['dimensions']==[800,600] and all(o[k] for k in ('fx','wlr','fbo','rbo')),'existing physical object')
+ need(o['dimensions']==[800,600] and all(o[k] for k in ('fx','wlr','fbo','rbo','image')) and type(o['tex']) is int and o['tex'] >= 0,'existing physical object')
@@ -46 +46,41 @@
- need(len(o['modifier'])==16 and all(len(p)==2 and p[0]>=800*4 and p[1]>=0 for p in o['planes']),'dmabuf fields')
+ return validate_planes(o)
+
+
+AMD_G1_MODIFIER = '020000001046bb04'
+UINT32_MAX = (1 << 32) - 1
+
+
+def validate_planes(o):
+ """Bounded metadata sanity, not a proof of allocation extent or DCC layout.
+
+ drm_fourcc.h SHA992f3380...: packed RGB has one image plane;
+ AMD DCC without RETILE adds one auxiliary plane. Only the observed AMD
+ modifier is allowlisted. No DCC size/alignment formula is inferred.
+ """
+ modifier = o['modifier']
+ need(isinstance(modifier, str) and len(modifier) == 16
+ and all(c in '0123456789abcdef' for c in modifier), 'modifier encoding')
+ need(modifier in ('0000000000000000', AMD_G1_MODIFIER), 'unknown modifier')
+ planes = o['planes']
+ need(len(planes) == (2 if modifier == AMD_G1_MODIFIER else 1), 'modifier plane count')
+ for p in planes:
+ need(isinstance(p, list) and len(p) == 2
+ and all(type(n) is int and 0 <= n <= UINT32_MAX for n in p),
+ 'pitch/offset uint32 fields')
+ need(p[0] > 0 and p[1] + p[0] <= UINT32_MAX, 'nonzero pitch/offset overflow')
+ pitch, offset = planes[0]
+ need(pitch >= WIDTH * 4 and pitch % 4 == 0, 'packed image stride')
+ image_end = offset + pitch * HEIGHT
+ need(image_end <= UINT32_MAX, 'image extent overflow')
+ if modifier == AMD_G1_MODIFIER:
+ aux_pitch, aux_offset = planes[1]
+ # This fixture only accepts a single-allocation, image-before-aux layout.
+ # Exported FD identity/allocation lengths are absent: this is an explicit
+ # bounded sanity restriction, not a universal DRM modifier requirement.
+ need(aux_offset >= image_end and aux_offset > offset
+ and planes[0] != planes[1], 'auxiliary offset/duplicate sanity')
+ return dict(layout='AMD_GFX11_DCC_NO_RETILE', image_stride='PASS',
+ auxiliary_fields='PLAUSIBLE_ONLY', auxiliary_extent='NOT_PROVEN',
+ allocation_bounds='NOT_PROVEN', fd_identity='NOT_OBSERVED')
+ return dict(layout='LINEAR_PACKED', image_stride='PASS',
+ allocation_bounds='NOT_PROVEN')
@@ -151,0 +192,51 @@
+
+
+def validate_g1_envelope(rows, samples, context):
+ """Additional exact G1 native protocol checks, independent of uniform equality.
+
+ A successful envelope never substitutes for validate_epoch/validate_writes.
+ In particular the observed halfpixel mismatch remains a strict rejection.
+ """
+ writes = [r for r in rows if r['kind'] == 'write']
+ copies = [r for r in rows if r['kind'] == 'copy']
+ need(len(writes) == 11 and len(copies) == 2, 'G1 eleven writes/two copies')
+ need([r['sequence'] for r in writes] == list(range(1, 12)), 'G1 ordered writes')
+ need([r['stage'] for r in writes] == list(range(6)) + [-1] * 5, 'G1 stage sequence')
+ for r in writes:
+ need(r['fbo'] == r['read_fbo'] and r['context'] == context
+ and r['viewport'] == [0, 0, WIDTH, HEIGHT], 'G1 draw bindings/context')
+ need(r['texture'] > 0 and r['program'] > 0 and r['color_mask'] == [1]*4
+ and r['stencil'] == r['scissor_enabled'] == 0, 'G1 raster state')
+ need(r['sampler'] in ([9729,9729,33071,33071], [9728,9728,33071,33071]),
+ 'G1 effective sampler')
+ uniforms = r['uniforms']
+ names = [u['name'] for u in uniforms]
+ need(len(names) == len(set(names)) and all(n in names for n in ('tex','proj','tex_proj')),
+ 'G1 unique sampler/matrix uniforms')
+ u = {u['name']:u for u in uniforms}
+ need(u['tex']['type'] == 35678 and u['tex']['value'] == [0], 'G1 sampler texture unit')
+ need(all(u[n]['type'] == 35675 and len(u[n]['value']) == 9 for n in ('proj','tex_proj')),
+ 'G1 matrix type/size')
+ need(all(type(b) is int and 0 <= b <= UINT32_MAX
+ for x in uniforms for b in x['value']), 'G1 uniform bit encoding')
+ if r['stage'] >= 0:
+ need(u['radius']['type'] == 5126 and u['halfpixel']['type'] == 35664
+ and len(u['halfpixel']['value']) == 2, 'G1 blur uniform types')
+ else:
+ need(u['alpha']['value'] == [1065353216] and u['discard_transparent']['value'] == [0],
+ 'G1 copy/composition uniforms')
+ for c, seq, src in zip(copies, (7,8), (samples[6]['object'], samples[0]['object'])):
+ validate_object(c['src'], 'main')
+ validate_object(c['dst'], 'main')
+ w = writes[seq-1]
+ need(object_identity(c['src']) == object_identity(src)
+ and w['fbo'] == c['dst']['fbo'] and w['texture'] == c['src']['tex']
+ and w['region'] == c['region'] == [[0,0,WIDTH,HEIGHT]], 'G1 copy continuity')
+ need(rows.index(w) < rows.index(c), 'G1 copy follows raster write')
+ need(rows.index(copies[0]) < rows.index(samples[7]) < rows.index(writes[7])
+ and rows.index(writes[-1]) < rows.index(samples[8]), 'G1 copy/sample order')
+ need(writes[8]['region'] == [[0,0,480,600]]
+ and writes[9]['region'] == writes[10]['region'] == [[480,0,800,600]]
+ and all(w['fbo'] == samples[8]['object']['fbo'] for w in writes[8:]),
+ 'G1 final composition regions/target')
+ return dict(writes=11, copies=2, status='STRUCTURAL_ONLY')
--- historical/test_fixture.py
+++ recovery/test_fixture.py
@@ -108,22 +107,0 @@
-class Controller(unittest.TestCase):
- def test_durable_one_shot_arm(self):
- import runner
- with tempfile.TemporaryDirectory() as tmp:
- d=Path(tmp)
- runner.arm(d,1,'A');self.assertEqual((d/'epoch-1.arm').read_bytes(),b'A\n')
- with self.assertRaises(v.Rejected):runner.arm(d,1,'A')
- with self.assertRaises(v.Rejected):runner.arm(d,2,'B')
- with self.assertRaises(v.Rejected):runner.arm(d,4,'A')
- with self.assertRaises(v.Rejected):runner.arm(d,2,'A')
- def test_real_SHM_validator_mismatch(self):
- import runner
- with tempfile.TemporaryDirectory() as tmp:
- d=Path(tmp);log=[]
- for i,colors in enumerate(('00101010,00202020','00303030,00404040','00101010,00202020'),1):
- cs=[int(x,16) for x in colors.split(',')];raw=b''.join(struct.pack('<I',cs[x//8%2]) for x in range(800))*600
- p=d/f'background-{i}.shm';p.write_bytes(raw)
- log.append(f'SHM_SOURCE mode=background path={p} bytes={len(raw)} stride=3200 format=XRGB8888 colors={colors}')
- self.assertTrue(runner.fx_validators.sources(d,'\n'.join(log))['ABA_byte_exact'])
- p=d/'background-3.shm';bad=bytearray(p.read_bytes());bad[0]^=1;p.write_bytes(bad)
- with self.assertRaises(ValueError):runner.fx_validators.sources(d,'\n'.join(log))
-

View file

@ -0,0 +1,23 @@
{
"status": "EXPERIMENTAL_OFFLINE_ONLY",
"parents": {
"validate.py": {
"parent": "/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field/validate.py",
"sha256": "158a820a2f170cabce37f7b8965ce23c3887cfbced32fecaea4914ea6f423ef7"
},
"analysis.py": {
"parent": "/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field/analysis.py",
"sha256": "5341909a7fa4010f52bfc909cd76f2288cfa5c4da9126b7258c47b4ad987fa3d"
},
"test_fixture.py": {
"parent": "/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field/test_fixture.py",
"sha256": "824c9fa022871076bef8b53357f6a25394706180a3eb29b1a31e993d46ef9ca1"
}
},
"historical_runner": {
"path": "/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field/runner.py",
"sha256": "60989c77cef0656d52607869f8b37bb4071335dbbed62b6fed1cd86ee2bed3d2",
"copied": false,
"reason": "Runtime forbidden by unresolved GL observability; no launcher needed."
}
}

View file

@ -0,0 +1,78 @@
"""Read-only G1 epoch1 audit. No compositor, GPU call, runtime writer or launcher."""
import argparse
import json
import struct
from pathlib import Path
import validate as v
HISTORICAL = Path('/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field')
HASHES = {'meta':'c8ca51bca694db9e21471356a4260064ed81ab7013a88d4edb4b8572044a825c',
'rgba':'a6dc7fd873e1ab7e52e04625bacb3251ba8e1aad2e766811efebde7ae7932a99',
'arm':'06f961b802bc46ee168555f066d28f4f0e9afdf3f88174c1ee6f9de004fc30a0'}
def load(directory=HISTORICAL/'runtime'):
directory = Path(directory)
data = {ext:(directory/f'epoch-1.{ext}').read_bytes() for ext in HASHES}
for ext, digest in HASHES.items():
v.need(v.sha(data[ext]) == digest, 'immutable historical hash '+ext)
rows = [json.loads(line) for line in data['meta'].splitlines()]
return rows, data['rgba']
def sample_protocol(rows, raw):
samples = [r for r in rows if r['kind'] == 'sample']
v.need([s['point'] for s in samples] == list(range(9)), 'nine ordered samples')
offset = 0
for sample in samples:
offset = v.validate_sample(sample, raw, 1, rows[0]['context'], offset)
v.need(offset == len(raw) == rows[-1]['bytes'] == 8312048
and v.sha(raw) == rows[-1]['sha256'], 'whole readback extent/hash')
v.need(all(samples[p]['after_ns'] <= samples[p+1]['before_ns'] for p in range(8)),
'sample native ordering')
return samples
def audit(directory=HISTORICAL/'runtime'):
rows, raw = load(directory)
samples = sample_protocol(rows, raw)
envelope = v.validate_g1_envelope(rows, samples, rows[0]['context'])
try:
v.validate_epoch(directory, 1)
except v.Rejected as error:
global_result = {'status':'REJECTED','reason':str(error)}
else:
global_result = {'status':'STRUCTURAL_ONLY','scientific_acceptance':False}
uniform_bits = []
for r in (r for r in rows if r['kind'] == 'write' and r['stage'] >= 0):
u = {u['name']:u for u in r['uniforms']}
expected = (1/800,1/600) if r['stage'] < 3 else (1/3200,1/2400)
uniform_bits.append(dict(stage=r['stage'], observed=u['halfpixel']['value'],
expected_float32=[struct.unpack('I',struct.pack('f',x))[0] for x in expected],
observed_float=[struct.unpack('f',struct.pack('I',x))[0] for x in u['halfpixel']['value']]))
def field(p):
s=samples[p]
return raw[s['offset']:s['offset']+s['bytes']]
u0 = field(6)
roi = b''.join(u0[(y*800+784)*4:(y*800+800)*4] for y in range(292,308))
return dict(status='EXPERIMENTAL_OFFLINE_ONLY', historical_status='INCONCLUSIVE_UNCHANGED',
historical_hashes=HASHES, samples=9, raw_bytes=len(raw),sample_protocol='PASS',
native_envelope=envelope, epoch_validator=global_result,
plane_evidence=v.validate_object(samples[1]['object']), halfpixel=uniform_bits,
sampled_relations=dict(U0_cache_RGBA_exact=roi==field(7),cache_presubmit_RGBA_exact=field(7)==field(8)),
GL_ERROR_QUEUE=rows[0]['GL_ERROR_QUEUE'],read_success=rows[0]['read_success'],
GPU_MODIFIER_AWARE_OFFLINE='INCONCLUSIVE',plane_metadata_correction='PASS',
GPU_DCC_DESIGN_REVIEW='NOT_PROVEN',GPU_DCC_RUNTIME='NOT_RUN',
GPU_WHOLE_FIELD_CAUSALITY='INCONCLUSIVE',epochs_acquired=1,
causal_limit='No B/A-return or whole-field output; no fitted precision tolerance; no physical DCC decoding.',
runtime_prerequisites=['Authorized new diagnostic binary with defensible read-success and GL error observability contract',
'Resolve effective uniform precision validation from primary specification without fitted tolerances',
'Allocation/FD/auxiliary bounds observability or explicit limited contract',
'Independent review of exact new source and acquisition preflight before any run reservation'])
if __name__ == '__main__':
parser=argparse.ArgumentParser()
parser.add_argument('--historical-runtime',type=Path,default=HISTORICAL/'runtime')
args=parser.parse_args()
print(json.dumps(audit(args.historical_runtime),indent=2))

View file

@ -0,0 +1,124 @@
"""Offline tests importing the SAME validator/model called by the runner."""
import copy,json,struct,tempfile,unittest
from pathlib import Path
import numpy as np
import validate as v
import analysis as a
def bits(x):return struct.unpack('I',struct.pack('f',x))[0]
def obj(n,cache=False):
return dict(fx=f'0x{n}',wlr=f'0x{n+100}',fbo=n,rbo=n+10,tex=n+20,image=f'img{n}',dimensions=[800,600],dmabuf_available=True,drm_format=v.XBGR if cache else v.ABGR,modifier='0000000000000000',dmabuf_size=[800,600],planes=[[3200,0]])
def fixture(e):
objects=[obj(1),obj(2),obj(3),obj(2),obj(3),obj(2),obj(3),obj(4,True),obj(1)]
rows=[dict(kind='begin',epoch=e,label='B' if e==2 else 'A',context='ctx',output='HEADLESS-1',pass_=e,scale=1,transform=0,radius=5,passes=3,strength=1,effects_neutral=True,GL_ERROR_QUEUE='UNOBSERVED',read_success='NOT_PROVEN',rows='GL_LOW_TO_HIGH',strings=['vendor','renderer','OpenGL ES 3.2','ES GLSL'],precision=[[0]*5]*6)]
rows[0]['pass']=rows[0].pop('pass_');raw=bytearray()
for p in range(9):
if 1<=p<=6:
uniforms=[dict(name=n,type=5126,value=[bits(x) for x in vals]) for n,vals in [('radius',[5]),('halfpixel',[1/800,1/600] if p<=3 else [1/3200,1/2400]),('proj',[1]*9),('tex_proj',[1]*9)]]
rows.append(dict(kind='write',sequence=p,stage=p-1,fbo=objects[p]['fbo'],program=7,read_fbo=1,texture=objects[p-1]['tex'],context='ctx',box=[0,0,800,600],region=[[0,0,*v.FRESH[p]]],viewport=[0,0,800,600],scissor=[0,0,0,0],scissor_enabled=0,blend=0,stencil=0,dither=1,color_mask=[1]*4,src=objects[p-1],dst=objects[p],sampler=[9729,9729,33071,33071],uniforms=uniforms))
if p==7:
rows.append(dict(kind='write',sequence=7,stage=-1,fbo=4,region=[[0,0,800,600]]))
rows.append(dict(kind='copy',src=objects[6],dst=objects[7],region=[[0,0,800,600]]))
x,y,w,h=v.plan(e,p)
yy,xx=np.mgrid[:h,:w];data=np.stack([(xx+x+p*13)%199,(yy+y+p*17)%199,(xx+yy+p*23)%199,np.full((h,w),255)],axis=2).astype('uint8').tobytes()
rows.append(dict(kind='sample',point=p,name=v.NAMES[p],roi=[x,y,w,h],fresh=list(v.FRESH[p]),offset=len(raw),bytes=len(data),sha256=v.sha(data),object=objects[p],before=[1,2,3,4,4,0,0,0,36064],after=[1,2,3,4,4,0,0,0,36064],context_before='ctx',context_after='ctx',target_read_buffer=36064,attachment=[36161,objects[p]['rbo'],35863,9729,8,8,8,0 if p==7 else 8],renderbuffer=[800,600,32856],before_ns=e*10000+p*10+1,after_ns=e*10000+p*10+2,fbo_status=36053,sentinel_alpha_written=True,valid=True));raw.extend(data)
rows.append(dict(kind='end',epoch=e,points=9,stages=6,writes=7,bytes=len(raw),total_bytes=sum(v.plan(k,p)[2]*v.plan(k,p)[3]*4 for k in range(1,e+1) for p in range(9)),sha256=v.sha(raw)))
return rows,bytes(raw)
class RuntimeValidator(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp=tempfile.TemporaryDirectory();cls.d=Path(cls.temp.name);cls.base={}
for e in (1,2,3):
rows,raw=fixture(e);cls.base[e]=rows;(cls.d/f'epoch-{e}.rgba').write_bytes(raw);(cls.d/f'epoch-{e}.rgba').chmod(0o600)
(cls.d/f'epoch-{e}.arm').write_text(('B' if e==2 else 'A')+'\n');(cls.d/f'epoch-{e}.arm').chmod(0o600)
cls.reset()
@classmethod
def reset(cls):
for e,rows in cls.base.items():
(cls.d/f'epoch-{e}.meta').write_text(''.join(json.dumps(r)+'\n' for r in rows));(cls.d/f'epoch-{e}.meta').chmod(0o600)
@classmethod
def tearDownClass(cls):cls.temp.cleanup()
def tearDown(self):self.reset()
def mutate(self,fn):
rows=copy.deepcopy(self.base[1]);fn(rows);(self.d/'epoch-1.meta').write_text(''.join(json.dumps(r)+'\n' for r in rows))
with self.assertRaises((v.Rejected,KeyError,IndexError)):v.validate_epoch(self.d,1)
def test_real_validator_accepts(self):self.assertEqual(len(v.validate(self.d)),3)
def test_arm_missing_invalid(self):
p=self.d/'epoch-1.arm';data=p.read_bytes()
try:
for bad in (b'',b'B\n',b'A\nextra'):
p.write_bytes(bad)
with self.assertRaises(v.Rejected):v.validate_epoch(self.d,1)
finally:p.write_bytes(data)
def test_metadata_mode(self):
p=self.d/'epoch-1.meta';p.chmod(0o644)
with self.assertRaises(v.Rejected):v.validate_epoch(self.d,1)
def test_budget(self):self.assertEqual(v.read_plan(),dict(reads=27,bytes=16633312))
def test_invalid_fourth(self):
with self.assertRaises(v.Rejected):v.plan(4,0)
def test_restore_each_field(self):
for k in range(9):
with self.subTest(k=k):self.mutate(lambda r,k=k:r[1]['after'].__setitem__(k,777))
def test_context(self):self.mutate(lambda r:r[1].update(context_after='other'))
def test_bad_format_channels(self):
for k,value in ((0,5890),(1,99),(2,5126),(3,35904),(4,10),(5,0),(6,5),(7,0)):
with self.subTest(k=k):self.mutate(lambda r,k=k,value=value:r[1]['attachment'].__setitem__(k,value))
def test_bad_dmabuf(self):self.mutate(lambda r:r[1]['object'].update(drm_format=0))
def test_rbo_size(self):self.mutate(lambda r:r[1]['renderbuffer'].__setitem__(0,799))
def test_readbuffer(self):self.mutate(lambda r:r[1].update(target_read_buffer=0))
def test_truncated_and_corrupt(self):
rp=self.d/'epoch-1.rgba';old=rp.read_bytes()
try:
for bad in (old[:-1],bytes([old[0]^1])+old[1:]):
rp.write_bytes(bad)
with self.assertRaises(v.Rejected):v.validate_epoch(self.d,1)
finally:rp.write_bytes(old)
def test_incomplete_reordered_extra(self):
for fn in (lambda r:r.pop(),lambda r:r.__setitem__(1,r[3]),lambda r:r.insert(3,copy.deepcopy(r[2]))):self.mutate(fn)
def test_admissibility(self):
for k,val in [('epoch',4),('label','B'),('output','HEADLESS-2'),('scale',1.25),('transform',1),('radius',4),('strength',.5),('effects_neutral',False)]:
with self.subTest(k=k):self.mutate(lambda r,k=k,val=val:r[0].__setitem__(k,val))
def test_sentinel(self):self.mutate(lambda r:r[1].update(sentinel_alpha_written=False))
def test_roi_outside(self):self.mutate(lambda r:r[1]['roi'].__setitem__(0,800))
def test_budget_offset(self):self.mutate(lambda r:r[-1].update(total_bytes=v.LIMIT+1))
def test_region_hole_same_extents(self):self.mutate(lambda r:r[2].update(region=[[0,0,400,149],[0,150,400,300]]))
def test_alias_source_fbo(self):self.mutate(lambda r:r[2]['src'].update(fbo=r[2]['dst']['fbo']))
def test_replaced_buffer(self):self.mutate(lambda r:r[3]['object'].update(wlr='replaced'))
def test_color_clear(self):self.mutate(lambda r:r.insert(-1,dict(kind='clear',mask=16384,fbo=2)))
def test_external_write(self):
def fn(r):r.insert(-1,dict(kind='write',stage=-1,sequence=8,fbo=2,region=[[0,0,1,1]]));r[-1]['writes']=8
self.mutate(fn)
def test_unknown_destroy(self):self.mutate(lambda r:r.insert(-1,dict(kind='destroy',fbo=2)))
def test_missing_draw(self):self.mutate(lambda r:r.pop(2))
def test_exact_reconstruction(self):
e=v.validate_epoch(self.d,1);r=v.reconstruct(e)
p2=np.frombuffer(r['P2'],dtype='uint8').reshape(600,800,4);q2=np.frombuffer(r['Q2'],dtype='uint8').reshape(600,800,4);p1=np.frombuffer(r['P1'],dtype='uint8').reshape(600,800,4)
np.testing.assert_array_equal(p2[:75,:100],a.field(e,3));np.testing.assert_array_equal(p2[75:],a.field(e,1)[75:]);np.testing.assert_array_equal(p2[:75,100:],a.field(e,1)[:75,100:])
np.testing.assert_array_equal(q2[:150,:200],a.field(e,4));np.testing.assert_array_equal(q2[150:],a.field(e,2)[150:]);np.testing.assert_array_equal(p1[:300,:400],a.field(e,5));np.testing.assert_array_equal(p1[300:],p2[300:]);np.testing.assert_array_equal(p1[:300,400:],p2[:300,400:])
def test_B_cannot_reconstruct(self):
with self.assertRaises(v.Rejected):v.reconstruct(v.validate_epoch(self.d,2))
def test_exact_predictions_mutated(self):
epochs=v.validate(self.d)
for p in (0,1,2):
e=copy.deepcopy(epochs);s=e[2]['samples'][p];raw=bytearray(e[2]['raw']);raw[s['offset']]^=1;e[2]['raw']=bytes(raw)
self.assertEqual(a.whole_hypothesis(e)['H_FRONTIERE_CHAMP_ENTIER'],'REFUTED')
class Model(unittest.TestCase):
def test_constant_and_partition(self):
src=np.full((80,100,4),64,dtype='uint8')
for down in (False,True):
pred,fresh,tail=a.ideal(src,20,15,down,(10,10));np.testing.assert_allclose(pred,64,rtol=0,atol=1e-13);np.testing.assert_array_equal(pred,fresh+tail)
def test_linear_ramp_bilinear(self):
yy,xx=np.mgrid[:100,:100];src=np.repeat((xx+yy)[:,:,None],4,axis=2).astype(float)
np.testing.assert_array_equal(a.bilinear(src,np.array([[10.5]]),np.array([[20.25]])),np.full((1,1,4),30.75))
def test_tap_weights_ramp(self):
yy,xx=np.mgrid[:100,:100];src=np.repeat((xx+yy)[:,:,None],4,axis=2).astype(float)
down,_,_=a.ideal(src,30,30,True,(100,100));self.assertEqual(down[10,10,0],41)
up,_,_=a.ideal(src,50,50,False,(100,100));self.assertAlmostEqual(up[20,20,0],19.5)
def test_composition(self):
src=np.full((1,1,4),255);np.testing.assert_array_equal(a.composition(src),[[[187,151,135]]])
with self.assertRaises(v.Rejected):a.composition(src,136)
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,73 @@
"""Actual immutable G1 bytes and adversarial in-memory copies; never rewrites G1."""
import copy
import unittest
import validate as v
import replay
from test_fixture import obj
class ModifierReplay(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.rows,cls.raw=replay.load()
cls.samples=replay.sample_protocol(cls.rows,cls.raw)
def test_nine_real_samples_and_global_rejection(self):
result=replay.audit()
self.assertEqual(result['sample_protocol'],'PASS')
self.assertEqual(result['epoch_validator'],{'status':'REJECTED','reason':'effective halfpixel'})
self.assertEqual(result['GL_ERROR_QUEUE'],'UNOBSERVED')
self.assertEqual(result['read_success'],'NOT_PROVEN')
self.assertEqual(result['GPU_DCC_RUNTIME'],'NOT_RUN')
def test_dcc_and_linear_metadata_only(self):
self.assertEqual(v.validate_object(self.samples[1]['object'])['auxiliary_extent'],'NOT_PROVEN')
self.assertEqual(v.validate_object(obj(1))['layout'],'LINEAR_PACKED')
# Linear is only a synthetic regression control, never an acquired GPU control.
def test_bad_planes_and_modifier(self):
o=self.samples[1]['object']
mutations=[('drm_format',0),('modifier','020000001046fb04'),
('modifier','020000001046bb05'),('modifier','ffffffffffffffff'),
('modifier','q'*16),('planes',[[3196,0],[1024,2293760]]),
('planes',[[3584,0],[0,2293760]]),('planes',[[3584,0],[1024,0]]),
('planes',[[3584,0],[1024,1]]),('planes',[[3584,0],[1024,2**32-1]]),
('planes',[[3584,2**32-1],[1024,2293760]]),
('planes',[[3584,0],[3584,0]]),('planes',[[3584,0]]),
('planes',[[3584,0],[True,2293760]]),('planes',[[3584,0],[1024,-1]])]
for key,value in mutations:
with self.subTest(key=key,value=value),self.assertRaises(v.Rejected):
bad=copy.deepcopy(o);bad[key]=value;v.validate_object(bad)
for key in o:
bad=copy.deepcopy(o);del bad[key]
with self.subTest(missing=key),self.assertRaises((v.Rejected,KeyError)):
v.validate_object(bad)
def test_real_sample_mutations(self):
mutations=[lambda s:s[4].update(valid=False),lambda s:s[4].update(sentinel_alpha_written=False),
lambda s:s[4].update(offset=1),lambda s:s[4].update(sha256='0'*64),
lambda s:s[4].update(context_after='other'),lambda s:s[4]['after'].__setitem__(3,99),
lambda s:s[4].update(target_read_buffer=0),lambda s:s[4].update(roi=[0,0,800,600]),
lambda s:s[4]['attachment'].__setitem__(1,999),lambda s:s[4].update(fbo_status=0)]
for mutate in mutations:
rows=copy.deepcopy(self.rows);samples=[r for r in rows if r['kind']=='sample'];mutate(samples)
with self.subTest(mutation=mutate),self.assertRaises(v.Rejected):replay.sample_protocol(rows,self.raw)
for raw in (self.raw[:-1],bytes([self.raw[0]^1])+self.raw[1:]):
with self.assertRaises(v.Rejected):replay.sample_protocol(self.rows,raw)
def test_eleven_writes_two_copies_and_mutations(self):
self.assertEqual(v.validate_g1_envelope(self.rows,self.samples,self.rows[0]['context'])['writes'],11)
def run(mutate):
rows=copy.deepcopy(self.rows);mutate(rows)
samples=[r for r in rows if r['kind']=='sample']
with self.assertRaises((v.Rejected,KeyError,IndexError)):
v.validate_g1_envelope(rows,samples,rows[0]['context'])
run(lambda r:r.remove(next(x for x in r if x['kind']=='copy')))
run(lambda r:r.remove(next(x for x in r if x['kind']=='write' and x['sequence']==11)))
for key,value in [('read_fbo',999),('texture',0),('context','wrong'),('sampler',[0]*4),('color_mask',[0]*4)]:
run(lambda r,key=key,value=value:next(x for x in r if x['kind']=='write').update({key:value}))
run(lambda r:next(x for x in r if x['kind']=='copy')['src'].update(fbo=999))
run(lambda r:next(x for x in r if x['kind']=='write')['uniforms'].append({'name':'tex','type':35678,'value':[0]}))
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,242 @@
"""EXPERIMENTAL modifier-aware OFFLINE G1 validator; importable offline, no launch or writer.
WHY/CONTRACT/INVARIANT: parse actual bounded publications, never trust a PASS
field or extents for regions; rejected metadata gives no native qualification.
"""
import hashlib
import json
import struct
import os
from pathlib import Path
WIDTH, HEIGHT = 800, 600
LIMIT = 16633312
META_LIMIT = 1024 * 1024
NAMES = ('main-before-blur','down-0','down-1','down-2','up-2','up-1','up-0','cache-after-copy','output-presubmit')
FRESH = ((800,600),(400,300),(200,150),(100,75),(200,150),(400,300),(800,600),(800,600),(800,600))
BXY = ((784,292),(392,142),(192,67),(92,29),(192,67),(392,142),(784,292),(784,292),(784,292))
GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, GL_UNSIGNED_NORMALIZED = 36064, 36161, 35863
# DRM little-endian AB24/XB24; actual import formats must be recorded.
ABGR, XBGR, ARGB, XRGB = [int.from_bytes(x,'little') for x in (b'AB24',b'XB24',b'AR24',b'XR24')]
class Rejected(ValueError): pass
def need(v, msg):
if not v: raise Rejected(msg)
def sha(data): return hashlib.sha256(data).hexdigest()
def plan(epoch, point):
need(epoch in (1,2,3) and 0<=point<9,'epoch/point budget')
if epoch==2 or point>=7: return (*BXY[point],16,16)
if 3<=point<=5: return (0,0,*FRESH[point])
return (0,0,800,600)
def read_plan():
total=sum(plan(e,p)[2]*plan(e,p)[3]*4 for e in (1,2,3) for p in range(9))
need(total==LIMIT and total<16*1024*1024,'planned raw budget')
return dict(reads=27,bytes=total)
def object_identity(o):
return tuple(o[k] if k!='dimensions' else tuple(o[k]) for k in ('fx','wlr','fbo','rbo','image','dimensions'))
def validate_object(o, role="effects"):
need(o['dimensions']==[800,600] and all(o[k] for k in ('fx','wlr','fbo','rbo','image')) and type(o['tex']) is int and o['tex'] >= 0,'existing physical object')
need(o['dmabuf_available'] is True and o['dmabuf_size']==[800,600] and len(o['planes']) in range(1,5),'real dmabuf')
need(o['drm_format'] in ((XBGR,) if role=='cache' else (ABGR,) if role=='effects' else (ABGR,XBGR,ARGB,XRGB)),'source compatible actual DRM format')
return validate_planes(o)
AMD_G1_MODIFIER = '020000001046bb04'
UINT32_MAX = (1 << 32) - 1
def validate_planes(o):
"""Bounded metadata sanity, not a proof of allocation extent or DCC layout.
drm_fourcc.h SHA992f3380...: packed RGB has one image plane;
AMD DCC without RETILE adds one auxiliary plane. Only the observed AMD
modifier is allowlisted. No DCC size/alignment formula is inferred.
"""
modifier = o['modifier']
need(isinstance(modifier, str) and len(modifier) == 16
and all(c in '0123456789abcdef' for c in modifier), 'modifier encoding')
need(modifier in ('0000000000000000', AMD_G1_MODIFIER), 'unknown modifier')
planes = o['planes']
need(len(planes) == (2 if modifier == AMD_G1_MODIFIER else 1), 'modifier plane count')
for p in planes:
need(isinstance(p, list) and len(p) == 2
and all(type(n) is int and 0 <= n <= UINT32_MAX for n in p),
'pitch/offset uint32 fields')
need(p[0] > 0 and p[1] + p[0] <= UINT32_MAX, 'nonzero pitch/offset overflow')
pitch, offset = planes[0]
need(pitch >= WIDTH * 4 and pitch % 4 == 0, 'packed image stride')
image_end = offset + pitch * HEIGHT
need(image_end <= UINT32_MAX, 'image extent overflow')
if modifier == AMD_G1_MODIFIER:
aux_pitch, aux_offset = planes[1]
# This fixture only accepts a single-allocation, image-before-aux layout.
# Exported FD identity/allocation lengths are absent: this is an explicit
# bounded sanity restriction, not a universal DRM modifier requirement.
need(aux_offset >= image_end and aux_offset > offset
and planes[0] != planes[1], 'auxiliary offset/duplicate sanity')
return dict(layout='AMD_GFX11_DCC_NO_RETILE', image_stride='PASS',
auxiliary_fields='PLAUSIBLE_ONLY', auxiliary_extent='NOT_PROVEN',
allocation_bounds='NOT_PROVEN', fd_identity='NOT_OBSERVED')
return dict(layout='LINEAR_PACKED', image_stride='PASS',
allocation_bounds='NOT_PROVEN')
def validate_sample(s, raw, epoch, context, offset):
p=s['point']; need(s['name']==NAMES[p] and s['roi']==list(plan(epoch,p)) and s['fresh']==list(FRESH[p]),'fixed ROI/fresh domain')
x,y,w,h=s['roi'];need(x>=0 and y>=0 and w>0 and h>0 and x+w<=800 and y+h<=600,'ROI texture bounds')
n=w*h*4;need(s['offset']==offset and s['bytes']==n and offset+n<=len(raw),'read framing/truncation')
data=raw[offset:offset+n];need(sha(data)==s['sha256'],'sample SHA')
need(s['before']==s['after'] and len(s['before'])==9,'READ/DRAW/RBO/PBO/pack/readbuffer restoration')
need(s['context_before']==s['context_after']==context,'EGL context restoration')
need(s['target_read_buffer']==GL_COLOR_ATTACHMENT0,'per-FBO readbuffer unchanged')
a=s['attachment'];need(len(a)==8 and a[:3]==[GL_RENDERBUFFER,s['object']['rbo'],GL_UNSIGNED_NORMALIZED] and a[3]==9729 and a[4:7]==[8,8,8] and a[7] in (0,8),'actual attachment channels/type/encoding')
need(len(s['renderbuffer'])==3 and s['renderbuffer'][:2]==[800,600] and s['renderbuffer'][2]>0,'actual RBO size/format')
validate_object(s['object'],'cache' if p==7 else 'main' if p in (0,8) else 'effects')
need(a[7]==(0 if s['object']['drm_format'] in (XBGR,XRGB) else 8),'alpha storage source compatibility')
need(s['sentinel_alpha_written'] is True and 205 not in data[3::4],'sentinel incomplete read')
need(type(s['before_ns']) is int and type(s['after_ns']) is int and 0<s['before_ns']<=s['after_ns'],'sample monotonic interval')
need(s['valid'] is True and s['fbo_status']==36053,'C preconditions/FBO completeness failed')
return offset+n
def validate_writes(rows, samples, context):
writes=[r for r in rows if r['kind']=='write'];blur=[r for r in writes if r['stage']>=0]
need(len(blur)==6 and [r['stage'] for r in blur]==list(range(6)),'six draws strict order')
need([r['sequence'] for r in writes]==list(range(1,len(writes)+1)),'all writes ordered')
objs=[samples[p]['object'] for p in range(7)]
ping,pong=object_identity(objs[1]),object_identity(objs[2])
need(ping!=pong and objs[1]['wlr']!=objs[2]['wlr'] and objs[1]['fbo']!=objs[2]['fbo'],'distinct ping/pong alias')
need([object_identity(o) for o in objs[1:]]==[ping,pong,ping,pong,ping,pong],'same physical buffers within pass')
for i,r in enumerate(blur):
need(r['region']==[[0,0,*FRESH[i+1]]] and r['box']==[0,0,800,600],'exact domain union, no holes')
need(object_identity(r['src'])==object_identity(objs[i]) and object_identity(r['dst'])==object_identity(objs[i+1]),'source/destination continuity')
need(r['texture']==r['src']['tex'] and r['src']['tex']>0,'actual source texture mapping')
for seen,draw_obj in ((objs[i],r['src']),(objs[i+1],r['dst'])):
need(seen['tex']==0 or seen['tex']==draw_obj['tex'],'existing texture replaced')
need(r['src']['wlr']!=r['dst']['wlr'] and r['src']['fbo']!=r['dst']['fbo'],'feedback alias')
need(r['fbo']==r['dst']['fbo'] and r['context']==context and r['viewport']==[0,0,800,600],'draw FBO/context/viewport')
need(r['blend']==r['stencil']==r['scissor_enabled']==0 and r['color_mask']==[1,1,1,1],'unmasked raster write')
need(r['sampler']==[9729,9729,33071,33071] and r['program']>0 and r['texture']>0,'effective sampler/program')
u={x['name']:x for x in r['uniforms']}
need(all(k in u for k in ('radius','halfpixel','proj','tex_proj')),'effective blur uniforms/matrices')
floats=lambda n: [struct.unpack('f',struct.pack('I',b))[0] for b in u[n]['value']]
need(floats('radius')==[5.0] and len(floats('proj'))==len(floats('tex_proj'))==9,'uniform radius/matrix')
expected=(1/800,1/600) if i<3 else (1/3200,1/2400)
need(floats('halfpixel')==[struct.unpack('f',struct.pack('f',x))[0] for x in expected],'effective halfpixel')
fbo_ids={objs[1]['fbo'],objs[2]['fbo']}
need(not any(r['stage']<0 and r['fbo'] in fbo_ids for r in writes),'external ping/pong write')
need(not any(r['kind']=='clear' and r['mask']&16384 for r in rows),'color clear')
need(not any(r['kind'] in ('destroy','replace','resize','migration') for r in rows),'object lifetime/layout change')
copies=[r for r in rows if r['kind']=='copy']
cache=[r for r in copies if r['dst']['fbo']==samples[7]['object']['fbo']]
need(len(cache)==1 and object_identity(cache[0]['src'])==pong and cache[0]['region']==[[0,0,800,600]],'cache copy exact source/domain')
# Every copy includes its corresponding render write, not just a declaration.
for c in copies:
need(any(w['stage']==-1 and w['fbo']==c['dst']['fbo'] and w['region']==c['region'] for w in writes),'copy actual raster write missing')
# Stages and reads alternate; no unseen color draw between output and sample.
positions={id(r):i for i,r in enumerate(rows)}
for i,r in enumerate(blur):
begin=positions[id(samples[i])];end=positions[id(samples[i+1])]
between=[w for w in writes if begin<positions[id(w)]<end]
need(between==[r],'additional/missing write in native interval')
return True
def validate_epoch(directory, epoch):
d=Path(directory);need(epoch in (1,2,3),'fourth epoch')
mp=d/f'epoch-{epoch}.meta';rp=d/f'epoch-{epoch}.rgba';arm=d/f'epoch-{epoch}.arm'
need(arm.is_file() and not arm.is_symlink() and arm.read_bytes()==(('B' if epoch==2 else 'A')+'\n').encode(),'arm absent/invalid')
for file in (mp,rp,arm):
need(file.is_file() and file.stat().st_uid==os.geteuid() and file.stat().st_mode&0o777==0o600,'exclusive mode/ownership')
need(mp.is_file() and rp.is_file() and not mp.is_symlink() and not rp.is_symlink(),'complete exclusive publication')
need(mp.stat().st_size<=META_LIMIT and rp.stat().st_size<=8312048,'bounded publications')
text=mp.read_bytes();need(text.endswith(b'\n'),'metadata framing')
try: rows=[json.loads(line) for line in text.splitlines()]
except (ValueError,UnicodeError) as e: raise Rejected('malformed metadata') from e
need(rows and rows[0]['kind']=='begin' and rows[-1]['kind']=='end','begin/end framing')
b,e=rows[0],rows[-1];need(b['epoch']==e['epoch']==epoch and b['label']==('B' if epoch==2 else 'A'),'epoch order/label')
need(b['output']=='HEADLESS-1' and b['scale']==1 and b['transform']==0 and b['radius']==5 and b['passes']==3 and b['strength']==1 and b['effects_neutral'] is True,'acquisition admissibility')
need(b['GL_ERROR_QUEUE']=='UNOBSERVED' and b['read_success']=='NOT_PROVEN' and b['rows']=='GL_LOW_TO_HIGH','observability limits retained')
need(len(b['strings'])==4 and b['strings'][2].startswith('OpenGL ES 3.') and len(b['precision'])==6,'actual API capabilities')
need(all(r['kind'] in ('begin','write','clear','copy','sample','end') for r in rows),'unknown write/event')
samples=[r for r in rows if r['kind']=='sample'];need([s['point'] for s in samples]==list(range(9)),'nine reads ordered')
raw=rp.read_bytes();offset=0
for s in samples: offset=validate_sample(s,raw,epoch,b['context'],offset)
need(all(samples[p]['after_ns']<=samples[p+1]['before_ns'] for p in range(8)),'native sample temporal order')
need(offset==len(raw)==e['bytes']==(9216 if epoch==2 else 8312048) and sha(raw)==e['sha256'],'epoch size/whole SHA')
need(e['points']==9 and e['stages']==6 and e['writes']==sum(r['kind']=='write' for r in rows),'end counts')
need(e['total_bytes']==sum(plan(k,p)[2]*plan(k,p)[3]*4 for k in range(1,epoch+1) for p in range(9)),'total read budget')
validate_writes(rows,samples,b['context'])
return dict(rows=rows,samples=samples,raw=raw)
def validate(directory):
d=Path(directory);need(not any(d.glob('epoch-4.*')),'fourth epoch forbidden')
epochs=[validate_epoch(d,e) for e in (1,2,3)]
need(len({x['rows'][0]['pass'] for x in epochs})==3,'different local acquisition pass')
return epochs
def reconstruct(epoch):
need(epoch['rows'][0]['epoch'] in (1,3),'B partial fields cannot reconstruct')
validate_writes(epoch['rows'],epoch['samples'],epoch['rows'][0]['context'])
def data(p):
s=epoch['samples'][p];return epoch['raw'][s['offset']:s['offset']+s['bytes']]
def replace(base, patch, w,h):
need(len(base)==800*600*4 and len(patch)==w*h*4,'reconstruction dimensions')
out=bytearray(base)
for y in range(h):out[y*800*4:(y*800+w)*4]=patch[y*w*4:(y+1)*w*4]
return bytes(out)
p2=replace(data(1),data(3),100,75);q2=replace(data(2),data(4),200,150);p1=replace(p2,data(5),400,300)
return dict(P2=p2,Q2=q2,P1=p1,U0=data(6),status='SOURCE_DERIVED_RECONSTRUCTION')
def validate_g1_envelope(rows, samples, context):
"""Additional exact G1 native protocol checks, independent of uniform equality.
A successful envelope never substitutes for validate_epoch/validate_writes.
In particular the observed halfpixel mismatch remains a strict rejection.
"""
writes = [r for r in rows if r['kind'] == 'write']
copies = [r for r in rows if r['kind'] == 'copy']
need(len(writes) == 11 and len(copies) == 2, 'G1 eleven writes/two copies')
need([r['sequence'] for r in writes] == list(range(1, 12)), 'G1 ordered writes')
need([r['stage'] for r in writes] == list(range(6)) + [-1] * 5, 'G1 stage sequence')
for r in writes:
need(r['fbo'] == r['read_fbo'] and r['context'] == context
and r['viewport'] == [0, 0, WIDTH, HEIGHT], 'G1 draw bindings/context')
need(r['texture'] > 0 and r['program'] > 0 and r['color_mask'] == [1]*4
and r['stencil'] == r['scissor_enabled'] == 0, 'G1 raster state')
need(r['sampler'] in ([9729,9729,33071,33071], [9728,9728,33071,33071]),
'G1 effective sampler')
uniforms = r['uniforms']
names = [u['name'] for u in uniforms]
need(len(names) == len(set(names)) and all(n in names for n in ('tex','proj','tex_proj')),
'G1 unique sampler/matrix uniforms')
u = {u['name']:u for u in uniforms}
need(u['tex']['type'] == 35678 and u['tex']['value'] == [0], 'G1 sampler texture unit')
need(all(u[n]['type'] == 35675 and len(u[n]['value']) == 9 for n in ('proj','tex_proj')),
'G1 matrix type/size')
need(all(type(b) is int and 0 <= b <= UINT32_MAX
for x in uniforms for b in x['value']), 'G1 uniform bit encoding')
if r['stage'] >= 0:
need(u['radius']['type'] == 5126 and u['halfpixel']['type'] == 35664
and len(u['halfpixel']['value']) == 2, 'G1 blur uniform types')
else:
need(u['alpha']['value'] == [1065353216] and u['discard_transparent']['value'] == [0],
'G1 copy/composition uniforms')
for c, seq, src in zip(copies, (7,8), (samples[6]['object'], samples[0]['object'])):
validate_object(c['src'], 'main')
validate_object(c['dst'], 'main')
w = writes[seq-1]
need(object_identity(c['src']) == object_identity(src)
and w['fbo'] == c['dst']['fbo'] and w['texture'] == c['src']['tex']
and w['region'] == c['region'] == [[0,0,WIDTH,HEIGHT]], 'G1 copy continuity')
need(rows.index(w) < rows.index(c), 'G1 copy follows raster write')
need(rows.index(copies[0]) < rows.index(samples[7]) < rows.index(writes[7])
and rows.index(writes[-1]) < rows.index(samples[8]), 'G1 copy/sample order')
need(writes[8]['region'] == [[0,0,480,600]]
and writes[9]['region'] == writes[10]['region'] == [[480,0,800,600]]
and all(w['fbo'] == samples[8]['object']['fbo'] for w in writes[8:]),
'G1 final composition regions/target')
return dict(writes=11, copies=2, status='STRUCTURAL_ONLY')

View file

@ -0,0 +1,67 @@
# Private CORE recovery graphics fixtures
EXPERIMENTAL, source-only. No production implementation, dependency, compositor
build or live-desktop operation. Historical E runs and laboratory are immutable.
The new laboratory is `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5`.
`parent-provenance.json` records every parent source byte at commit 70954e26.
Changed Python modules are fresh fixture sources; changed C clients retain SHM
protocol buffers until observable release, emit explicit close receipts and
separate those releases from native GPU retirement. Generated protocol C/headers
remain in the historical source directory and are copied only to the private
LAB by `prepare.py`. No generated binaries, protocol files, PNG or logs belong
in this source directory. The private LAB `source-delta.patch` is a textual parent/new comparison.
From the repository root:
```sh
python -B -m unittest discover -s tooling/probes/step20c-core-recovery/graphics -p test_recovery.py
python -B tooling/probes/step20c-core-recovery/graphics/replay.py
python -B tooling/probes/step20c-core-recovery/graphics/prepare.py
```
Preparation compiles only five private C17 clients and does not connect to
Wayland. Copy the reviewed fixture Python/config/source tree into LAB/graphics,
then seal exact identities and the resulting client binaries using `seal.py`.
An independent ACCEPTED review must bind the manifest's exact SHA before
`run.py <S-id>` is allowed to debit the separate durable ticket budget.
`begin()` verifies manifest bytes, binary/wlroots identity, authorized family,
pre-existing run absence and limits before the durable reservation. The parent
owns budget initialization and runtime serialization. Never rerun a consumed ID.
Prospective capture ceilings are 12/12/18/12/12/14/14/14/12 (120 total).
S0/S1 standard compare actual H2 A/B paint and input, explicit B close, surviving
A input and final whole-H2 RGB. S2 first exercises Mosaic dismissal, then selects
and verifies workspace1/H1/Mosaic before its second owner's spawn; a wrong map
is rejected before any click. Its live second chain then performs the same
standard return. No mapped chain is silently moved. S3/S4 use actual H2 y=600,
configured popup Y crossings, output-local captures and measured global input.
Missing crossing is NOT EXERCISED, never clipping PASS.
S5/S6 apply fullscreen to a genuine nonleaf con ancestor, not the workspace IPC
fullscreen marker. Each retains equivalent raw paint/input/return observations;
a separate paired analysis must compare their results without defining new UX.
S7 hides and returns the first chain without any intervening click, verifies its
live IDs/grabs and visible B input, then closes explicitly. Optional destructive
hidden dismissal is omitted to preserve the bounded prospective sequence; E5
already observes that separate lifecycle, without proving universal click-through.
S8 has a preserved PRE-RELEASE HOLD proof and a separate POST-RELEASE ACK/commit
validator. E6 replay already acquires all eight images and the timeout/release
ordering; S8 is justified only for missing normal-close, SHM release and final
restoration observations. Saved native identity/GPU completion remain NOT PROVEN.
Clients close before private compositor exit. Explicit receipt, child-first
protocol destruction, SHM release, client error/code, terminal IPC, mechanical
cleanup, measurements and architectural verdict remain distinct observations.
IPC exit1 is retained verbatim; allowed transport handling never changes it to 0.
No native clip/tree/buffer generation hooks exist on the CORE SHA, so effective
clip removal is sampled through A/B spill plus paint/pointer corroboration.
No full damage coverage or transaction atomicity is inferred from screenshots.
Offline negatives call the same predicates imported by Session/run/cases:
null/real/branch floating, wrong owner/PID/path/workspace/output/layout, fake
fullscreen ancestry, unmapped B/fake grabs/parent/native buffer size, absent Y
crossing/wrong output/frame, destructive hidden click, effective standard
continuation absent, one-pixel RGB corruption/mask, pending IPC, wrong SHA and
superficial PASS, HOLD ACK contamination, inverted release phases/dead client,
and incomplete scientific closure. Unobserved native tree state stays explicit.

View file

@ -0,0 +1,113 @@
"""Small campaign scenarios over the same reviewed Session; no historical runner copy."""
import re,time
from PIL import ImageChops
from engine import *
from geometry import *
def init_reference(s,prefix_fillers=1):
s.cmd('workspace 2');s.launch_client('reference','static-reference');rid=s.find('reference')['id'];s.cmd(f'[con_id={rid}] border none')
s.cmd('workspace 1')
for i in range(prefix_fillers):s.launch_client('filler'+str(i),'static-reference')
s.cmd('layout mosaic' if s.spec['mode']=='mosaic' else 'layout splith')
return rid
def stable_reference(s):
s.inject('move',[s.outputs[0]['logical'][0]+10,s.outputs[0]['logical'][1]+10]);time.sleep(.15)
a=s.capture('H2-reference1','HEADLESS-2');time.sleep(.6);b=s.capture('H2-reference2','HEADLESS-2');exact(a,b)
need(all(v==(48,48,56) for v in a.get_flattened_data()),'static uniform H2');s.save('reference-stability',dict(status='PASS',exact=exact(a,b)));return a
def root_state(s,state,origin,window_start,expect_hidden=False,previous=None,expected_tree=None):
# WHY/CONTRACT/INVARIANT: no PI/native scene identity exists in CORE.
# Pixel/input evidence qualifies sampled clipping only, never generation.
return dict(status='NOT OBSERVABLE',native_tree_identity='NOT OBSERVABLE',
applied_generation='NOT OBSERVABLE',owner_id=state['owner']['id'],
workspace_id=state['workspace']['id'],window=[window_start,time.monotonic_ns()])
def open_chain(s,tail_fillers=0):
s.cmd('workspace 1');s.expected_layout='mosaic' if s.spec['mode']=='mosaic' else 'splith'
pre=s.snapshot('before-chain-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
validators.selected_workspace(pre,'1','HEADLESS-1',s.expected_layout)
s.debit('chain');s.launch_client('owner',s.spec['client'],s.spec['placement'])
s.owner('owner') # Reject wrong map before any click; never move mapped popup chains.
for i in range(tail_fillers):s.launch_client('tail'+str(i),'static-reference')
s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner');baseline=s.reference if hasattr(s,'reference') else stable_reference(s);s.reference=baseline
im=s.capture('owner-top-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');rect=[*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']]
xy=solid_point(im,COLORS['TOPLEVEL'],s.outputs[0],s.frames['HEADLESS-1'],rect)
need(contains([*state['origin'],420,575 if s.spec['axis']==1 else 320],xy),'opening native bounds')
ta,ba=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.2)
aim=s.capture('popup-A-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');prov=Provenance(state['owner']['id'],state['workspace']['id'],'bounded-opening-window',state['evidence'])
xyA=solid_point(aim,COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'])
tb,bb=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
window=time.monotonic_ns();time.sleep(.15);p=validators.grab(s.log('owner'));g=geometry(s.log('owner'),state['origin'],prov)
s.delivery('owner',rect,xy,ta,ba,'TOPLEVEL',p['grabs'][0]['serial']);s.delivery('owner',g['A'],xyA,tb,bb,'POPUP_A',p['grabs'][1]['serial'])
[validators.crosses(g[k],s.spec['axis'],s.spec['boundary']) for k in ('A','B')]
left,right=s.pair_capture('mapped-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
for name in ('POPUP_A','POPUP_B'):
need(count(left,COLORS[name])>0,'permitted popup pixels missing')
if s.spec['mode']=='pristine':need(count(right,COLORS[name])>0,'standard popup continuation missing')
if s.spec['mode']=='mosaic':validators.rgb(baseline,right);root=root_state(s,state,state['origin'],window)
else:root=dict(status='NOT OBSERVABLE',meaning='unmodified control has no private clip instrumentation')
s.save('chain',dict(protocol=p,geometry=g,state=state,root=root,H2_exact=s.spec['mode']=='mosaic',color_counts={n:[count(left,c),count(right,c)] for n,c in COLORS.items()}))
return state,baseline,p,g,left,right
def popup_campaign(s):
init_reference(s);state,baseline,p,g,left,right=open_chain(s)
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
forbidden=intersection(g['B'],s.outputs[1]['logical']);need(forbidden[2]>6 and forbidden[3]>6,'H2 crossing interior missing')
xy=[forbidden[0]+forbidden[2]//2,forbidden[1]+forbidden[3]//2]
if s.spec['mode']=='pristine':
# The point must be real standard paint, not just a requested extent.
xy=solid_point(right,COLORS['POPUP_B'],s.outputs[1],s.frames['HEADLESS-2'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
else:
tails,buttons=s.click('owner',xy);ds=dismissal(tails['owner'],buttons['owner']);s.save('dismissal',dict(proof=ds,reference_buttons=buttons.get('reference'),interpretation='outside grab dismissal; no universal click-through claim'))
need(not any('target=POPUP_A ' in l or 'target=POPUP_B ' in l for v in buttons.values() for l in v),'forbidden popup delivery')
s.close_client('owner');time.sleep(.2);final=s.pair_capture('final');exact(baseline,final[1]);need(not any(count(im,COLORS[n]) for im in final for n in ('POPUP_A','POPUP_B')),'popup residual')
return 'PASS'
def hidden_campaign(s):
init_reference(s,2);state,baseline,p,g,left,right=open_chain(s,2)
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
delta=state['scene'][0]+state['scene'][2]-s.outputs[0]['logical'][0]+16;start=time.monotonic_ns();s.scroll('owner',delta);hidden=s.owner('owner')
need(intersection(hidden['scene'],s.outputs[0]['logical'])[2]==0,'owner not completely hidden')
images=s.pair_capture('fully-hidden');need(not any(count(im,COLORS[n]) for im in images for n in ('POPUP_A','POPUP_B')),'hidden popup residual');exact(baseline,images[1])
root=root_state(s,hidden,hidden['origin'],start,True,previous=state);cut=len(s.log('owner'));s.inject('move',[-790,590]);need('target=POPUP_A ' not in s.log('owner')[cut:] and 'target=POPUP_B ' not in s.log('owner')[cut:],'hidden popup pointer enter')
validators.hidden_survival(s.log('owner'),p,[])
return_start=time.monotonic_ns();s.scroll('owner',-delta);restored=s.owner('owner');need(restored['origin']==state['origin'],'same owner origin restored')
images=s.pair_capture('returned');validators.hidden_survival(s.log('owner'),p,[e for e in s.events if e['kind']=='injection' and start<=e['ns']<=return_start]);exact(baseline,images[1]);returned_root=root_state(s,restored,restored['origin'],return_start,previous=hidden,expected_tree=root.get('tree'));fresh=validators.grab(s.log('owner'));need(fresh['ids']==p['ids'] and fresh['grabs']==p['grabs'],'unchanged live chain')
xy=solid_point(images[0],COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
s.save('hidden-return',dict(status='PASS',hidden=hidden,empty_root=root,returned=restored,returned_root=returned_root,ids=p['ids']))
s.close_client('owner');final=s.pair_capture('final');exact(baseline,final[1]);return 'PASS'
def sample_surviving(s,images,tag):
if 'POPUP_DONE ' in s.log('owner'):
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='legitimate popup dismissal, no new chain reserved'));return
state=s.owner('owner');prot=validators.grab(s.log('owner'));geo=geometry(s.log('owner'),state['origin'],Provenance(state['owner']['id'],state['workspace']['id'],'NOT OBSERVABLE','mutation paint+pointer'))
index=next(i for i,o in enumerate(s.outputs) if o['name']==state['workspace']['output'])
try:point=solid_point(images[index],COLORS['POPUP_B'],s.outputs[index],s.frames[s.outputs[index]['name']],geo['B'])
except ValueError:
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='B has no measured visible interior'));return
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B');s.save(tag+'-input',dict(status='PASS',point=point,protocol=prot))
def dynamic_campaign(s):
blank=s.capture('startup-vacated-H1','HEADLESS-1');init_reference(s,2);s.cmd('gaps inner 0');state,baseline,p,g,left,right=open_chain(s)
owner_id=state['owner']['id'];ws_id=state['workspace']['id'];order=state['workspace'].get('mosaic',{}).get('rows',[{}])[0].get('children')
s.cmd('output HEADLESS-1 resolution 960x600 position -960 0',True);s.outputs[0]={**s.outputs[0],'logical':[-960,0,960,600],'mode':[960,600]};time.sleep(.35)
snap=s.snapshot('resized960');sample_surviving(s,s.pair_capture('resized960'),'resized960')
clamp=dict(status='NOT EXERCISED',reason='standard control has no Mosaic camera')
if s.spec['mode']=='mosaic':
ws=workspace(snap['tree'],'1');row=ws['mosaic']['rows'][0];n=len(row['children']);gap=max(0,ws.get('gaps',{}).get('inner',0));cell=max(640,(960-(n-1)*gap)//n);maximum=max(0,n*cell+(n-1)*gap-960)
request=maximum+960;before=row['scroll_x'];s.scroll('owner',request);settled=s.snapshot('clamped960');actual=workspace(settled['tree'],'1')['mosaic']['rows'][0]['scroll_x']
need(actual==maximum,'source-derived clamp mismatch');clamp=dict(status='PASS',count=n,gap=gap,minimum_cell_width=640,source='frozen mosaic.c125-152',maximum=maximum,before=before,requested_relative=request,observed=actual)
s.cmd('output HEADLESS-1 resolution 800x600 position -800 0',True);s.outputs[0]=dict(s.spec['outputs'][0]);time.sleep(.35);s.snapshot('resized-back');sample_surviving(s,s.pair_capture('resized-back'),'resized-back')
stages=[]
for out,tag in [('HEADLESS-2','migrated-H2'),('HEADLESS-1','migrated-back-H1')]:
s.cmd('workspace 1');s.cmd(f'move workspace to output {out}',True);time.sleep(.35);snap=s.snapshot(tag);ws=workspace(snap['tree'],'1');owner=s.find('owner');need(ws['id']==ws_id and ws['output']==out and owner['id']==owner_id,'migrated exact owner/workspace')
if order is not None:need(ws['mosaic']['rows'][0]['children']==order,'logical row order changed')
pictures=s.pair_capture(tag);sample_surviving(s,pictures,tag);stages.append(dict(tag=tag,workspace=ws,owner=owner,popup_done=re.findall(r'(POPUP_[AB]) POPUP_DONE',s.log('owner'))))
if out=='HEADLESS-2':need(not any(count(pictures[0],COLORS[n]) for n in ('POPUP_A','POPUP_B')),'old output retained popup pixels')
s.save('dynamic-ownership',dict(status='PASS',clamp=clamp,stages=stages,ids_before=p['ids'],ids_after=validators.grab(s.log('owner'))['ids'] if 'POPUP_DONE ' not in s.log('owner') else 'legitimate dismissal recorded',atomic_generation='NOT PROVEN'))
s.close_client('owner')
for name in list(s.proc):
if name.startswith('filler'):s.close_client(name,'close')
time.sleep(.2);final=s.pair_capture('final');exact(baseline,final[1]);exact(blank,final[0]);return 'PASS'

View file

@ -0,0 +1,284 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
* parent is A's xdg_surface; logs name object IDs as well as colors. */
struct surface {
const char *name;
struct wl_surface *wl;
struct xdg_surface *xdg;
struct xdg_popup *popup;
bool configured, mapped, done;
int width, height, anchor_x, anchor_y;
uint32_t color;
};
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static struct xdg_toplevel *top_role;
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
static volatile sig_atomic_t running = 1;
static struct surface *entered;
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(struct surface *s) {
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
char path[] = "/tmp/mosaic-nested-buffer-XXXXXX";
int fd = mkstemp(path);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
unlink(path);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
s->width*4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(s->wl, buffer, 0, 0);
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
wl_surface_commit(s->wl);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
s->mapped = true;
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
}
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
struct surface *s=data;
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
xdg_surface_ack_configure(xdg, serial);
s->configured=true;
if (!s->mapped && !s->done) draw(s);
}
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
int32_t h, struct wl_array *states) {
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
}
static void on_top_close(void *data, struct xdg_toplevel *role) {
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
}
static const struct xdg_toplevel_listener top_listener = {
.configure=on_top_configure, .close=on_top_close,
};
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
int32_t y, int32_t w, int32_t h) {
struct surface *s=data;
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
}
static void on_popup_done(void *data, struct xdg_popup *role) {
struct surface *s=data;
s->done=true;
s->mapped=false;
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
}
static const struct xdg_popup_listener popup_listener = {
.configure=on_popup_configure, .popup_done=on_popup_done,
};
/* WHY: XDG requires child-first destruction even if the compositor sends
* dismissals for multiple levels in one dispatch batch. */
static void destroy_popup(struct surface *s) {
if (!s->popup) return;
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
wl_proxy_get_id((struct wl_proxy *)s->popup),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->wl));
xdg_popup_destroy(s->popup);
xdg_surface_destroy(s->xdg);
wl_surface_destroy(s->wl);
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
if (entered==s) entered=NULL;
}
static void collect_dismissals(void) {
if (b.done) destroy_popup(&b);
if (a.done) {
destroy_popup(&b);
destroy_popup(&a);
}
}
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
if (s->popup || !parent->mapped || !serial) return;
s->wl=wl_compositor_create_surface(compositor);
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
xdg_positioner_set_size(pos,s->width,s->height);
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
xdg_popup_add_listener(s->popup,&popup_listener,s);
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
s->name,parent->name,serial,serial);
xdg_popup_grab(s->popup,seat,serial);
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
xdg_positioner_destroy(pos);
}
static struct surface *which(struct wl_surface *wl) {
if (wl==top.wl) return &top;
if (wl==a.wl && wl) return &a;
if (wl==b.wl && wl) return &b;
return NULL;
}
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
entered=which(wl);
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
wl_fixed_to_double(x),wl_fixed_to_double(y));
}
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
struct wl_surface *wl) {
struct surface *s=which(wl);
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
if (entered==s) entered=NULL;
}
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
wl_fixed_t x,wl_fixed_t y) {
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
}
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
uint32_t time,uint32_t button,uint32_t state) {
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
entered?entered->name:"OTHER",serial,button,state);
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
}
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
static void on_frame(void *data,struct wl_pointer *ptr) {}
static const struct wl_pointer_listener pointer_listener={
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
.axis=on_axis,.frame=on_frame,
};
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
pointer=wl_seat_get_pointer(s);
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
}
}
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
const char *interface,uint32_t version) {
if (!strcmp(interface,wl_compositor_interface.name))
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
else if (!strcmp(interface,wl_shm_interface.name))
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
else if (!strcmp(interface,xdg_wm_base_interface.name))
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
else if (!strcmp(interface,wl_seat_interface.name)) {
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
wl_seat_add_listener(seat,&seat_listener,NULL);
}
}
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
static void stop(int sig) { running=0; }
int main(int argc,char **argv) {
signal(SIGTERM,stop);
const char *placement=argc>1?argv[1]:"b_cross";
/* EXPERIMENTAL: only placement changes; serial/grab/lifecycle stay historical.
* Both 1x1 anchors lie inside their parent SHM window geometry. */
if (!strcmp(placement,"static_cross")) {
a.anchor_x=100; a.anchor_y=80; a.width=350;
b.anchor_x=100; b.anchor_y=60; b.width=300;
} else if (!strcmp(placement,"visible")) {
a.anchor_x=50; a.anchor_y=80; a.width=250;
b.anchor_x=20; b.anchor_y=60; b.width=170;
} else if (!strcmp(placement,"both_cross")) {
a.anchor_x=350; a.anchor_y=80; a.width=350;
b.anchor_x=160; b.anchor_y=60; b.width=300;
} else if (!strcmp(placement,"b_cross")) {
a.anchor_x=200; a.anchor_y=80; a.width=300;
b.anchor_x=220; b.anchor_y=60; b.width=300;
} else { fprintf(stderr,"invalid placement\n"); return 2; }
display=wl_display_connect(NULL);
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
struct wl_registry *registry=wl_display_get_registry(display);
wl_registry_add_listener(registry,&registry_listener,NULL);
wl_display_roundtrip(display); wl_display_roundtrip(display);
if (!compositor || !shm || !wm || !seat || !pointer) {
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
}
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
top.wl=wl_compositor_create_surface(compositor);
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
top_role=xdg_surface_get_toplevel(top.xdg);
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
wl_proxy_get_id((struct wl_proxy *)top.wl),
wl_proxy_get_id((struct wl_proxy *)top.xdg),
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
wl_surface_commit(top.wl);
while (running) {
wl_display_flush(display);
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
{.fd=STDIN_FILENO,.events=POLLIN}};
int ready=poll(fds,2,500);
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
if (wl_display_dispatch(display)<0) break;
collect_dismissals();
}
if (fds[1].revents & POLLIN) {
char cmd[64];
if (fgets(cmd,sizeof(cmd),stdin)) {
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
}
}
}
destroy_popup(&b); destroy_popup(&a);
fprintf(stderr,"TOPLEVEL DESTROY\n");
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
wl_display_roundtrip(display);
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
wl_display_flush(display);
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
wl_display_disconnect(display);
return 0;
}

View file

@ -0,0 +1,239 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* WHY: the compositor must wait on a real, responsive XDG client.
* CONTRACT: hold only a received configure; ACK the latest received serial.
* INVARIANT: Wayland dispatch and ping replies continue while ACK is held. */
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_surface *surface;
static struct xdg_surface *xdg;
static struct xdg_toplevel *toplevel;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static volatile sig_atomic_t running = 1;
static bool mapped, hold_next, held;
static uint32_t latest_serial;
static int32_t width = 420, height = 320, configured_width = 420, configured_height = 320;
static unsigned generation;
static long long stamp(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
}
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(void) {
if (configured_width > 0) width = configured_width;
if (configured_height > 0) height = configured_height;
if (width <= 0 || height <= 0 || width > 4096 || height > 4096) {
fprintf(stderr, "INVALID_SIZE %d %d\n", width, height);
exit(2);
}
size_t bytes = (size_t)width * (size_t)height * 4;
char name[] = "/tmp/mosaic-saved-shm-XXXXXX";
int fd = mkstemp(name);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("shm"); exit(2); }
unlink(name);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(2); }
uint32_t color = (generation++ % 2) ? 0x0020d0a0 : 0x002344cc;
#ifdef STATIC_REFERENCE
/* CONTRACT: H2 uses one opaque SHM color across every real configure. */
color = 0x00303038;
#endif
for (size_t i = 0; i < bytes / 4; ++i) pixels[i] = color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
width * 4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
fprintf(stderr, "COMMIT ns=%lld serial=%u size=%dx%d color=%08x\n",
stamp(), latest_serial, width, height, color);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
mapped = true;
}
static void ack_and_commit(void) {
if (!latest_serial) return;
xdg_surface_ack_configure(xdg, latest_serial);
fprintf(stderr, "ACK ns=%lld serial=%u size=%dx%d\n",
stamp(), latest_serial, configured_width, configured_height);
draw();
held = false;
}
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
fprintf(stderr, "PONG ns=%lld serial=%u\n", stamp(), serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
static void role_configure(void *data, struct xdg_toplevel *role,
int32_t w, int32_t h, struct wl_array *states) {
if (w > 0) configured_width = w;
if (h > 0) configured_height = h;
fprintf(stderr, "ROLE_CONFIGURE ns=%lld size=%dx%d\n", stamp(), w, h);
}
static void role_close(void *data, struct xdg_toplevel *role) { running = 0; }
static const struct xdg_toplevel_listener role_listener = {
.configure = role_configure, .close = role_close,
};
static void configure(void *data, struct xdg_surface *s, uint32_t serial) {
latest_serial = serial;
fprintf(stderr, "XDG_CONFIGURE ns=%lld serial=%u size=%dx%d hold=%d previous=%d\n",
stamp(), serial, configured_width, configured_height, hold_next || held, held);
if (hold_next || held) {
held = true;
hold_next = false;
fprintf(stderr, "HELD ns=%lld serial=%u\n", stamp(), serial);
} else {
ack_and_commit();
}
}
static const struct xdg_surface_listener surface_listener = {.configure = configure};
static void pointer_enter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s, wl_fixed_t x, wl_fixed_t y) {
fprintf(stderr, "ENTER ns=%lld x=%.2f y=%.2f\n", stamp(),
wl_fixed_to_double(x), wl_fixed_to_double(y));
}
static void pointer_leave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s) { fprintf(stderr, "LEAVE ns=%lld\n", stamp()); }
static void pointer_motion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t x, wl_fixed_t y) {
fprintf(stderr, "MOTION ns=%lld x=%.2f y=%.2f\n", stamp(),
wl_fixed_to_double(x), wl_fixed_to_double(y));
}
static void pointer_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state) {
fprintf(stderr, "BUTTON ns=%lld serial=%u button=%u state=%u\n",
stamp(), serial, button, state);
}
static void pointer_axis(void *data, struct wl_pointer *p, uint32_t t,
uint32_t axis, wl_fixed_t value) {}
static void pointer_frame(void *data, struct wl_pointer *p) {}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion,
.button = pointer_button, .axis = pointer_axis, .frame = pointer_frame,
};
static void capabilities(void *data, struct wl_seat *s, uint32_t caps) {
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
pointer = wl_seat_get_pointer(s);
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
}
}
static void seat_name(void *data, struct wl_seat *s, const char *name) {}
static const struct wl_seat_listener seat_listener = {
.capabilities = capabilities, .name = seat_name,
};
static void global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (!strcmp(interface, wl_compositor_interface.name))
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, version < 4 ? version : 4);
else if (!strcmp(interface, wl_shm_interface.name))
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
else if (!strcmp(interface, xdg_wm_base_interface.name))
wm = wl_registry_bind(registry, name, &xdg_wm_base_interface, version < 3 ? version : 3);
else if (!strcmp(interface, wl_seat_interface.name)) {
seat = wl_registry_bind(registry, name, &wl_seat_interface, version < 5 ? version : 5);
wl_seat_add_listener(seat, &seat_listener, NULL);
}
}
static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
static const struct wl_registry_listener registry_listener = {
.global = global, .global_remove = global_remove,
};
static void stop(int signum) { running = 0; }
int main(void) {
signal(SIGTERM, stop);
display = wl_display_connect(NULL);
if (!display) { fprintf(stderr, "CONNECT_FAILED\n"); return 1; }
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_roundtrip(display);
wl_display_roundtrip(display);
if (!compositor || !shm || !wm) { fprintf(stderr, "MISSING_GLOBAL\n"); return 2; }
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
surface = wl_compositor_create_surface(compositor);
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
xdg_surface_add_listener(xdg, &surface_listener, NULL);
toplevel = xdg_surface_get_toplevel(xdg);
xdg_toplevel_add_listener(toplevel, &role_listener, NULL);
#ifdef STATIC_REFERENCE
xdg_toplevel_set_title(toplevel, "Mosaic Static Reference");
xdg_toplevel_set_app_id(toplevel, "org.mosaic.StaticReference");
#else
xdg_toplevel_set_title(toplevel, "Mosaic Saved Buffer Probe");
xdg_toplevel_set_app_id(toplevel, "org.mosaic.SavedBufferProbe");
#endif
wl_surface_commit(surface);
fprintf(stderr, "READY ns=%lld\n", stamp());
while (running) {
wl_display_flush(display);
struct pollfd fds[2] = {
{.fd = wl_display_get_fd(display), .events = POLLIN},
{.fd = STDIN_FILENO, .events = POLLIN},
};
int ready = poll(fds, 2, 100);
if (ready < 0) { if (errno == EINTR) continue; perror("poll"); break; }
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
if (wl_display_dispatch(display) < 0) break;
}
if (fds[1].revents & POLLIN) {
char cmd[64];
if (!fgets(cmd, sizeof(cmd), stdin)) break;
if (!strncmp(cmd, "hold_next_configure", 19)) {
hold_next = true;
fprintf(stderr, "COMMAND hold_next_configure DONE ns=%lld\n", stamp());
} else if (!strncmp(cmd, "release_configure", 17)) {
if (held) ack_and_commit();
fprintf(stderr, "COMMAND release_configure DONE ns=%lld\n", stamp());
} else if (!strncmp(cmd, "close", 5)) {
fprintf(stderr,"CLOSE_RECEIVED\n");
running = 0;
fprintf(stderr, "COMMAND close DONE ns=%lld\n", stamp());
}
}
}
fprintf(stderr, "EXIT ns=%lld mapped=%d held=%d error=%d\n",
stamp(), mapped, held, wl_display_get_error(display));
xdg_toplevel_destroy(toplevel);
xdg_surface_destroy(xdg);
wl_surface_destroy(surface);
wl_display_roundtrip(display);
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
wl_display_flush(display);
wl_display_disconnect(display);
return 0;
}

View file

@ -0,0 +1,294 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
* parent is A's xdg_surface; logs name object IDs as well as colors. */
struct surface {
const char *name;
struct wl_surface *wl;
struct xdg_surface *xdg;
struct xdg_popup *popup;
bool configured, mapped, done;
int width, height, anchor_x, anchor_y;
uint32_t color;
};
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static struct xdg_toplevel *top_role;
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
static volatile sig_atomic_t running = 1;
static struct surface *entered;
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(struct surface *s) {
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
char path[] = "/tmp/mosaic-nested-buffer-XXXXXX";
int fd = mkstemp(path);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
unlink(path);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
s->width*4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(s->wl, buffer, 0, 0);
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
wl_surface_commit(s->wl);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
s->mapped = true;
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
}
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
struct surface *s=data;
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
xdg_surface_ack_configure(xdg, serial);
s->configured=true;
if (!s->mapped && !s->done) draw(s);
}
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
int32_t h, struct wl_array *states) {
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
}
static void on_top_close(void *data, struct xdg_toplevel *role) {
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
}
static const struct xdg_toplevel_listener top_listener = {
.configure=on_top_configure, .close=on_top_close,
};
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
int32_t y, int32_t w, int32_t h) {
struct surface *s=data;
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
}
static void on_popup_done(void *data, struct xdg_popup *role) {
struct surface *s=data;
s->done=true;
s->mapped=false;
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
}
static const struct xdg_popup_listener popup_listener = {
.configure=on_popup_configure, .popup_done=on_popup_done,
};
/* WHY: XDG requires child-first destruction even if the compositor sends
* dismissals for multiple levels in one dispatch batch. */
static void destroy_popup(struct surface *s) {
if (!s->popup) return;
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
wl_proxy_get_id((struct wl_proxy *)s->popup),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->wl));
xdg_popup_destroy(s->popup);
xdg_surface_destroy(s->xdg);
wl_surface_destroy(s->wl);
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
if (entered==s) entered=NULL;
}
static void collect_dismissals(void) {
if (b.done) destroy_popup(&b);
if (a.done) {
destroy_popup(&b);
destroy_popup(&a);
}
}
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
if (s->popup || !parent->mapped || !serial) return;
s->wl=wl_compositor_create_surface(compositor);
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
xdg_positioner_set_size(pos,s->width,s->height);
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
xdg_popup_add_listener(s->popup,&popup_listener,s);
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
s->name,parent->name,serial,serial);
xdg_popup_grab(s->popup,seat,serial);
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
xdg_positioner_destroy(pos);
}
static struct surface *which(struct wl_surface *wl) {
if (wl==top.wl) return &top;
if (wl==a.wl && wl) return &a;
if (wl==b.wl && wl) return &b;
return NULL;
}
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
entered=which(wl);
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
wl_fixed_to_double(x),wl_fixed_to_double(y));
}
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
struct wl_surface *wl) {
struct surface *s=which(wl);
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
if (entered==s) entered=NULL;
}
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
wl_fixed_t x,wl_fixed_t y) {
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
}
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
uint32_t time,uint32_t button,uint32_t state) {
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
entered?entered->name:"OTHER",serial,button,state);
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
}
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
static void on_frame(void *data,struct wl_pointer *ptr) {}
static const struct wl_pointer_listener pointer_listener={
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
.axis=on_axis,.frame=on_frame,
};
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
pointer=wl_seat_get_pointer(s);
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
}
}
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
const char *interface,uint32_t version) {
if (!strcmp(interface,wl_compositor_interface.name))
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
else if (!strcmp(interface,wl_shm_interface.name))
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
else if (!strcmp(interface,xdg_wm_base_interface.name))
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
else if (!strcmp(interface,wl_seat_interface.name)) {
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
wl_seat_add_listener(seat,&seat_listener,NULL);
}
}
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
static void stop(int sig) { running=0; }
int main(int argc,char **argv) {
signal(SIGTERM,stop);
const char *placement=argc>1?argv[1]:"b_cross";
/* CONTRACT: vertical positioner anchors remain inside the toplevel's
* effective window geometry, which defaults to its buffer bounds. */
if (!strncmp(placement,"v_",2)) top.height=575;
if (!strcmp(placement,"visible")) {
a.anchor_x=50; a.anchor_y=80; a.width=250;
b.anchor_x=20; b.anchor_y=60; b.width=170;
} else if (!strcmp(placement,"v_visible")) {
a.anchor_x=50; a.anchor_y=80; a.width=250;
b.anchor_x=20; b.anchor_y=60; b.width=170;
} else if (!strcmp(placement,"v_b_cross")) {
/* WHY: only B's requested rectangle reaches below y=600 when the
* tiled toplevel content begins near y=25. Configure and pixels
* remain the authority for the actual crossing. */
a.anchor_x=50; a.anchor_y=350; a.width=250;
b.anchor_x=20; b.anchor_y=155; b.width=170;
} else if (!strcmp(placement,"v_both_cross")) {
a.anchor_x=50; a.anchor_y=480; a.width=250;
b.anchor_x=20; b.anchor_y=80; b.width=170;
} else if (!strcmp(placement,"both_cross")) {
a.anchor_x=350; a.anchor_y=80; a.width=350;
b.anchor_x=160; b.anchor_y=60; b.width=300;
} else if (!strcmp(placement,"b_cross")) {
a.anchor_x=200; a.anchor_y=80; a.width=300;
b.anchor_x=220; b.anchor_y=60; b.width=300;
} else { fprintf(stderr,"invalid placement\n"); return 2; }
display=wl_display_connect(NULL);
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
struct wl_registry *registry=wl_display_get_registry(display);
wl_registry_add_listener(registry,&registry_listener,NULL);
wl_display_roundtrip(display); wl_display_roundtrip(display);
if (!compositor || !shm || !wm || !seat || !pointer) {
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
}
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
top.wl=wl_compositor_create_surface(compositor);
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
top_role=xdg_surface_get_toplevel(top.xdg);
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
wl_proxy_get_id((struct wl_proxy *)top.wl),
wl_proxy_get_id((struct wl_proxy *)top.xdg),
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
wl_surface_commit(top.wl);
while (running) {
wl_display_flush(display);
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
{.fd=STDIN_FILENO,.events=POLLIN}};
int ready=poll(fds,2,500);
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
if (wl_display_dispatch(display)<0) break;
collect_dismissals();
}
if (fds[1].revents & POLLIN) {
char cmd[64];
if (fgets(cmd,sizeof(cmd),stdin)) {
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
}
}
}
destroy_popup(&b); destroy_popup(&a);
fprintf(stderr,"TOPLEVEL DESTROY\n");
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
wl_display_roundtrip(display);
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
wl_display_flush(display);
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
wl_display_disconnect(display);
return 0;
}

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position 0 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 600
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position 0 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 600
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,8 @@
output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
seat seat0 hide_cursor 100
shadows disable
blur disable

View file

@ -0,0 +1,234 @@
"""Bounded owned headless session, usable only after the separate matrix review gate."""
import json,os,re,shutil,signal,subprocess,tempfile,threading,time
from pathlib import Path
from PIL import Image,ImageDraw
from geometry import *
from gate import LAB,sha,clean_environment,action_debit,BASE
ROOT=LAB
import sys
from ipc_predicates import nodes,workspace
from predicates import protocol,geometry,pair,dismissal
import validators
from lifecycle import opening_pair
from model import Provenance
COLORS={'TOPLEVEL':(35,68,204),'POPUP_A':(224,192,32),'POPUP_B':(32,208,160)}
class Session:
def __init__(self,spec,auth):
self.spec=spec;self.auth=auth;self.folder=LAB/'graphics/runs'/spec['id'];self.run=self.folder/'run';self.run.mkdir(parents=True)
self.runtime=Path(tempfile.mkdtemp(prefix='m20-',dir='/tmp'));self.runtime.chmod(0o700)
self.env=clean_environment();self.env.update(XDG_RUNTIME_DIR=str(self.runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2')
self.outputs=[dict(o) for o in spec['outputs']];self.frames={o['name']:'logical' for o in self.outputs}
self.proc={};self.closed=[];self.handles=[];self.started=time.monotonic();self.finished=threading.Event();self.expired=False;self.events=[];self.pointer=None
def start(self):
spec=self.spec;auth=self.auth
cfg='\n'.join(f"output {o['name']} resolution {o['mode'][0]}x{o['mode'][1]} scale {o['scale']} transform {o['transform']} position {o['logical'][0]} {o['logical'][1]}" for o in self.outputs)
cfg+='\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
if spec['id'] in ('E2-fractional','E2-r90'):
marker=Image.new('RGB',(800,600),(30,30,46));draw=ImageDraw.Draw(marker)
for k,box in [('TL',(15,15,44,44)),('TR',(755,15,784,44)),('BL',(15,555,44,584)),('BR',(755,555,784,584)),('C',(385,285,414,314))]:draw.rectangle(box,fill=MARKERS[k])
marker.save(self.run/'marker-source.png');self.marker_source=self.run/'marker-source.png';self.record('generated-content-PNG',path=str(self.marker_source),counts_as_capture=False)
need(cfg==(BASE/'configs'/(spec['id']+'.conf')).read_text(),'sealed config bytes');(self.run/'config').write_text(cfg);self.watch=threading.Thread(target=self.watchdog,daemon=True);self.watch.start()
self.comp=self.spawn('compositor',[auth['binary'],'-d',*(['-Dtxn-timeout=5000'] if spec['id']=='S8-core-saved' else []),'-c',self.run/'config'])
self.sock=self.wait(lambda:next(iter(self.runtime.glob('sway-ipc.*.sock')),None));display=self.wait(lambda:next((p for p in self.runtime.glob('wayland-*') if p.is_socket()),None))
self.env.update(WAYLAND_DISPLAY=display.name,SWAYSOCK=str(self.sock));need(Path(f'/proc/{self.comp.pid}/exe').resolve()==Path(auth['binary']).resolve() and sha(Path(f'/proc/{self.comp.pid}/exe'))==auth['binary_sha256'],'actual executable')
maps=Path(f'/proc/{self.comp.pid}/maps').read_text();(self.run/'proc-maps.txt').write_text(maps)
paths={s.split()[-1] for s in maps.splitlines() if len(s.split())>=6 and s.split()[-1].startswith('/')}
wl=[Path(p).resolve() for p in paths if 'libwlroots' in p];need(wl==[Path(auth['wlroots'])] and sha(wl[0])==auth['wlroots_sha256'],'mapped wlroots')
scene=[Path(p).resolve() for p in paths if 'libscenefx' in p]
if spec['binary']=='core':need(not scene,'static SceneFX')
else:need(scene==[Path(auth['pristine_scene'])] and sha(scene[0])==auth['pristine_scene_sha256'],'mapped pristine SceneFX')
render=[s for s in self.log('compositor').splitlines() if any(x in s for x in ('Creating scenefx FX renderer','GL renderer:','GL vendor:','Using OpenGL ES'))]
need(any('Creating scenefx FX renderer' in s for s in render) and any('GL renderer:' in s for s in render),'actual renderer')
self.record('actual-renderer',lines=render);self.snapshot('startup')
if spec['id'] in ('E2-fractional','E2-r90'):
self.spawn('marker-background',['swaybg','-o','HEADLESS-1','-i',self.marker_source,'-m','fill']);time.sleep(.35)
im=self.capture('marker-H1','HEADLESS-1',orientation_pending=True);m=marker_frame(im,self.outputs[0]['scale'],self.outputs[0]['transform']=='90');self.frames['HEADLESS-1']=m['frame'];self.record('marker-frame',**m)
self.pointer=self.spawn('pointer',[LAB/'graphics/clients/pointer-daemon']);self.wait(lambda:'POINTER_READY' in self.log('pointer'))
def record(self,kind,**fields):
e=dict(kind=kind,ns=time.monotonic_ns(),**fields);self.events.append(e)
with (self.run/'events.jsonl').open('a') as f:f.write(json.dumps(e)+'\n')
def save(self,name,value):
(self.run/(name+'.json')).write_text(json.dumps(value,indent=2)+'\n')
def debit(self,*kinds):action_debit(self.spec['id'],*kinds)
def spawn(self,name,argv,debug=False):
need(not self.expired,'expired before spawn');f=(self.run/(name+'.log')).open('w');self.handles.append(f)
p=subprocess.Popen(list(map(str,argv)),stdin=subprocess.PIPE,stdout=f,stderr=subprocess.STDOUT,text=True,start_new_session=True,env={**self.env,**({'WAYLAND_DEBUG':'1'} if debug else {})})
self.proc[name]=p;self.record('spawn',name=name,pid=p.pid,argv=list(map(str,argv)));return p
def log(self,name):return (self.run/(name+'.log')).read_text(errors='replace')
def wait(self,fn,seconds=5):
end=min(self.started+143,time.monotonic()+seconds)
while time.monotonic()<end:
need(not self.expired,'watchdog')
if hasattr(self,'comp'):need(self.comp.poll() is None,'unexpected compositor exit')
v=fn()
if v:return v
time.sleep(.02)
raise Ambiguous('bounded wait timeout')
def watchdog(self):
while not self.finished.wait(.1):
if time.monotonic()-self.started>143 or any(p.stat().st_size>32*1024*1024 for p in self.run.glob('*.log')):
self.expired=True
for p in list(self.proc.values()):
try:os.killpg(p.pid,signal.SIGTERM)
except ProcessLookupError:pass
return
def ipc(self,*args,strict=True):
need(self.sock.is_relative_to(self.runtime) and self.sock.is_socket(),'private socket')
self.record('ipc-request',args=args)
p=subprocess.run(['swaymsg','-s',str(self.sock),*args],env=self.env,capture_output=True,text=True,timeout=4)
self.record('ipc-reply',args=args,exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
if strict:need(p.returncode==0,'IPC command failure')
return json.loads(p.stdout) if p.returncode==0 else None
def cmd(self,command,business=False):
self.debit('mutation' if business else 'configuration');reply=self.ipc(command);need(all(r['success'] for r in reply),'command rejected')
def snapshot(self,tag,validate=True):
snap=dict(tree=self.ipc('-t','get_tree'),outputs=self.ipc('-t','get_outputs'),workspaces=self.ipc('-t','get_workspaces'),ns=time.monotonic_ns())
if validate:expected_outputs(snap['outputs'],self.outputs)
self.save(tag+'-snapshot',snap);return snap
def find(self,name):
pid=self.proc[name].pid;rows=[n for n in nodes(self.ipc('-t','get_tree')) if n.get('pid')==pid];need(len(rows)<=1,'client owner ambiguity');return rows[0] if rows else None
def launch_client(self,name,client,*args):
p=self.spawn(name,[LAB/'graphics/clients'/client,*args],True);self.wait(lambda:self.find(name));time.sleep(.25);return p
def capture(self,tag,out,orientation_pending=False):
self.debit('capture');spec=next(o for o in self.outputs if o['name']==out);p=self.run/(tag+'.png');need(not p.exists(),'capture unique')
t=time.monotonic_ns();r=subprocess.run(['grim','-s',str(spec['scale']),'-o',out,str(p)],env=self.env,capture_output=True,text=True,timeout=4)
self.record('capture',tag=tag,output=out,start_ns=t,end_ns=time.monotonic_ns(),exit=r.returncode,path=str(p),sha256=sha(p) if p.exists() else None)
need(r.returncode==0,'capture failure');im=Image.open(p).convert('RGB')
logical=tuple(round(v*spec['scale']) for v in spec['logical'][2:]);physical=tuple(spec['mode'])
need(im.size==logical or (orientation_pending and im.size==physical) or (self.frames[out]=='physical90' and im.size==physical),'PNG size')
return im
def pair_capture(self,tag):return [self.capture(tag+'-H1','HEADLESS-1'),self.capture(tag+'-H2','HEADLESS-2')]
def inject(self,action,point,grab=False):
need(action in ('move','left','right'),'injection action');outs=self.ipc('-t','get_outputs');active=[o for o in outs if o['active']]
rects=[[o['rect'][k] for k in ('x','y','width','height')] for o in active];need(any(contains(r,point) for r in rects),'logical point outside output')
minx=min(r[0] for r in rects);miny=min(r[1] for r in rects);w=max(r[0]+r[2] for r in rects)-minx;h=max(r[1]+r[3] for r in rects)-miny
self.debit('move',*(['click'] if action!='move' else []),*(['grab'] if grab else []))
sent=[point[0]-minx,point[1]-miny];marker=f'INJECT_DONE {action} {sent[0]},{sent[1]}';before=self.log('pointer').count(marker)
self.pointer.stdin.write(f'{action} {sent[0]} {sent[1]} {w} {h}\n');self.pointer.stdin.flush();self.wait(lambda:self.log('pointer').count(marker)>before)
self.record('injection',action=action,global_logical=point,sent=sent,logical_extent=[w,h],grab=grab);time.sleep(.12)
def click(self,name,point,action='right',grab=False):
before={n:len(self.log(n)) for n in self.proc if n not in ('compositor','pointer','background')};t=time.monotonic_ns();self.inject(action,point,grab)
validators.window(t,time.monotonic_ns(),[e['ns'] for e in self.events if e['kind']=='injection' and e['ns']>=t]);tails={n:self.log(n)[i:] for n,i in before.items()};lines={n:[s for s in v.splitlines() if s.startswith(('POINTER_BUTTON ','BUTTON '))] for n,v in tails.items()}
self.record('click-observation',name=name,point=point,start_ns=t,tails=tails,buttons=lines)
return tails,lines
def delivery(self,name,rect,point,tails,lines,target,opening_serial=None):
need(not any(v for n,v in lines.items() if n!=name),'wrong client received popup button')
proof=opening_pair(lines[name],target,opening_serial) if opening_serial else pair(lines[name],target)
rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',tails[name]);need(rows,'fresh pointer local coordinates')
local=list(map(float,rows[-1]));validators.applied(rect[:2],rect[:2],[point[0]-local[0],point[1]-local[1]],'applied')
for e in proof:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, {e[2]}, {e[3]}\)',tails[name]) is not None,'button actual wire')
return proof
def owner(self,name):
snap=self.snapshot(name+'-owner')
rows=[n for n in nodes(snap['tree']) if n.get('pid')==self.proc[name].pid]
need(len(rows)==1,'owner identity/ambiguity');owner=rows[0]
ws=workspace(snap['tree'],'1')
path=__import__('ipc_predicates').ancestry(snap['tree'],owner['id'])
validators.owner_context(owner,path,self.proc[name].pid,'1','HEADLESS-1',getattr(self,'expected_layout','mosaic' if self.spec['mode']=='mosaic' else 'splith'))
# WHY: standard_return changes layout after the initial campaign mode.
# CONTRACT: camera applies only to current Mosaic tiled descendants;
# floating branches and fullscreen group ancestry remain exempt.
# WHY/CONTRACT: root/output/workspace IPC descriptors may have floating=null.
# INVARIANT: null means no floating flag; branch membership still counts,
# and this geometry remains a pending candidate, never native-applied.
eligible=validators.camera_eligible(path,ws)
origin=assigned_origin(owner);scene=exterior_ipc(owner)
if eligible:
scroll=int(ws['mosaic']['rows'][0]['scroll_x']);origin[0]-=scroll;scene[0]-=scroll
# INVARIANT: IPC never becomes native-applied without fresh paint and pointer corroboration.
raw='current-layout IPC candidate; native paint and fresh pointer-local corroboration required, generation NOT OBSERVABLE'
data=dict(geometry_status='IPC_PENDING_CANDIDATE; requires fresh paint/pointer after every mutation',owner=owner,workspace=ws,origin=origin,scene=scene,evidence=raw,snapshot=snap);self.save(name+'-geometry',data);return data
def calibrate(self,name):
state=self.owner(name)
if self.spec['mode']=='mosaic' and self.spec['axis']==0:
delta=int(state['origin'][0]+state['owner']['window_rect']['width']//2)
if delta:self.scroll(name,delta)
state=self.owner(name);need(crossing([*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']],0,0),'owner real x0 crossing')
return state
def scroll(self,name,delta):
self.debit('camera');self.cmd(f'[con_id={self.find(name)["id"]}] layout mosaic scroll {int(delta)}');time.sleep(.35)
self.record('camera',owner=self.find(name)['id'],delta=int(delta))
def client_cmd(self,name,cmd,marker=None):
self.debit('configuration' if cmd.startswith('close') else 'mutation');before=len(self.log(name));self.record('client-request',name=name,command=cmd)
self.proc[name].stdin.write(cmd+'\n');self.proc[name].stdin.flush()
if marker:self.wait(lambda:marker in self.log(name)[before:])
def close_client(self,name,command='close_owner'):
# WHY: exit0 alone cannot prove SHM release or an explicit normal close.
# CONTRACT: retain each PID's validation, including retired same-name owners.
# INVARIANT: a failed closure is durable evidence, never laundered by cleanup.
p=self.proc[name];proof=dict(name=name,pid=p.pid,status='INCONCLUSIVE',failure=None)
try:
if p.poll() is None:self.client_cmd(name,command)
code=p.wait(timeout=3);raw=self.log(name)
archive=self.run/f'{name}-closed-{p.pid}.log';archive.write_text(raw)
proof.update(exit=code,log=str(archive),sha256=sha(archive),close_received='CLOSE_RECEIVED' in raw,shm_closure=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',raw),wayland_errors=re.findall(r'EXIT[^\n]*error=(\d+)',raw))
validators.client_exit(raw,code);self.wait(lambda:self.find(name) is None)
proof['status']='PASS'
except Exception as error:
proof['failure']=f'{type(error).__name__}: {error}'
proof.setdefault('exit',p.poll())
raise
finally:
self.closed.append(proof);self.record('client-closed',**proof)
def cleanup(self,measurement_status,failure=None):
terminal=None;rescue=False;closures=[]
# Close test clients while their Wayland server still exists. Mechanical
# rescue and terminal IPC can never substitute for scientific closure.
for name in reversed(list(self.proc)):
if name in ('compositor','pointer') or any(c['pid']==self.proc[name].pid for c in self.closed):continue
try:
self.close_client(name,'close_owner' if name=='owner' and self.spec['client']!='saved-client' and self.spec['id']!='S8-core-saved' else 'close')
except Exception:pass # close_client retains the explicit validation failure.
# WHY/CONTRACT/INVARIANT: archived and already-exited clients count equally;
# an empty list of newly closed processes must not erase prior failures.
closures=[dict(c) for c in self.closed]
tracked=[p.pid for n,p in self.proc.items() if n not in ('compositor','pointer')]
closure_coverage=all(any(c['pid']==pid for c in closures) for pid in tracked)
if hasattr(self,'comp') and self.comp.poll() is None and hasattr(self,'sock') and self.sock.is_socket():
try:
p=subprocess.run(['swaymsg','-s',str(self.sock),'exit'],env=self.env,capture_output=True,text=True,timeout=2)
terminal=dict(ipc_exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
self.save('terminal-ipc',terminal)
except Exception as e:
terminal=dict(ipc_error=str(e));self.save('terminal-ipc',terminal)
if terminal is not None and 'ipc_exit' in terminal:
try:self.comp.wait(timeout=1)
except Exception as e:
terminal['wait_error']=str(e);self.save('terminal-ipc',terminal)
codes=[dict(name=c['name'],pid=c['pid'],exit=c['exit']) for c in self.closed if not any(p.pid==c['pid'] for p in self.proc.values())]
# One shared cleanup deadline prevents N owned clients multiplying waits.
cleanup_end=time.monotonic()+2
if self.pointer and self.pointer.poll() is None and self.pointer.stdin and not self.pointer.stdin.closed:self.pointer.stdin.close()
for name,p in reversed(list(self.proc.items())):
if p.poll() is None:
try:p.wait(timeout=max(.01,cleanup_end-time.monotonic()))
except subprocess.TimeoutExpired:pass
residual=[p for p in self.proc.values() if p.poll() is None]
if residual:
rescue=True
for p in residual:
try:os.killpg(p.pid,signal.SIGKILL)
except ProcessLookupError:pass
kill_end=time.monotonic()+1
for p in residual:p.wait(timeout=max(.01,kill_end-time.monotonic()))
for name,p in self.proc.items():
codes.append(dict(name=name,pid=p.pid,exit=p.returncode))
if p.stdin and not p.stdin.closed:p.stdin.close()
self.finished.set();self.watch.join(timeout=1)
for h in self.handles:h.close()
for p in self.proc.values():
try:os.killpg(p.pid,0)
except ProcessLookupError:pass
else:rescue=True;os.killpg(p.pid,signal.SIGKILL)
reaped=all(not Path('/proc',str(p.pid)).exists() for p in self.proc.values());need(reaped,'owned process residual');shutil.rmtree(self.runtime)
seconds=time.monotonic()-self.started
protocol_errors=[n for n in self.proc if n not in ('compositor','pointer') and re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',self.log(n),re.I)]
cleanup_pass=bool(closure_coverage and all(c['status']=='PASS' for c in closures) and not rescue and reaped and seconds<=150 and not self.expired and not protocol_errors and all(p['exit']==0 for p in codes))
transport_pass=terminal is not None and terminal.get('ipc_exit') in (0,1) and 'wait_error' not in terminal and 'ipc_error' not in terminal
final_status=measurement_status if failure is None and cleanup_pass and transport_pass else 'INCONCLUSIVE'
self.save('outcome',dict(status=final_status,measurements_pass=measurement_status,architecture_verdict='INCONCLUSIVE',evidence_provenance=self.auth,untested_matrix_cells=['ATOMIC_GENERATION','COMPLETE_DAMAGE','NATIVE_BUFFER_IDENTITY','GPU_LIFETIME'],scientific_cleanup='PASS' if cleanup_pass else 'INCONCLUSIVE',runtime_exit=codes,measurements_status=measurement_status,failure=failure,terminal=terminal,explicit_client_closures=self.closed,mechanical_cleanup='PASS' if reaped and not rescue and not self.runtime.exists() else 'INCONCLUSIVE',client_closures=closures,processes=codes,rescue=rescue,reaped=reaped,runtime_removed=not self.runtime.exists(),seconds=seconds,watchdog=self.expired,protocol_errors=protocol_errors,cleanup_pass=cleanup_pass,transport_status='PASS' if transport_pass else 'INCONCLUSIVE'))
return final_status

View file

@ -0,0 +1,61 @@
"""WHY/CONTRACT/INVARIANT: ticket counters are durable, never refunded."""
import fcntl,hashlib,json,os,time,uuid
from pathlib import Path
LAB=Path('/home/fy59/swayfx-mosaic-core-recovery-qmhckba5')
BASE=LAB/'graphics'
TICKET='SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1'
WL=Path('/usr/lib/libwlroots-0.20.so')
WL_SHA='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
def need(v,m):
if not v:raise ValueError(m)
def clean_environment():
e=os.environ.copy()
for k in list(e):
if k in ('DISPLAY','WAYLAND_DISPLAY','SWAYSOCK','XDG_RUNTIME_DIR','LD_PRELOAD','LD_LIBRARY_PATH','DBUS_SESSION_BUS_ADDRESS','XDG_CURRENT_DESKTOP','DESKTOP_SESSION','XDG_ACTIVATION_TOKEN','DESKTOP_STARTUP_ID') or k.startswith(('WLR_','PI_','OPT_','SAVED_','XDG_SESSION_')):e.pop(k,None)
return e
def durable(p,value,exclusive=False):
p=Path(p);tmp=p.with_name(p.name+'.tmp-'+uuid.uuid4().hex)
with tmp.open('x') as f:json.dump(value,f,indent=2);f.write('\n');f.flush();os.fsync(f.fileno())
if exclusive:
os.link(tmp,p);tmp.unlink()
else:os.replace(tmp,p)
fd=os.open(p.parent,os.O_DIRECTORY);os.fsync(fd);os.close(fd)
def transact(fn,path=LAB/'budget.json'):
# Lock inode stays stable while JSON replacement is atomic.
with Path(str(path)+'.lock').open('a') as lock:
fcntl.flock(lock,fcntl.LOCK_EX);d=json.loads(Path(path).read_text());need(d['ticket']==TICKET,'old ticket');r=fn(d);durable(path,d);return r
def validate_review(manifest,review):
need(review.get('decision')=='ACCEPTED' and review.get('independent') is True,'independent review absent')
need(review.get('manifest_sha256')==sha(manifest),'stale review')
m=json.loads(Path(manifest).read_text());need(m['ticket']==TICKET and m['lab']==str(LAB),'wrong lab/ticket')
for p,h in m['files'].items():need(sha(p)==h,'frozen bytes changed: '+p)
need(sha(WL)==WL_SHA,'wlroots changed')
return m
def begin(rid):
from plans import RUNS
m=validate_review(BASE/'pre-run-manifest.json',json.loads((BASE/'review.json').read_text()))
need(rid in m.get('authorized_runs',[]),'family not reviewed/authorized')
need(not (BASE/'runs'/rid/'run').exists(),'pre-existing run folder')
spec=RUNS[rid];auth=m['identities'][spec['binary']];need(sha(auth['binary'])==auth['binary_sha256'],'binary changed')
def reserve(d):
need(d['limits']['runs']==11 and d['limits']['captures']==160 and d['limits']['captures_per_run']==18 and d['limits']['duration_s']==150,'wrong ticket limits')
need(not any(r['id']==rid for r in d['runs']),'consumed run')
need(len(d['runs'])<11 and sum(r.get('capture_limit',r.get('captures',0)) for r in d['runs'])+spec['capture_limit']<=160,'global budget')
need(sum(r.get('campaign')=='S' for r in d['runs'])<9,'S run budget')
row=dict(id=rid,campaign='S',token=uuid.uuid4().hex,started_ns=time.monotonic_ns(),capture_limit=spec['capture_limit'],actions={},status='RESERVED')
d['runs'].append(row);return row
row=transact(reserve);durable(BASE/'runs'/rid/'reservation.json',row,True);return auth
def action_debit(rid,*kinds):
from plans import RUNS
def update(d):
rows=[r for r in d['runs'] if r['id']==rid];need(len(rows)==1,'reservation missing');r=rows[0]
for k in kinds:
limit=RUNS[rid]['capture_limit'] if k=='capture' else RUNS[rid]['action_limits'][k]
need(r['actions'].get(k,0)<limit,'local '+k+' exhausted');r['actions'][k]=r['actions'].get(k,0)+1
transact(update)

View file

@ -0,0 +1,62 @@
"""Explicit logical global, IPC assignment, physical mode and PNG-frame adapters."""
from dataclasses import dataclass
import re
from PIL import Image
class Ambiguous(ValueError):pass
def need(ok,msg):
if not ok:raise Ambiguous(msg)
def box_end(r):return [r[0]+r[2],r[1]+r[3]]
def contains(r,p):return r[0]<=p[0]<r[0]+r[2] and r[1]<=p[1]<r[1]+r[3]
def crossing(r,axis,boundary):return r[axis]<boundary<r[axis]+r[axis+2]
def intersection(a,b):
x=max(a[0],b[0]);y=max(a[1],b[1]);return [x,y,max(0,min(a[0]+a[2],b[0]+b[2])-x),max(0,min(a[1]+a[3],b[1]+b[3])-y)]
def assigned_origin(owner):
# IPC rect already excludes top decoration (ipc-json.c899–906).
return [owner['rect']['x']+owner['window_rect']['x'],owner['rect']['y']+owner['window_rect']['y']]
def exterior_ipc(owner):
r=owner['rect'];d=owner['deco_rect']['height'];return [r['x'],r['y']-d,r['width'],r['height']+d]
MARKERS={'TL':(255,32,32),'TR':(32,255,32),'BL':(32,32,255),'BR':(255,255,32),'C':(192,32,255)}
def marker_frame(image,scale,rotated):
positions={k:[] for k in MARKERS};lookup={v:k for k,v in MARKERS.items()}
for y in range(image.height):
for x in range(image.width):
k=lookup.get(image.getpixel((x,y)))
if k:positions[k].append((x,y))
need(all(len(v)>=100 for v in positions.values()),'marker color missing')
c={k:[sum(p[i] for p in v)/len(v) for i in (0,1)] for k,v in positions.items()}
logical=image.size==(round(800*scale),round(600*scale)) and c['TL'][0]<c['TR'][0] and c['TL'][1]<c['BL'][1]
physical=rotated and image.size==(round(600*scale),round(800*scale)) and c['TL'][0]>c['BL'][0] and c['TL'][1]<c['TR'][1]
need(logical or physical,'PNG orientation unestablished')
return dict(frame='logical' if logical else 'physical90',centers=c,counts={k:len(v) for k,v in positions.items()},size=list(image.size))
def png_global(px,output,frame):
scale=output['scale'];x,y=px;ox,oy,w,h=output['logical']
if frame=='physical90':x,y=y,round(h*scale)-1-x
need(frame in ('logical','physical90'),'unknown PNG frame')
# Sample integer logical centers only; resample at the chosen center later.
return [ox+int(x/scale),oy+int(y/scale)]
def global_png(p,output,frame):
x=(p[0]-output['logical'][0])*output['scale'];y=(p[1]-output['logical'][1])*output['scale']
if frame=='physical90':x,y=output['logical'][3]*output['scale']-1-y,x
return [round(x),round(y)]
def solid_point(image,color,output,frame,rectangle=None,margin=3):
for y in range(margin,image.height-margin,3):
for x in range(margin,image.width-margin,3):
if image.getpixel((x,y))!=color:continue
p=png_global((x,y),output,frame)
if rectangle is not None and not contains(rectangle,p):continue
pp=global_png(p,output,frame)
if not (margin<=pp[0]<image.width-margin and margin<=pp[1]<image.height-margin):continue
if all(image.getpixel((xx,yy))==color for yy in range(pp[1]-margin,pp[1]+margin+1) for xx in range(pp[0]-margin,pp[0]+margin+1)):
return p
raise Ambiguous('no measured solid logical interior')
def count(image,color):return sum(v==color for v in image.get_flattened_data())
def exact(a,b):need(a.size==b.size and a.tobytes()==b.tobytes(),'whole RGB differs');return dict(equal=True,pixels=a.width*a.height)
def expected_outputs(outputs,spec):
active=[o for o in outputs if o['active']];need(len(active)==2,'active output count')
for want in spec:
rows=[o for o in active if o['name']==want['name']];need(len(rows)==1,'output identity')
o=rows[0];need([o['rect'][k] for k in ('x','y','width','height')]==want['logical'],'logical output geometry')
need(o['scale']==want['scale'] and o['transform']==want['transform'],'scale/IPC transform string')
need([o['current_mode'][k] for k in ('width','height')]==want['mode'],'physical mode')
return active

View file

@ -0,0 +1,21 @@
"""Pure tree identity helpers only; IPC geometry is pending candidate."""
def nodes(node):
yield node
for child in node.get('nodes', []) + node.get('floating_nodes', []):
yield from nodes(child)
def workspace(tree, name):
rows = [n for n in nodes(tree) if n.get('type') == 'workspace' and n.get('name') == name]
assert len(rows) == 1, 'workspace identity'
return rows[0]
def ancestry(tree, con_id, path=()):
if tree.get('id') == con_id:
return path + (tree,)
for child in tree.get('nodes', []) + tree.get('floating_nodes', []):
found = ancestry(child, con_id, path + (tree,))
if found:
return found
return ()

View file

@ -0,0 +1,64 @@
"""Pure E2 input/lifecycle predicates shared with offline negative tests."""
import re
from model import require
from predicates import COLORS, paint, equal, pair, point
def opening_pair(lines,target,serial):
actual=[]
for line in lines:
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line)
require(m is not None,'opening-button-format');actual.append([m[1],*map(int,m.groups()[1:])])
require(len(actual)==2 and all(e[0]==target for e in actual) and
[e[2:] for e in actual]==[[272,1],[272,0]] and actual[0][1]==serial and
0<actual[0][1]<actual[1][1]<=0xffffffff,'real-left-opening-pair')
return actual
def explicit_child_close(before,after,ids,request_ns,done_ns):
"""Client destroy must follow a recorded explicit request; no popup_done."""
require(type(request_ns) is int and request_ns<done_ns,'explicit-close-request-order')
require(after.startswith(before),'append-only-client-protocol')
tail=after[len(before):]
require('POPUP_DONE ' not in after,'close-must-not-be-dismissal')
b=ids['POPUP_B'];a=ids['POPUP_A']
destroy=f'POPUP_B DESTROY role={b[2]} xdg={b[1]} wl={b[0]}'
require(tail.count(destroy)==1 and tail.count('COMMAND close_b DONE')==1 and
tail.index(destroy)<tail.index('COMMAND close_b DONE'),'explicit-child-destroy-DONE')
for obj,stem in [(b[2],'xdg_popup'),(b[1],'xdg_surface'),(b[0],'wl_surface')]:
require(tail.count(f'{stem}#{obj}.destroy()')==1,'child-protocol-destroy')
require(after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1 and
'POPUP_A DESTROY ' not in after and 'TOPLEVEL DESTROY' not in after,'same-A-remains-mapped')
return dict(status='PASS',request_ns=request_ns,done_ns=done_ns,B=b,A=a,
meaning='explicit client close, no compositor popup_done')
def a_survival(image,before,after,ids,buttons,global_point,rect):
a=ids['POPUP_A']
require(paint(image,'POPUP_B')['count']==0 and paint(image,'POPUP_A')['count']>0,'child-zero-residual-A-painted')
require(before.startswith('') and after.startswith(before),'A-input-protocol-appended')
require('POPUP_A DESTROY ' not in after and 'POPUP_A POPUP_DONE ' not in after and
after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1,'same-A-live-after-input')
require(rect[0]<=global_point[0]<rect[0]+rect[2] and rect[1]<=global_point[1]<rect[1]+rect[3], 'A-recomputed-point-bounds')
return dict(status='PASS',pair=pair(buttons,'POPUP_A'),ids=a,point=global_point)
def normal_owner_close(before,after,ids,code):
require(code==0 and after.startswith(before),'normal-client-exit')
tail=after[len(before):]
require(tail.count('COMMAND close_owner')==1 and tail.count('TOPLEVEL DESTROY')==1 and
'POPUP_DONE ' not in tail,'normal-owner-close-command')
a=ids['POPUP_A'];top=ids['TOPLEVEL']
require(tail.count(f'POPUP_A DESTROY role={a[2]} xdg={a[1]} wl={a[0]}')==1 and
tail.index('COMMAND close_owner')<tail.index('POPUP_A DESTROY ')<tail.index('TOPLEVEL DESTROY'),
'normal-owner-child-first-destruction')
for obj,stem in [(a[2],'xdg_popup'),(a[1],'xdg_surface'),(a[0],'wl_surface'),
(top[2],'xdg_toplevel'),(top[1],'xdg_surface'),(top[0],'wl_surface')]:
require(tail.count(f'{stem}#{obj}.destroy()')==1,'owner-protocol-destroy')
return dict(status='PASS',client_exit=code,ids=ids)
def final_restoration(baseline,final):
cmp=equal(baseline,final)
require(cmp['equal'] and all(paint(final,n)['count']==0 for n in ('POPUP_A','POPUP_B')),'whole-H2-final-exact')
return dict(status='PASS',comparison=cmp)

View file

@ -0,0 +1,194 @@
"""EXPERIMENTAL typed half-open geometry. Pure: no processes or sockets.
Source contracts are recorded in the offline gate, not inferred from RGB.
Every conversion is explicit and keeps assignment/owner/generation provenance.
"""
from dataclasses import dataclass
from enum import Enum
class Invalid(ValueError):
pass
class Space(str, Enum):
NATIVE = 'NativeSHM'
CONFIGURE = 'XDGConfigure'
CONTENT = 'AppliedContent'
GLOBAL = 'SceneGlobal'
POPUP = 'PopupLocal'
VIEWPORT = 'GlobalViewport'
PNG = 'OutputPNG'
def require(value, contract):
if not value:
raise Invalid(contract)
@dataclass(frozen=True)
class Provenance:
owner: int
workspace: int
generation: str
evidence: str
state: str = 'applied'
@dataclass(frozen=True)
class Rect:
space: Space
x: int
y: int
width: int
height: int
provenance: Provenance
def __post_init__(self):
require(isinstance(self.space, Space), 'tag-required')
require(all(type(v) is int for v in (self.x, self.y, self.width, self.height)), 'integer-geometry')
require(self.width >= 0 and self.height >= 0, 'nonnegative-size')
require(bool(self.provenance.evidence), 'evidence-required')
@property
def end(self):
return (self.x + self.width, self.y + self.height)
def contains(self, xy):
return self.x <= xy[0] < self.end[0] and self.y <= xy[1] < self.end[1]
def values(self):
return [self.x, self.y, self.width, self.height]
@dataclass(frozen=True)
class Native:
rectangle: Rect
buffer_id: int
stride: int
format: int
commit_serial: int
@dataclass(frozen=True)
class Applied:
scene: Rect
window: Rect
configured: Rect
native: Native
border_left: int
border_top: int
titlebar: int
configure_ack: int
view_offset: tuple = (0, 0)
@dataclass(frozen=True)
class Output:
name: str
rectangle: Rect
scale: float
transform: str
def same(a, b):
require(a.owner == b.owner, 'owner-identity')
require(a.workspace == b.workspace, 'workspace-identity')
require(a.generation == b.generation, 'applied-generation')
require(a.state == b.state == 'applied', 'pending-not-applied')
def content(applied):
s, w, c, n = applied.scene, applied.window, applied.configured, applied.native
require(s.space is Space.GLOBAL and w.space is Space.CONTENT and c.space is Space.CONFIGURE,
'assignment-spaces')
require(n.rectangle.space is Space.NATIVE, 'native-space')
for r in (w, c, n.rectangle):
same(s.provenance, r.provenance)
require(n.buffer_id > 0 and n.stride == n.rectangle.width * 4 and n.format in (0, 1), 'native-buffer-identity')
require(0 < n.commit_serial <= 0xffffffff, 'commit-serial')
require(0 < applied.configure_ack <= 0xffffffff, 'assigned-configure-ack')
require((w.width, w.height) == (c.width, c.height), 'configure-assignment')
require(applied.border_left == w.x and applied.border_top == applied.titlebar + w.y,
'titlebar-content-offset')
require(w.width + 2 * applied.border_left == s.width and
w.height + applied.border_top + applied.border_left == s.height, 'assigned-destination')
return Rect(Space.GLOBAL, s.x + applied.border_left + applied.view_offset[0],
s.y + applied.border_top + applied.view_offset[1], w.width, w.height, s.provenance)
def intersect(rect, boundary):
require(rect.space is Space.GLOBAL and boundary.space in (Space.GLOBAL, Space.VIEWPORT), 'global-intersection')
same(rect.provenance, boundary.provenance)
x, y = max(rect.x, boundary.x), max(rect.y, boundary.y)
return Rect(Space.GLOBAL, x, y, max(0, min(rect.end[0], boundary.end[0]) - x),
max(0, min(rect.end[1], boundary.end[1]) - y), rect.provenance)
def to_png(rect, output):
require(rect.space is Space.GLOBAL and output.rectangle.space is Space.GLOBAL, 'global-to-png-spaces')
require(output.scale == 1.0, 'unmeasured-output-scale')
require(output.transform == 'normal', 'unmeasured-output-transform')
same(rect.provenance, output.rectangle.provenance)
visible = intersect(rect, output.rectangle)
return Rect(Space.PNG, min(max(visible.x - output.rectangle.x,0),output.rectangle.width), min(max(visible.y - output.rectangle.y,0),output.rectangle.height),
visible.width, visible.height, rect.provenance)
def from_png(rect, output):
require(rect.space is Space.PNG and output.rectangle.space is Space.GLOBAL, 'png-to-global-spaces')
require(output.scale == 1.0 and output.transform == 'normal', 'unmeasured-png-transform')
same(rect.provenance, output.rectangle.provenance)
require(0 <= rect.x <= rect.end[0] <= output.rectangle.width and
0 <= rect.y <= rect.end[1] <= output.rectangle.height, 'png-bounds')
return Rect(Space.GLOBAL, rect.x + output.rectangle.x, rect.y + output.rectangle.y,
rect.width, rect.height, rect.provenance)
def verify_paint(rect, output, bbox, count, clip=None):
expected = to_png(intersect(rect, clip) if clip is not None else rect, output)
actual = [] if bbox is None else list(bbox)
box=[expected.x, expected.y, *expected.end] if expected.width and expected.height else []
require(actual == box, 'paint-origin-intersection')
require(count == expected.width * expected.height, 'paint-area')
return expected
def popup(parent, configured, parent_xdg, expected_parent_xdg, ack, mapped):
require(parent.space is Space.GLOBAL and configured.space is Space.POPUP, 'popup-spaces')
same(parent.provenance, configured.provenance)
require(parent_xdg == expected_parent_xdg and expected_parent_xdg > 0, 'xdg-parent')
require(0 < ack <= 0xffffffff, 'popup-ack')
require(mapped is True, 'popup-map')
return Rect(Space.GLOBAL, parent.x + configured.x, parent.y + configured.y,
configured.width, configured.height, parent.provenance)
def crossing(rect, boundary_x):
require(rect.space is Space.GLOBAL, 'crossing-space')
return 'EXERCISED' if rect.x < boundary_x < rect.end[0] else 'NOT EXERCISED'
def local_clip_to_global(local, origin, provenance):
require(local.space is Space.CONTENT, 'clip-local-space')
same(local.provenance, provenance)
return Rect(Space.VIEWPORT, local.x + origin[0], local.y + origin[1],
local.width, local.height, provenance)
def pointer_local(xy, rect, actual):
require(rect.space is Space.GLOBAL and rect.contains(xy), 'pointer-global-bounds')
require(list(actual) == [xy[0] - rect.x, xy[1] - rect.y], 'pointer-local-correlation')
return list(actual)
def fresh_chain(old, new):
require(old['owner'] != new['owner'] and old['pid'] != new['pid'], 'fresh-E2-identity')
require(len(set(old['serials'] + new['serials'])) == 4 and
max(old['serials']) < min(new['serials']), 'fresh-E2-serials')
def fresh_standard(mosaic, standard):
require(mosaic.provenance.owner == standard.provenance.owner and
mosaic.provenance.workspace == standard.provenance.workspace, 'standard-owner')
require(mosaic.provenance.generation != standard.provenance.generation, 'fresh-standard-generation')
return standard
def constraints(flags, requested, configured):
require(type(flags) is int and 0 <= flags <= 63, 'positioner-flags')
require(flags != 0 or requested == configured, 'zero-flags-no-adjustment')
return {'flags': flags, 'request': requested, 'actual': configured, 'prediction_only': True}

View file

@ -0,0 +1,56 @@
{
"status": "EXPERIMENTAL",
"parent_commit": "70954e26f62fb3df25995e31b0c615762ff85a60",
"parent_directory": "tooling/probes/step20c-minimal-core-gate/graphics",
"files": {
"cases.py": "c3811fcad57f319500aad3643f000b704c25a89b44ac1ed05ec5f05d8c750589",
"engine.py": "b8abebd09659be1868ab0f66f3e33bb6d38d50b163d2754900d7a324343673bd",
"fx.py": "0a016c0f97c96344e2c62bf24a5533d315c6cc16d628cd840887d50362ffb8cf",
"fx_validators.py": "3a46ff7ab5dd1e45941c0c30d944ff0763e4426e70cf762571b1f06c75f2e8f0",
"gate.py": "d37118314d022b0049df748d4ed4ec127c238dd8fd2f1340faee21ddc20bc3ab",
"geometry.py": "76a80e98887d3a3707ee942b48a49c1850cdc4aeeb28237d9fea33fa3470fb12",
"ipc_predicates.py": "6d5a25c225685adf24978a8bd9b4a6de44a369af6620d10db95accacc892b6f8",
"lifecycle.py": "df0c11027b3b8b0ef371ddb6ec4bcfd28dac09835644d0c3ce6e273d5e565e92",
"model.py": "a578fc589fb092dae3ccdcf8c4eec12a97e515f721decc4c968078dedcb2367a",
"pixels.py": "d315ddad978238ffbf1c8e4d712d4608ba23564703332d814f627f57f7b6e7f5",
"plans.py": "fbacf2fde781b53a8a5c5fd1b7475c310621f1cd635fe7e397d048ea5a5268ea",
"predicates.py": "d1d3505d9553444a3c6ec4b6c47eed817adac0451db1518ee15e33e0683c12fb",
"prepare.py": "e6094b2f720962b83106717c1c76ac23098051778c4df43064e6c0147645d321",
"run.py": "4503434f912ff8623d02ef897b0f702b61510ff7b3cc5a464734b09240ab8b8a",
"saved.py": "ea70128504805b780bf0d59c5157c6fbb03fe7597b67062dda90103fb42109e6",
"saved_validators.py": "02c848d8eb8e2b7ffae66362040ce7378bbcf0d94db2457e769e0286edb01eea",
"seal.py": "8203b28c17b7244e9ec4fd8989c3207b99532faa963b3421aebd87a6f9e00f11",
"test_fixture_repair_R1.py": "56d5c7410ee5de522f183225fc72eef2859d004a171632adefc8479686cc4b7b",
"test_fixture_repair_R2.py": "75a4963c6a8509dce197034d6bf01d46ef9bf8d5a3e30414d8bd0203c56b7fc0",
"test_graphics.py": "45ba9eb7229bfb7f87742733853e43c12b6d54d5e3ffbda14d4a76111601194b",
"test_workspace_marker_R2B.py": "7af7ab259a372f4df8a21719abe5a0c197c6641175aeac40671bd3df3be1d9d5",
"validators.py": "9834bc25e7891c341a714852a569d8c5f04c01e56b4fca6f1605713f7e9ac94b",
"wire-fixture.txt": "c6c9fc072f78fbcf9cfdeebcc5becdbdfb61824352495a0e767d7d390267f640",
"configs/E0-core.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E0-pristine.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E1.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E2-fractional.conf": "c9f351c52645e2cb502693da4611830db556dad40e8505e148bc716b5f89bfec",
"configs/E2-r90.conf": "4affb6b0ad280d7166f63239708847bf184fadb492de50ce3155d2f66c3b99c9",
"configs/E2-vertical.conf": "d3e22dc4565af0e21e6180fa4a37a35b92a0f8260d5873719359f04fc7933e95",
"configs/E3.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E4.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E5.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E6.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"configs/E7.conf": "caf7cdff074714cb5b2ba35827558ce5a7defda125de034a7d42296f03b0528b",
"clients/fx-client.c": "f1abd428f6b35cea4ad3a82f30e39d79314324ce9652556e11118666ad2453a4",
"clients/fx-clients.c": "cc913ff83ce20e430d17eda99504278c8cacafb56b81cb44d7bd99ad3f2cf31a",
"clients/layer-shell-client.h": "bd359658282daf42b2f3270f2f670ae7d1d045ea47e3410bd3fdd012c8b47237",
"clients/layer-shell-protocol.c": "699dabe913a95113f56f82c87f59b3748cdfd959c17c45f39073b53cb2af998e",
"clients/nested-client.c": "a11c9b3c6599e5c08b8e11917d85b7bfa0457f1fea0161ce8230faa9922d0c6c",
"clients/pointer-daemon.c": "8e4eaad95882d0d73866f801899007240b01cb6e15a712e820b3e3f71a78bbe5",
"clients/region-client.c": "51564a0eebf8b99ea1947e2699bdd5b6073c3f37ce3b4165ba500171783a30b6",
"clients/saved-client.c": "f4d462b9eb9406e336f5613bdc347fe9cf9c28c06aae28dad95b6b9e556b1e3f",
"clients/static-reference.c": "e346a85ded3345b232cd6edca5a1ac57aebfd548a60e12b3c47a5cf515e6c50e",
"clients/vertical-client.c": "f56151d4c34c577a9a5e82dda2bfccfc0febbce1cda741eea32d9658904905f9",
"clients/virtual-pointer-client.h": "0dcb6107cf948a007dcef0a6b2192e4f7b9166c0f57b499ca49b7aaf48adc56c",
"clients/virtual-pointer-protocol.c": "a72e9b38dd5ed0c49fae22aa2f01883aea76075ad33f6399d9b2033df4330675",
"clients/wlr-layer-shell-unstable-v1.xml": "996b8c66e1c2276c443336d4cb174634e8e5948823779042f244930525b79cc2",
"clients/xdg-shell-client.h": "a09726137ea9fe1a23d58c95f1d33ab77a3fc692c63726bc82fbc91ab8e5c6fc",
"clients/xdg-shell-protocol.c": "9e12fcd5676d8c6b6cfd233c2eba563163c0ea7e586cb6e27f202a328323c7f0"
}
}

View file

@ -0,0 +1,21 @@
"""Exact binary RGB masks with explicit output origin, clip and occlusion.
Screenshots prove color/visible geometry only, never native-buffer identity.
"""
from PIL import Image,ImageChops,ImageDraw
from model import *
COLORS=dict(TOPLEVEL=(35,68,204),POPUP_A=(224,192,32),POPUP_B=(32,208,160))
def color_geometry(im,target,rect,output,clip,occluders=()):
require(im.size==(output.rectangle.width,output.rectangle.height),'replay-output-size')
visible=intersect(rect,clip) if clip else rect
expected=Image.new('L',im.size,0);draw=ImageDraw.Draw(expected)
box=to_png(visible,output)
if box.width and box.height:draw.rectangle((box.x,box.y,box.end[0]-1,box.end[1]-1),fill=255)
for occ in occluders:
ob=to_png(occ,output)
if ob.width and ob.height:draw.rectangle((ob.x,ob.y,ob.end[0]-1,ob.end[1]-1),fill=0)
actual=Image.new('L',im.size);actual.putdata([255 if px==COLORS[target] else 0 for px in im.get_flattened_data()])
require(ImageChops.difference(expected,actual).getbbox() is None,'replay-paint-'+target)
return dict(count=sum(actual.histogram()[1:]),bbox=actual.getbbox())

View file

@ -0,0 +1,8 @@
"""Prospective recovery matrix; historical E reservations are closed."""
def out(name,logical):
return dict(name=name,logical=logical,mode=logical[2:],scale=1,transform='normal')
H=[out('HEADLESS-1',[-800,0,800,600]),out('HEADLESS-2',[0,0,800,600])]
CAP={'S0-pristine-standard':12,'S1-core-standard':12,'S2-core-horizontal':18,'S3-pristine-vertical':12,'S4-core-vertical':12,'S5-pristine-fullscreen':14,'S6-core-fullscreen':14,'S7-core-hidden-return':14,'S8-core-saved':12}
RUNS={rid:dict(id=rid,binary='pristine' if 'pristine' in rid else 'core',mode='pristine' if 'standard' in rid or 'pristine' in rid else 'mosaic',outputs=H,client='nested-client',placement='static_cross',axis=0,boundary=0,capture_limit=cap,action_limits=dict(configuration=60,mutation=24,move=20,click=12,grab=4,camera=6,chain=2)) for rid,cap in CAP.items()}
for rid in ('S3-pristine-vertical','S4-core-vertical'):
RUNS[rid].update(outputs=[out('HEADLESS-1',[0,0,800,600]),out('HEADLESS-2',[0,600,800,600])],client='vertical-client',placement='v_both_cross',axis=1,boundary=600)

View file

@ -0,0 +1,124 @@
"""Shared offline/live predicates: facts precede scientific classification.
CONTRACT: full RGB comparisons have no mask/tolerance; grabs require real logs.
INVARIANT: dismissal, client destruction, clipping and layout remain distinct.
"""
import re
from model import Rect,Space,Provenance,Output,popup,crossing,local_clip_to_global,require
from pixels import color_geometry,COLORS
from PIL import Image,ImageChops
class Defect(RuntimeError):
"""A directly measured scientific contract failure, distinct from fixture ambiguity."""
def paint(im,target):
im=im.convert('RGB');assert im.size==(800,600)
mask=Image.new('L',im.size);mask.putdata([255 if p==COLORS[target] else 0 for p in im.get_flattened_data()])
return dict(count=sum(mask.histogram()[1:]),bbox=mask.getbbox())
def point(im,target,origin):
box=paint(im,target)['bbox'];assert box,('no measured paint',target)
# WHY: A may be occluded by B; bbox center alone is not a valid hit point.
x1,y1,x2,y2=box
for y in range(y1+10,y2-10,5):
for x in range(x1+10,x2-10,5):
if all(im.getpixel((xx,yy))==COLORS[target] for yy in range(y-8,y+9) for xx in range(x-8,x+9)):
return [x+origin,y]
raise AssertionError('no solid measured interior '+target)
def equal(a,b):
assert a.size==b.size==(800,600)
rows=[(i%800,i//800,x,y) for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y]
return dict(equal=not rows,changed=len(rows),bbox=ImageChops.difference(a,b).getbbox(),pixels=rows)
def reference(a,b):
result=equal(a,b)
assert result['equal'] and all(c==(48,48,56) for c in a.get_flattened_data()),'unstable reference'
return result
def configured(text,name):
rows=re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',text)
assert rows,(name,'no configure')
return list(map(int,rows[-1]))
def geometry(text,origin,provenance=None):
a=configured(text,'POPUP_A');b=configured(text,'POPUP_B')
require(provenance is not None,'popup-owner-provenance-required')
p=provenance
top=Rect(Space.GLOBAL,*origin,0,0,p)
ids={n:list(map(int,re.search(rf'{n} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text).groups())) for n in ('TOPLEVEL','POPUP_A','POPUP_B')}
result={}
parent=top
for key,name,parent_name,c in [('A','POPUP_A','TOPLEVEL',a),('B','POPUP_B','POPUP_A',b)]:
parent_id=int(re.search(rf'{name} CREATE .*parent_xdg=(\d+)',text)[1])
ack=int(re.findall(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text)[-1])
parent=popup(parent,Rect(Space.POPUP,*c,p),parent_id,ids[parent_name][1],ack,name+' MAP ' in text)
result[key]=parent.values()
result.update(configured_A=a,configured_B=b,origin=origin,provenance=dict(owner=p.owner,workspace=p.workspace,generation=p.generation,evidence=p.evidence,state=p.state),
crossing={key:crossing(Rect(Space.GLOBAL,*result[key],p),0) for key in ('A','B')})
return result
def mapped(text):
for name in ['POPUP_A','POPUP_B']:
assert text.count(name+' MAP ')==1 and name+' POPUP_DONE ' not in text and name+' DESTROY ' not in text,(name,'not mapped')
def protocol(text):
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',text,re.I),'protocol error'
mapped(text)
ids={}
for name in ['TOPLEVEL','POPUP_A','POPUP_B']:
m=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text);assert m
ids[name]=list(map(int,m.groups()))
assert len({n for v in ids.values() for n in v})==9,'object identities reused'
grabs=[]
for name,parent in [('POPUP_A','TOPLEVEL'),('POPUP_B','POPUP_A')]:
create=re.search(rf'{name} CREATE .*parent={parent} parent_xdg=(\d+)',text);assert create
assert int(create[1])==ids[parent][1], 'wrong XDG parent'
debug_parent=re.search(rf'xdg_surface#{ids[name][1]}\.get_popup\(new id xdg_popup#{ids[name][2]}, xdg_surface#{ids[parent][1]}, xdg_positioner#\d+\)',text)
require(debug_parent is not None,'debug-parent-request')
m=re.search(rf'{name} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',text);assert m
serial,used=map(int,m.groups());assert serial==used and 0<serial<=0xffffffff
button=f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1'
assert text.index(button)<m.start(),'grab serial not from earlier button'
debug_button=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',text)
require(debug_button is not None and debug_button.start()<m.start(),'debug-real-button-serial')
role=text.index(name+' ROLE_CONFIGURE ');cfg=re.search(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text);assert cfg
mp=text.index(name+' MAP ')
assert m.start()<role<cfg.start()<mp,'configure ACK map order'
popup_role=ids[name][2];xdg=ids[name][1];wl=ids[name][0]
# WAYLAND_DEBUG is independent corroboration, not just fixture claims.
request=re.search(rf'xdg_popup#{popup_role}\.grab\(wl_seat#\d+, {serial}\)',text);assert request
ack=re.search(rf'xdg_surface#{xdg}\.ack_configure\({cfg[1]}\)',text);assert ack
attach=re.search(rf'wl_surface#{wl}\.attach\(',text);assert attach
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',text));assert len(commits)>=2
assert request.start()<commits[0].start()<role<ack.start()<attach.start()<commits[1].start()<mp
actual=configured(text,name)
require(re.search(rf'xdg_popup#{popup_role}\.configure\({actual[0]}, {actual[1]}, {actual[2]}, {actual[3]}\)',text) is not None,'debug-popup-configure')
require(re.search(rf'xdg_surface#{xdg}\.configure\({cfg[1]}\)',text) is not None,'debug-xdg-configure')
native_attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',text[ack.start():mp])
require(native_attach is not None,'popup-native-attach')
native_buffer=int(native_attach[1])
native_create=re.search(rf'create_buffer\(new id wl_buffer#{native_buffer}, 0, {actual[2]}, {actual[3]}, {actual[2]*4}, 1\)',text[role:mp])
require(native_create is not None,'popup-native-configured-size')
if parent=='POPUP_A':assert text.index('POPUP_A MAP ')<text.index(button)
grabs.append(dict(popup=name,parent=parent,serial=serial,used=used,origin='wl_pointer.button',ids=ids[name],ack=int(cfg[1]),native_buffer=native_buffer,native_size=actual[2:]))
assert grabs[0]['serial']<grabs[1]['serial'], 'distinct ordered serials'
return dict(ids=ids,grabs=grabs,hierarchy='TOPLEVEL -> POPUP_A -> POPUP_B',status='PASS')
def pair(lines,target):
events=[]
for line in lines:
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line);assert m
events.append([m[1],*map(int,m.groups()[1:])])
if len(events)!=2 or any(e[0]!=target for e in events) or [e[2:] for e in events]!=[[273,1],[273,0]]:
raise Defect('wrong actual button recipient/pair: '+str(events))
assert 0<events[0][1]<events[1][1]<=0xffffffff
return events
def dismissal(text,buttons):
if any('target=POPUP_A ' in b or 'target=POPUP_B ' in b for b in buttons):
raise Defect('invisible popup received actual button')
done=re.findall(r'(POPUP_[AB]) POPUP_DONE ',text)
destroyed=re.findall(r'(POPUP_[AB]) DESTROY ',text)
assert done==['POPUP_B','POPUP_A'] and destroyed==['POPUP_B','POPUP_A'],('dismissal order',done,destroyed)
return dict(done=done,destroyed=destroyed,invisible_buttons=buttons,status='PASS',meaning='outside dismissal distinct from invisible hit')

View file

@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""C17 builds only; no Wayland connections or compositor launches."""
import json,subprocess,shutil
from pathlib import Path
from gate import BASE,LAB,sha,durable,clean_environment,need
def prepare():
dest=BASE/'clients';commands=[]
dest.mkdir(parents=True,exist_ok=True)
parent=Path(__file__).resolve().parents[2]/'step20c-minimal-core-gate/graphics/clients'
for source in parent.iterdir():
if source.is_file():shutil.copy2(source,dest/source.name)
for source in (Path(__file__).resolve().parent/'clients').iterdir():shutil.copy2(source,dest/source.name)
flags=subprocess.run(['pkg-config','--cflags','--libs','wayland-client'],capture_output=True,text=True,timeout=10,check=True).stdout.split()
for n,proto in [('nested-client','xdg-shell'),('vertical-client','xdg-shell'),('saved-client','xdg-shell'),('static-reference','xdg-shell'),('pointer-daemon','virtual-pointer')]:
argv=['cc','-std=c17','-O2','-Wall','-Wextra',str(dest/(n+'.c')),str(dest/(proto+'-protocol.c')),*([str(dest/'layer-shell-protocol.c')] if n=='fx-client' else []),'-o',str(dest/n),*flags]
r=subprocess.run(argv,env=clean_environment(),capture_output=True,text=True,timeout=30);commands.append(dict(argv=argv,exit=r.returncode,stdout=r.stdout,stderr=r.stderr));need(r.returncode==0,'client compile '+n)
for rid in __import__('plans').RUNS:(BASE/'runs'/rid).mkdir(parents=True,exist_ok=True)
durable(BASE/'compile.json',dict(commands=commands,files={str(p):sha(p) for p in dest.iterdir() if p.is_file()},runtime='NOT RUN'),True)
if __name__=='__main__':prepare()

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Read-only replay of immutable raw E incidents; never rewrite old verdicts."""
import json,re
from pathlib import Path
from PIL import Image
import validators as v
from saved_validators import held_configure,released_configure,rgb_exact,timeout_line
from ipc_predicates import nodes,ancestry,workspace
from gate import sha,LAB,durable
HIST=Path('/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/graphics/runs')
CORE='29baf31aa3eeed259825c87d0799fa5d3dc83710afb967154604eb1c6657f965'
def saved_raw():
d=HIST/'E6/run';raw=(d/'owner.log').read_text();ev=[json.loads(l) for l in (d/'events.jsonl').read_text().splitlines()]
release=next(e['ns'] for e in ev if e['kind']=='client-request' and e.get('command')=='release_configure')
held_serial=int(re.search(r'HELD ns=\d+ serial=(\d+)',raw)[1])
cut=raw.index('COMMAND hold_next_configure DONE');before=raw[:cut];prior=int(re.findall(r'ACK ns=\d+ serial=(\d+)',before)[-1])
tail=raw[cut:];wire=re.search(rf'[^\n]*xdg_surface#\d+\.ack_configure\({held_serial}\)',tail)
holdtext=tail[:wire.start()];held=held_configure(before,holdtext,prior)
caps=[e for e in ev if e['kind']=='capture'];post=next(e for e in caps if e['tag']=='natural-timeout-H1')['start_ns']
live=released_configure(held,tail,release,post)
request=next(e['ns'] for e in ev if e['kind']=='ipc-request' and e['args']==['gaps inner current set 30'])
timeout=timeout_line((d/'compositor.log').read_text(),-1,True,request,post,release)
for e in caps:
v.evidence_identity(dict(binary_sha256=json.loads((d/'outcome.json').read_text())['evidence_provenance']['binary_sha256'],measurement_kind='raw'),CORE)
assert e['sha256']==sha(e['path']) and e['output']==('HEADLESS-1' if e['tag'].endswith('H1') else 'HEADLESS-2')
with Image.open(e['path']) as im:assert im.size==(800,600)
ref=Image.open(d/'H2-reference1.png').convert('RGB')
for name in ('H2-reference2','held-H2','natural-timeout-H2','restored-live-H2'):rgb_exact(ref,Image.open(d/(name+'.png')).convert('RGB'))
assert max(e['end_ns'] for e in caps if e['tag'].startswith('held-'))<request+4_700_000_000
assert post<release<live['ack_ns']<next(e['start_ns'] for e in caps if e['tag']=='restored-live-H1')
return dict(held=held,live=live,timeout_native=timeout,request_ns=request,natural_capture_ns=post,capture_count=len(caps),whole_H2='EXACT',original_verdict='INCONCLUSIVE',normal_owner_close='NOT ACQUIRED',final_after_close='NOT ACQUIRED',native_saved_identity='NOT OBSERVABLE',S8_justification='Explicit normal-close, SHM releases and final restoration missing; phased ACK/live PNG already acquired')
def replay():
result={'status':'PASS','historical_verdicts_unchanged':True,'incidents':{},'files':{}}
for rid in ('E0-pristine','E0-core','E2-vertical'):
d=HIST/rid/'run';candidates=[]
for p in d.glob('*snapshot.json'):
snap=json.loads(p.read_text());tree=snap['tree']
for n in nodes(tree):
if n.get('pid') and n.get('app_id')=='org.mosaic.NestedGrabProbe':
path=ancestry(tree,n['id']);ws=next(x for x in path if x['type']=='workspace');candidates.append(dict(owner=n['id'],floating=[x.get('floating') for x in path],eligible=v.camera_eligible(path,ws)))
# Some failed owner snapshots were overwritten; reconstruct from actual IPC
# reply JSON rather than inventing a tree or repairing historical snapshots.
if not candidates:
for line in (d/'events.jsonl').read_text().splitlines():
e=json.loads(line)
if e['kind']=='ipc-reply' and e.get('args')==['-t','get_tree'] and e['exit']==0:
tree=json.loads(e['stdout'])
for n in nodes(tree):
if n.get('app_id')=='org.mosaic.NestedGrabProbe':
path=ancestry(tree,n['id']);ws=next(x for x in path if x['type']=='workspace');candidates.append(dict(owner=n['id'],floating=[x.get('floating') for x in path],eligible=v.camera_eligible(path,ws)))
assert candidates,rid
result['incidents'][rid]=dict(ancestry_replayed=candidates,original_verdict='INCONCLUSIVE',png_count=len(list(d.glob('*.png'))))
for rid in ('E1','E4','E5'):
d=HIST/rid/'run';events=[json.loads(l) for l in (d/'events.jsonl').read_text().splitlines()]
result['incidents'][rid]=dict(original_verdict=json.loads((d/'outcome.json').read_text())['status'],png_count=len(list(d.glob('*.png'))))
if rid=='E1':
evidence=json.loads((d/'first-chain-protocol-log.json').read_text())['raw'];result['incidents'][rid]['first_chain']=v.grab(evidence[:evidence.index('POPUP_B POPUP_DONE')])
# The surviving owner log belongs to chain2 on H2; reject its actual tree.
maps=[json.loads(e['stdout']) for e in events if e['kind']=='ipc-reply' and e.get('args')==['-t','get_tree'] and e['exit']==0]
tree=maps[-1];owner=next(n for n in nodes(tree) if n.get('app_id')=='org.mosaic.NestedGrabProbe');path=ancestry(tree,owner['id'])
try:v.owner_context(owner,path,owner['pid'],'1','HEADLESS-1','mosaic')
except ValueError:result['incidents'][rid]['wrong_second_map_rejected']=True
else:raise AssertionError('historical wrong map accepted')
if rid=='E4':
state=json.loads((d/'fullscreen-current-ancestor.json').read_text());result['incidents'][rid]['actual_nonleaf']=v.fullscreen_ancestor(state['path'])['id']
if rid=='E5':
raw=(d/'owner.log').read_text();done=raw.index('POPUP_B POPUP_DONE');result['incidents'][rid]['dismissal_before_return']=True
assert raw.index('POPUP_A POPUP_DONE')>done
result['saved']=saved_raw()
for rid in ('E0-pristine','E0-core','E2-vertical','E1','E4','E5','E6'):
for p in (HIST/rid/'run').iterdir():
if p.is_file():result['files'][str(p)]=sha(p)
return result
if __name__=='__main__':
r=replay();durable(LAB/'graphics/historical-replay.json',r);print(json.dumps({k:v for k,v in r.items() if k!='files'},indent=2))

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""WHY/CONTRACT/INVARIANT: reserve once, reviewed bytes only, no retry."""
import argparse,time,re
from gate import LAB,begin,durable,BASE,need
from plans import RUNS
from engine import Session
import cases,validators
from geometry import *
from ipc_predicates import ancestry
def normal_remaining(s):
for n in list(s.proc):
if n not in ('owner','compositor','pointer') and s.proc[n].poll() is None:s.close_client(n,'close')
def standard_return(s,state,baseline,p,g):
oid=state['owner']['id'];s.expected_layout='splith';s.cmd(f'[con_id={oid}] layout splith',True);time.sleep(.4)
state=s.owner('owner');prot=validators.grab(s.log('owner'));need(prot['ids']==p['ids'],'live second chain preserved')
geo=cases.geometry(s.log('owner'),state['origin'],cases.Provenance(oid,state['workspace']['id'],'NOT OBSERVABLE','pointer+paint corroboration'))
images=s.pair_capture('standard-live');validators.standard_spill(images,COLORS)
for target,key,cmd in [('POPUP_B','B','close_b'),('POPUP_A','A',None)]:
im=images[1];xy=solid_point(im,COLORS[target],s.outputs[1],s.frames['HEADLESS-2'],geo[key]);tails,buttons=s.click('owner',xy);s.delivery('owner',geo[key],xy,tails,buttons,target)
if cmd:
before=s.log('owner');request=time.monotonic_ns();s.client_cmd('owner',cmd,'COMMAND close_b DONE');time.sleep(.2)
from lifecycle import explicit_child_close
explicit_child_close(before,s.log('owner'),p['ids'],request,time.monotonic_ns())
images=s.pair_capture('B-closed-A-survives');need(count(images[1],COLORS['POPUP_A'])>0 and count(images[1],COLORS['POPUP_B'])==0,'A not alive/B residual')
s.close_client('owner');final=s.pair_capture('standard-final');validators.rgb(baseline,final[1])
return 'PARTIALLY_FEASIBLE'
def horizontal(s):
cases.init_reference(s);state,baseline,p,g,left,right=cases.open_chain(s)
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
forbidden=intersection(g['B'],s.outputs[1]['logical']);need(forbidden[2]>6 and forbidden[3]>6,'forbidden interior');point=[forbidden[0]+forbidden[2]//2,forbidden[1]+forbidden[3]//2]
tails,buttons=s.click('owner',point);proof=cases.dismissal(tails['owner'],buttons['owner']);s.save('dismissal',proof);s.close_client('owner');s.save('first-chain-protocol-log',dict(raw=s.log('owner')))
# Two independent real chains; no resurrected dismissed popup.
state,baseline2,p,g,left,right=cases.open_chain(s);return standard_return(s,state,baseline2,p,g)
def fullscreen(s):
# WHY/CONTRACT/INVARIANT: fullscreen the actual nonleaf parent containing owner.
cases.init_reference(s);s.debit('chain');s.launch_client('owner','nested-client','static_cross');oid=s.find('owner')['id']
s.cmd(f'[con_id={oid}] focus');s.cmd('splitv');s.launch_client('nested-filler','static-reference');s.cmd(f'[con_id={oid}] focus')
before=s.owner('owner');baseline=cases.stable_reference(s);s.cmd('focus parent');s.cmd('fullscreen enable',True);time.sleep(.4)
state=s.owner('owner');path=ancestry(state['snapshot']['tree'],oid);ancestor=validators.fullscreen_ancestor(path);anc=[ancestor]
s.save('fullscreen-current-ancestor',dict(before=before,path=path,ancestor_ids=[n['id'] for n in anc]))
if not anc:raise ValueError('current fullscreen ancestor NOT EXERCISED')
image=s.capture('fullscreen-owner','HEADLESS-1');xy=solid_point(image,COLORS['TOPLEVEL'],s.outputs[0],s.frames['HEADLESS-1'])
tails,buttons=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.2)
image=s.capture('fullscreen-A','HEADLESS-1');xyA=solid_point(image,COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'])
tailsA,buttonsA=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
prot=validators.grab(s.log('owner'));geo=cases.geometry(s.log('owner'),state['origin'],cases.Provenance(oid,state['workspace']['id'],'NOT OBSERVABLE','fullscreen paint+pointer'))
s.delivery('owner',[*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']],xy,tails,buttons,'TOPLEVEL',prot['grabs'][0]['serial'])
s.delivery('owner',geo['A'],xyA,tailsA,buttonsA,'POPUP_A',prot['grabs'][1]['serial'])
images=s.pair_capture('fullscreen-nested');point=solid_point(images[0],COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],geo['B']);tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B')
s.cmd(f'[con_id={anc[-1]["id"]}] fullscreen disable',True);time.sleep(.4);s.pair_capture('fullscreen-left')
s.expected_layout='splith';s.cmd(f'[con_id={oid}] layout splith',True);time.sleep(.4);s.pair_capture('fullscreen-standard');s.close_client('owner');final=s.pair_capture('fullscreen-final');validators.rgb(baseline,final[1])
s.save('fullscreen-policy',dict(status='INCONCLUSIVE',reason='paired pristine/core raw observations; cross-run comparison required, no UX policy inferred',atomic_generation='NOT OBSERVABLE'))
return 'PARTIALLY_FEASIBLE'
COLORS=cases.COLORS
def main():
p=argparse.ArgumentParser();p.add_argument('run_id',choices=RUNS);rid=p.parse_args().run_id
auth=begin(rid);s=None;status='INCONCLUSIVE';failure=None
try:
s=Session(RUNS[rid],auth);s.start()
if rid=='S2-core-horizontal':status=horizontal(s)
elif 'fullscreen' in rid:status=fullscreen(s)
elif rid=='S7-core-hidden-return':status=cases.hidden_campaign(s)
elif rid=='S8-core-saved':
from saved import run
status=run(s)
elif 'standard' in rid:
cases.init_reference(s);state,baseline,p,g,left,right=cases.open_chain(s);status=standard_return(s,state,baseline,p,g)
else:status=cases.popup_campaign(s)
normal_remaining(s)
except Exception as e:
# WHY/CONTRACT/INVARIANT: successful measurements do not qualify a failed run.
failure=f'{type(e).__name__}: {e}';status='INCONCLUSIVE'
finally:
if s is not None:status=s.cleanup(status,failure)
print(status,failure or '');return 0 if status in ('PASS','PARTIALLY_FEASIBLE') else 1
if __name__=='__main__':raise SystemExit(main())

View file

@ -0,0 +1,30 @@
"""One actual held→natural-timeout episode, exact binary, EXPERIMENTAL."""
import re,time
from pathlib import Path
import cases,engine
from geometry import need
from saved_validators import pending_geometry,held_configure,saved_identity,natural_retirement,rgb_exact,timeout_line,released_configure
def run(s):
cases.init_reference(s,2);s.launch_client('owner','saved-client');s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner');baseline=cases.stable_reference(s)
initial=s.snapshot('initial-applied');before=s.log('owner');acks=re.findall(r'ACK ns=\d+ serial=(\d+)',before);need(acks,'initial actual ACK');prior=int(acks[-1])
argv=Path(f'/proc/{s.comp.pid}/cmdline').read_bytes().split(b'\0');need(b'-Dtxn-timeout=5000' in argv,'actual startup timeout5000')
s.client_cmd('owner','hold_next_configure','COMMAND hold_next_configure DONE');cut=len(s.log('owner'));ccut=len(s.log('compositor'));s.cmd('workspace 1');request=time.monotonic_ns();s.cmd('gaps inner current set 30',True)
pending=s.snapshot('saved-requested-pending');s.wait(lambda:'HELD ' in s.log('owner')[cut:]);proof=pending_geometry(initial,pending,state['owner']['id'],state['workspace']['id'],before,s.log('owner')[cut:],prior,request)
held_start=time.monotonic_ns();held=s.pair_capture('held');held_end=time.monotonic_ns();need(held_start>proof['held']['ns'] and held_end<request+4_700_000_000,'held capture before expiration')
commits=re.findall(r'COMMIT ns=\d+ serial=\d+ size=\d+x\d+ color=([0-9a-f]+)',before);need(commits,'actual retained SHM color');value=int(commits[-1],16);color=((value>>16)&255,(value>>8)&255,value&255)
rgb_exact(baseline,held[1]);need(any(p==color for p in held[0].getdata()),'owner retained SHM color visible H1')
native=None
if False:native=saved_identity(s.log('compositor'),state['owner']['id'],request)
s.wait(lambda:re.search(r'Transaction .*timed out \(\d*[1-9]\d* waiting\)',s.log('compositor')[ccut:]),seconds=6)
observed=time.monotonic_ns();need(s.proc['owner'].poll() is None,'owner alive natural timeout');hold=held_configure(before,s.log('owner')[cut:],prior)
timeout=timeout_line(s.log('compositor')[ccut:],-1,True,request,observed,2**63)
after_start=time.monotonic_ns();after=s.pair_capture('natural-timeout');rgb_exact(baseline,after[1]);release=time.monotonic_ns();s.client_cmd('owner','release_configure','COMMAND release_configure DONE');s.wait(lambda:re.search(r'ACK ns=\d+ serial='+str(proof['held']['serial'])+r'\b',s.log('owner')[cut:]));live=s.pair_capture('restored-live');rgb_exact(baseline,live[1])
if native:
native=saved_identity(s.log('compositor'),state['owner']['id'],request,True)
retirement=natural_retirement(s.log('compositor'),native,after_start,time.monotonic_ns(),True,before,s.log('owner')[cut:],prior)
else:retirement={'status':'NOT OBSERVABLE; CORE has no native saved identity'}
# WHY CONTRACT INVARIANT: CPU destruction is measured separately from GPU
# completion, and process exit0 alone never qualifies Wayland cleanup.
s.save('saved-evidence',dict(request_ns=request,held_start_ns=held_start,held_end_ns=held_end,timeout_observed_ns=observed,timeout_native=timeout,geometry=proof,native=native,retirement=retirement,held_wire=hold,post_release=released_configure(hold,s.log('owner')[cut:],release,observed,s.proc['owner'].poll() is None),gpu_completion='NOT PROVEN',native_generation='NOT PROVEN',binary=s.auth))
s.close_client('owner','close');final=s.pair_capture('final');rgb_exact(baseline,final[1]);return 'PASS'

View file

@ -0,0 +1,55 @@
"""Local pure CORE saved evidence; no diagnostic/historical import."""
import re
from geometry import need,intersection
from ipc_predicates import workspace,nodes
from PIL import ImageChops
def held_configure(before,after,prior_serial):
need(type(prior_serial)is int and prior_serial>0,'prior serial required')
need(re.search(rf'ACK ns=\d+ serial={prior_serial}\b',before) and re.search(rf'ack_configure\({prior_serial}\)',before),'initial actual ACK required')
need('wl_shm_pool#' in before and '.create_buffer(' in before and 'COMMIT ns=' in before,'real initial SHM required')
held=re.findall(r'HELD ns=(\d+) serial=(\d+)',after);need(len(held)==1,'unique real HELD configure')
ns,serial=map(int,held[0]);need(serial!=prior_serial,'new held serial')
need(re.search(rf'xdg_surface#\d+\.configure\({serial}\)',after),'received actual configure wire')
need(re.search(rf'XDG_CONFIGURE ns=\d+ serial={serial}\b.*hold=1',after),'client received hold state')
old=re.findall(r'ACK ns=\d+ serial='+str(prior_serial)+r' size=(\d+)x(\d+)',before);new=re.findall(r'XDG_CONFIGURE ns=\d+ serial='+str(serial)+r' size=(\d+)x(\d+)',after)
need(old and new and old[-1]!=new[-1],'new geometry configure required')
need(not re.search(rf'ack_configure\({serial}\)|ACK ns=\d+ serial={serial}\b',after),'held serial must remain unACKed')
return dict(ns=ns,serial=serial,prior_serial=prior_serial,initial_size=list(map(int,old[-1])),requested_size=list(map(int,new[-1])),status='HELD_WIRE_VALIDATED',native_saved_identity='NOT OBSERVABLE')
def pending_geometry(initial,pending,owner_id,workspace_id,before,after,prior,request_ns):
oldws=workspace(initial['tree'],'1');ws=workspace(pending['tree'],'1')
need(oldws['id']==ws['id']==workspace_id and ws['output']==oldws['output']=='HEADLESS-1' and ws['layout']==oldws['layout']=='mosaic','same requested workspace')
need('gaps' not in ws,'IPC gaps field is absent in audited source')
old=next(n for n in nodes(oldws) if n['id']==owner_id)
owners=[n for n in nodes(ws) if n['id']==owner_id];need(len(owners)==1,'same owner ancestry')
target=owners[0];need(target['pid']==old['pid'] and target['app_id']==old['app_id']=='org.mosaic.SavedBufferProbe','same actual client')
a=oldws['rect'];b=ws['rect'];need([b[k] for k in ('x','y','width','height')]==[a['x']+30,a['y']+30,a['width']-60,a['height']-60],'requested workspace rect changed by runtime gaps30')
proof=held_configure(before,after,prior)
need(proof['ns']>request_ns and proof['requested_size']==[target['window_rect']['width'],target['window_rect']['height']],'held wire after command matches requested dimensions')
need(proof['initial_size']==[old['window_rect']['width'],old['window_rect']['height']],'initial applied dimensions')
return dict(held=proof,workspace=ws,owner=target)
def rgb_exact(a,b):
need(a.mode==b.mode=='RGB' and a.size==b.size,'whole RGB contract')
need(ImageChops.difference(a,b).getbbox() is None,'whole RGB equal; no masked channel/ROI');return True
def timeout_line(text,activation_line,client_alive,request_ns,observed_ns,cleanup_ns):
need(client_alive and request_ns+4_700_000_000<=observed_ns<cleanup_ns,'natural timeout window/client alive')
matches=[(i,l) for i,l in enumerate(text.splitlines()) if i>activation_line and re.search(r'Transaction .*timed out \(\d*[1-9]\d* waiting\)',l)]
need(len(matches)==1,'native natural timeout required');return matches[0]
def saved_identity(*args,**kwargs):raise ValueError("CORE native saved identity NOT OBSERVABLE")
def natural_retirement(*args,**kwargs):raise ValueError("CORE retirement NOT OBSERVABLE")
def released_configure(held,after,release_ns,timeout_ns,client_alive=True):
"""POST-RELEASE accepts the ACK forbidden in the immutable HOLD prefix."""
need(client_alive and held['ns']<timeout_ns<release_ns,'HOLD timeout release phase order')
serial=held['serial']
a=re.findall(rf'ACK ns=(\d+) serial={serial}\b',after)
c=re.findall(rf'COMMIT ns=(\d+) serial={serial}\b',after)
need(len(a)==1 and len(c)==1,'unique released ACK/commit')
ack,commit=int(a[0]),int(c[0])
need(release_ns<ack<=commit,'release precedes live ACK and commit')
need(re.search(rf'ack_configure\({serial}\)',after),'released ACK actual wire')
return dict(serial=serial,release_ns=release_ns,ack_ns=ack,commit_ns=commit,status='POST_RELEASE_LIVE')

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Seal exact final closure; independent review still required afterwards."""
import argparse,json,shutil,sys
from pathlib import Path
from gate import *
def seal(identity):
identities=json.loads(Path(identity).read_text());need(set(identities)=={'core','pristine'},'both exact identities required')
files={str(p):sha(p) for p in BASE.rglob('*') if p.is_file() and p.suffix in ('.py','.c','.h','.xml','.txt','.json','.conf','.md') and p.name not in ('review.json','pre-run-manifest.json') and 'runs' not in p.parts}
files.update(json.loads((BASE/'compile.json').read_text())['files'])
for name,a in identities.items():
need(name in ('core','pristine'),'identity name');need(sha(a['binary'])==a['binary_sha256'],'exact binary');files[a['binary']]=a['binary_sha256']
need(a['wlroots']==str(WL) and a['wlroots_sha256']==WL_SHA,'required wlroots');files[str(WL)]=WL_SHA
if name=='pristine':
need(a.get('source_parent')=='fd71a6bdc061bd633b488ae7b83e8a6981d22f86','pristine parent');need(a.get('pristine_scene') and a.get('source_proof'),'pristine comparability proof required');files[a['pristine_scene']]=a['pristine_scene_sha256'];proof=json.loads(Path(a['source_proof']).read_text());need(proof.get('status')=='PASS' and proof.get('pinned_parent')==a['source_parent'],'pristine source proof unproven');files[a['source_proof']]=sha(a['source_proof'])
for row in proof['files']:
path=Path(proof['source'])/row['path'];need(row['match'] and sha(path)==row['expected'],'pristine source changed');files[str(path)]=row['expected']
for p in (LAB/'build-audit').glob('*.json'):files[str(p)]=sha(p)
for t in ('swaymsg','grim','swaybg'):
path=Path(shutil.which(t)).resolve();files[str(path)]=sha(path)
import PIL
for p in Path(PIL.__file__).parent.rglob('*'):
if p.is_file() and p.suffix in ('.py','.so'):files[str(p)]=sha(p)
files[str(Path(sys.executable).resolve())]=sha(Path(sys.executable).resolve())
durable(BASE/'pre-run-manifest.json',dict(ticket=TICKET,lab=str(LAB),status='SEALED_REVIEW_PENDING',authorized_runs=list(__import__('plans').RUNS),identities=identities,files=files),True)
print(sha(BASE/'pre-run-manifest.json'))
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('identities');seal(p.parse_args().identities)

View file

@ -0,0 +1,127 @@
"""Adversarial tests import exactly the predicates used by Session/run/cases."""
import copy,json,re,unittest,os
from unittest.mock import patch
import gate
from PIL import Image
import engine,cases,run,validators as v,saved_validators as sv
from replay import saved_raw,HIST,replay
from ipc_predicates import ancestry,workspace,nodes
from plans import RUNS
from geometry import png_global,global_png,expected_outputs,solid_point
class Recovery(unittest.TestCase):
def rejects(self,fn,*args):
with self.assertRaises((ValueError,AssertionError,RuntimeError)):fn(*args)
def test_desktop_environment_scrubbed(self):
names=['DISPLAY','WAYLAND_DISPLAY','SWAYSOCK','XDG_RUNTIME_DIR','LD_PRELOAD','LD_LIBRARY_PATH','DBUS_SESSION_BUS_ADDRESS','XDG_CURRENT_DESKTOP','DESKTOP_SESSION','XDG_ACTIVATION_TOKEN','DESKTOP_STARTUP_ID','XDG_SESSION_ID','WLR_BACKENDS']
with patch.dict(os.environ,{k:'desktop' for k in names}):
e=gate.clean_environment();self.assertTrue(all(k not in e for k in names))
def test_actual_gate_stale_and_consumed(self):
import tempfile
from pathlib import Path
with tempfile.TemporaryDirectory() as d:
p=Path(d)/'manifest';p.write_text('{}')
self.rejects(gate.validate_review,p,dict(decision='ACCEPTED',independent=True,manifest_sha256='stale'))
budget=Path(d)/'budget';budget.write_text(json.dumps(dict(ticket='historical',runs=[])));before=budget.read_bytes()
self.rejects(gate.transact,lambda x:None,budget);self.assertEqual(before,budget.read_bytes())
rid='S0-pristine-standard';auth=dict(binary='fake',binary_sha256='sha')
m=dict(authorized_runs=[rid],identities=dict(pristine=auth))
limits=dict(runs=11,captures=160,captures_per_run=18,duration_s=150)
def tx(callback):return callback(dict(limits=limits,runs=[dict(id=rid)]))
# begin's real reserve callback, not a duplicate budget model.
with patch.object(gate,'validate_review',return_value=m),patch.object(gate,'sha',return_value='sha'),patch.object(gate,'transact',side_effect=tx),patch.object(Path,'read_text',return_value='{}'):
self.rejects(gate.begin,rid)
def test_live_shared(self):
self.assertIs(engine.validators,v);self.assertIs(run.validators,v);self.assertIs(cases.validators,v)
def tree(self):
t=json.loads((HIST/'E2-vertical/run/owner-owner-snapshot.json').read_text())['tree'];n=next(n for n in nodes(t) if n.get('app_id')=='org.mosaic.NestedGrabProbe');return t,n,ancestry(t,n['id'])
def test_null_floating_actual_ancestry(self):
t,n,p=self.tree();ws=workspace(t,'1');self.assertTrue(v.camera_eligible(p,ws));self.assertTrue(any(x.get('floating') is None for x in p))
n['floating']='user_on';self.assertFalse(v.camera_eligible(p,ws));n['floating']='auto_off'
parent=p[-2];parent['nodes'].remove(n);parent['floating_nodes'].append(n);self.assertFalse(v.camera_eligible(p,ws))
def test_wrong_owner_context(self):
t,n,p=self.tree();v.owner_context(n,p,n['pid'],'1','HEADLESS-1','mosaic')
for args in [(n,p,0,'1','HEADLESS-1','mosaic'),(n,p[:-1],n['pid'],'1','HEADLESS-1','mosaic'),(n,p,n['pid'],'2','HEADLESS-1','mosaic'),(n,p,n['pid'],'1','HEADLESS-2','mosaic'),(n,p,n['pid'],'1','HEADLESS-1','splith')]:self.rejects(v.owner_context,*args)
def test_fullscreen_workspace_marker_insufficient(self):
t,n,p=self.tree();self.rejects(v.fullscreen_ancestor,p)
q=json.loads((HIST/'E4/run/fullscreen-current-ancestor.json').read_text())['path'];v.fullscreen_ancestor(q)
q[-2]['fullscreen_mode']=0;self.rejects(v.fullscreen_ancestor,q)
def test_wire_grab_and_native_size(self):
raw=(__import__('pathlib').Path(__file__).parent/'wire-fixture.txt').read_text();proof=v.grab(raw)
for bad in (raw.replace('POPUP_B MAP ','BAD MAP '),raw.replace('origin=wl_pointer.button','origin=fake'),raw.replace('used='+str(proof['grabs'][0]['serial']),'used=999999'),raw.replace('parent_xdg='+str(proof['ids']['TOPLEVEL'][1]),'parent_xdg=999999'),re.sub(r'(create_buffer\(new id wl_buffer#\d+, 0, )\d+, \d+, \d+, 1\)',r'\g<1>999, 999, 3996, 1)',raw)):self.rejects(v.grab,bad)
def test_vertical_axis_outputs_frame(self):
s=RUNS['S4-core-vertical'];self.assertEqual(s['boundary'],600);v.crosses([50,505,250,170],1,600)
self.rejects(v.crosses,[50,100,250,170],1,600);self.rejects(v.crosses,[50,505,250,170],0,0)
o=s['outputs'][1];self.assertEqual(png_global((30,40),o,'logical'),[30,640]);self.assertEqual(global_png([30,640],o,'logical'),[30,40]);self.rejects(png_global,(1,2),o,'unknown')
rows=[dict(active=True,name=o['name'],rect=dict(zip(('x','y','width','height'),o['logical'])),scale=1,transform='normal',current_mode=dict(width=800,height=600)) for o in s['outputs']];expected_outputs(rows,s['outputs']);rows[1]['rect']['y']=0;self.rejects(expected_outputs,rows,s['outputs'])
def test_hidden_dismissal_rejected(self):
raw=(__import__('pathlib').Path(__file__).parent/'wire-fixture.txt').read_text();initial=v.grab(raw);v.hidden_survival(raw,initial,[dict(action='move')]);self.rejects(v.hidden_survival,raw,initial,[dict(action='left')]);self.rejects(v.hidden_survival,raw+'POPUP_B POPUP_DONE ',initial,[])
def test_effective_standard_clip_removal(self):
a=Image.new('RGB',(20,20));a.putpixel((0,0),engine.COLORS['POPUP_A']);a.putpixel((1,1),engine.COLORS['POPUP_B']);v.standard_spill([a,a],engine.COLORS)
a.putpixel((1,1),(0,0,0));self.rejects(v.standard_spill,[a,a],engine.COLORS)
def test_rgb_sha_applied(self):
a=Image.new('RGB',(20,20));b=a.copy();b.putpixel((19,19),(0,0,1));self.rejects(v.rgb,a,b);self.rejects(v.rgb,a,a,True)
self.rejects(v.evidence_identity,dict(status='PASS'),'CORE');self.rejects(v.evidence_identity,dict(binary_sha256='old',measurement_kind='raw'),'CORE');self.rejects(v.applied,[0,0],[0,0],[0,0],'pending')
def test_saved_real_temporal_phases(self):
r=saved_raw();d=HIST/'E6/run';raw=(d/'owner.log').read_text();serial=r['held']['serial'];wire=re.search(rf'[^\n]*xdg_surface#\d+\.ack_configure\({serial}\)',raw);cut=raw.index('COMMAND hold_next_configure DONE');before=raw[:cut]
sv.held_configure(before,raw[cut:wire.start()],30)
self.rejects(sv.held_configure,before,raw[cut:],30)
self.rejects(sv.released_configure,r['held'],raw,r['live']['ack_ns']+1,r['natural_capture_ns'])
self.rejects(sv.released_configure,r['held'],raw,r['live']['release_ns'],r['live']['release_ns']+1)
self.rejects(sv.released_configure,r['held'],raw,r['live']['release_ns'],r['natural_capture_ns'],False)
self.rejects(sv.timeout_line,(d/'compositor.log').read_text(),-1,False,r['request_ns'],r['natural_capture_ns'],r['live']['release_ns'])
def test_normal_closure_separate(self):
good='CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=2\nEXIT error=0';v.client_exit(good,0)
for bad in (good.replace('released=2','released=1'),good.replace('CLOSE_RECEIVED',''),good.replace('error=0','error=32')):self.rejects(v.client_exit,bad,0)
def closure_main(self,bad=None,archived=False,exited=False,scenario_failure=False):
# WHY: predicate-only tests missed main's success surviving normal_remaining.
# CONTRACT: run the real main -> normal_remaining -> Session.cleanup offline.
# INVARIANT: fake I/O never launches a compositor or consumes a reservation.
import tempfile,time,threading,io
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock
good='CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=2\nEXIT error=0'
class Process:
def __init__(self,pid,done=False):self.pid=pid;self.returncode=0 if done else None;self.stdin=io.StringIO()
def poll(self):return self.returncode
def wait(self,timeout=None):self.returncode=0;return 0
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);s=engine.Session.__new__(engine.Session)
s.spec=RUNS['S4-core-vertical'];s.auth={};s.run=root/'run';s.run.mkdir();s.runtime=root/'runtime';s.runtime.mkdir()
s.closed=[];s.handles=[];s.events=[];s.started=time.monotonic();s.finished=threading.Event();s.expired=False;s.pointer=None;s.env={}
s.watch=Mock();s.sock=Mock();s.sock.is_socket.return_value=True
s.comp=Process(999999991);s.proc={'compositor':s.comp,'reference':Process(999999992,exited)}
(s.run/'reference.log').write_text(good if archived else bad or good);s.start=Mock();s.find=Mock(return_value=None);s.debit=Mock()
def campaign(session):
if archived:
old=Process(999999993);s.proc['owner']=old;(s.run/'owner.log').write_text(bad)
try:s.close_client('owner')
except engine.Ambiguous:pass
s.proc['owner']=Process(999999994);(s.run/'owner.log').write_text(good)
if scenario_failure:raise ValueError('scenario failed')
return 'PASS'
def terminal(*args,**kwargs):s.comp.returncode=0;return SimpleNamespace(returncode=0,stdout='',stderr='')
with patch.object(run,'begin',return_value={}),patch.object(run,'Session',return_value=s),patch.object(run.cases,'popup_campaign',side_effect=campaign),patch('sys.argv',['run.py','S4-core-vertical']),patch.object(engine.subprocess,'run',side_effect=terminal),patch.object(engine.os,'killpg',side_effect=ProcessLookupError),patch('builtins.print'):
result=run.main()
return result,json.loads((s.run/'outcome.json').read_text())
def test_main_normal_remaining_closure_failure(self):
bad='CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=1\nEXIT error=0'
result,out=self.closure_main(bad)
self.assertEqual(result,1);self.assertEqual(out['status'],'INCONCLUSIVE');self.assertEqual(out['scientific_cleanup'],'INCONCLUSIVE');self.assertEqual(out['mechanical_cleanup'],'PASS')
self.assertIn('SHM release',out['failure']);self.assertEqual(out['explicit_client_closures'][0]['status'],'INCONCLUSIVE')
def test_main_normal_close_success_control(self):
result,out=self.closure_main();self.assertEqual(result,0);self.assertEqual(out['status'],'PASS');self.assertEqual(out['scientific_cleanup'],'PASS');self.assertEqual(out['mechanical_cleanup'],'PASS')
def test_main_archived_same_name_failure(self):
result,out=self.closure_main('CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=1\nEXIT error=0',archived=True)
self.assertEqual(result,1);self.assertIsNone(out['failure']);self.assertEqual(out['scientific_cleanup'],'INCONCLUSIVE');self.assertEqual(out['mechanical_cleanup'],'PASS')
owners=[c for c in out['explicit_client_closures'] if c['name']=='owner'];self.assertEqual([c['status'] for c in owners],['INCONCLUSIVE','PASS']);self.assertNotEqual(owners[0]['pid'],owners[1]['pid'])
def test_main_already_exited_client_validated(self):
for bad in ('SHM_CLOSURE created=2 released=2\nEXIT error=0','CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=1\nEXIT error=0'):
result,out=self.closure_main(bad,exited=True);self.assertEqual(result,1);self.assertEqual(out['scientific_cleanup'],'INCONCLUSIVE');self.assertEqual(len(out['client_closures']),1)
def test_main_scenario_failure_never_success(self):
result,out=self.closure_main(scenario_failure=True);self.assertEqual(result,1);self.assertEqual(out['status'],'INCONCLUSIVE');self.assertEqual(out['scientific_cleanup'],'PASS');self.assertIn('scenario failed',out['failure'])
def test_budgets(self):
self.assertEqual([r['capture_limit'] for r in RUNS.values()],[12,12,18,12,12,14,14,14,12]);self.assertEqual(sum(r['capture_limit'] for r in RUNS.values()),120)
def test_historical_raw_replay(self):self.assertEqual(replay()['status'],'PASS')
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,72 @@
"""Imported live/offline scientific predicates, not a parallel test model."""
import re
from geometry import need,exact,crossing,expected_outputs
from predicates import protocol
def client_exit(log,code):
if 'TOPLEVEL CREATE ' in log:
order=re.findall(r'(POPUP_[AB]|TOPLEVEL) DESTROY',log)
need(order in (['POPUP_B','POPUP_A','TOPLEVEL'],['TOPLEVEL']),'popup child-first destruction order')
need(log.index('CLOSE_RECEIVED')<log.index('TOPLEVEL DESTROY'),'close precedes destruction')
need('CLOSE_RECEIVED' in log,'explicit close receipt absent')
releases=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',log)
need(len(releases)==1 and int(releases[0][0])>0 and releases[0][0]==releases[0][1],'client SHM release incomplete/unobserved')
need(code==0,'client nonzero');errors=re.findall(r'EXIT[^\n]*error=(\d+)',log)
need(bool(errors) and all(e=='0' for e in errors),'client error/nonobservable exit')
need(not re.search(r'protocol error|wl_display[^\n]*\.error\(',log,re.I),'wire error')
def window(start,end,events):
need(start<end and len(events)==1 and start<=events[0]<=end,'ambiguous event window')
def owner(node,owner_id,workspace_id,path):
need(node['id']==owner_id and [n['id'] for n in path if n['type']=='workspace']==[workspace_id],'wrong owner ancestry')
def rgb(reference,actual,masked=False):
need(not masked,'H2 occlusion/mask forbidden');return exact(reference,actual)
def grab(log):return protocol(log)
def applied(geometry,paint_origin,pointer_origin,state):
need(state=='applied' and geometry==paint_origin==pointer_origin,'pending/uncorroborated origin')
def crosses(rect,axis,boundary):need(crossing(rect,axis,boundary),'crossing not exercised')
def shadow(before,after):need(before.tobytes()!=after.tobytes(),'shadow not exercised')
def saved_identity(identity,binary_sha):
need(identity['binary_sha256']==binary_sha,'wrong binary saved identity')
need(identity.get('native') is True,'saved identity not observable')
def camera_eligible(path,ws):
# Null flags are legitimate on IPC descriptors; branch membership remains
# authoritative even when a child omits its floating flag.
floating=any((n.get('floating') or 'auto_off').endswith('on') for n in path)
floating |= any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:]))
return ws.get('layout')=='mosaic' and not floating and not any(n.get('type')=='con' and n.get('fullscreen_mode') for n in path)
def owner_context(node,path,pid,workspace_name,output,layout):
need(path and path[-1]==node and node.get('pid')==pid,'owner path/pid mismatch')
ws=[n for n in path if n.get('type')=='workspace'];outs=[n for n in path if n.get('type')=='output']
need(len(ws)==len(outs)==1,'owner ancestry must include workspace/output')
need(ws[0].get('name')==workspace_name and ws[0].get('output')==output and outs[0].get('name')==output and ws[0].get('layout')==layout,'wrong mapped workspace/output/layout')
owner(node,node['id'],ws[0]['id'],path)
return ws[0]
def selected_workspace(snapshot,name,output,layout):
from ipc_predicates import workspace
ws=workspace(snapshot['tree'],name)
need(ws.get('output')==output and ws.get('layout')==layout,'pre-map workspace/output/layout')
rows=[r for r in snapshot['workspaces'] if r.get('name')==name]
need(len(rows)==1 and rows[0].get('focused') and rows[0].get('output')==output,'pre-map workspace not selected')
return ws
def fullscreen_ancestor(path):
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('fullscreen_mode') and n.get('nodes')]
need(rows,'nonleaf current fullscreen ancestor NOT EXERCISED')
return rows[-1]
def hidden_survival(log,initial,injections):
need(not any(e.get('action')!='move' for e in injections),'hide/return contaminated by dismissal click')
fresh=grab(log)
need(fresh['ids']==initial['ids'] and fresh['grabs']==initial['grabs'],'same live grabbed chain required')
return fresh
def standard_spill(images,colors):
# Native clip identity is unavailable on CORE: demonstrate effective removal
# through both continued popup paints, then freshly corroborated pointer input.
need(all(any(px==colors[n] for px in images[1].get_flattened_data()) for n in ('POPUP_A','POPUP_B')),'standard continuation for both A/B NOT EXERCISED')
def evidence_identity(record,binary_sha):
need(record.get('binary_sha256')==binary_sha and record.get('measurement_kind')=='raw','wrong SHA or superficial status')

View file

@ -0,0 +1,221 @@
[22:46:38.282527] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
[22:46:38.282546] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
[22:46:38.282664] {Display Queue} wl_display#1.delete_id(3)
[22:46:38.282677] {Default Queue} wl_registry#2.global(1, "wl_fixes", 1)
[22:46:38.282685] {Default Queue} wl_registry#2.global(2, "wl_shm", 2)
[22:46:38.282692] {Default Queue} -> wl_registry#2.bind(2, "wl_shm", 1, new id [unknown]#4)
[22:46:38.282699] {Default Queue} wl_registry#2.global(3, "zwp_linux_dmabuf_v1", 5)
[22:46:38.282703] {Default Queue} wl_registry#2.global(4, "wp_linux_drm_syncobj_manager_v1", 1)
[22:46:38.282708] {Default Queue} wl_registry#2.global(5, "wl_compositor", 6)
[22:46:38.282712] {Default Queue} -> wl_registry#2.bind(5, "wl_compositor", 4, new id [unknown]#5)
[22:46:38.282716] {Default Queue} wl_registry#2.global(6, "wl_subcompositor", 1)
[22:46:38.282720] {Default Queue} wl_registry#2.global(7, "wl_data_device_manager", 3)
[22:46:38.282724] {Default Queue} wl_registry#2.global(8, "zwlr_gamma_control_manager_v1", 1)
[22:46:38.282728] {Default Queue} wl_registry#2.global(9, "zxdg_output_manager_v1", 3)
[22:46:38.282732] {Default Queue} wl_registry#2.global(10, "ext_idle_notifier_v1", 2)
[22:46:38.282736] {Default Queue} wl_registry#2.global(11, "zwp_idle_inhibit_manager_v1", 1)
[22:46:38.282740] {Default Queue} wl_registry#2.global(12, "zwlr_layer_shell_v1", 5)
[22:46:38.282744] {Default Queue} wl_registry#2.global(13, "xdg_wm_base", 5)
[22:46:38.282751] {Default Queue} -> wl_registry#2.bind(13, "xdg_wm_base", 3, new id [unknown]#6)
[22:46:38.282756] {Default Queue} wl_registry#2.global(14, "zwp_tablet_manager_v2", 1)
[22:46:38.282760] {Default Queue} wl_registry#2.global(15, "org_kde_kwin_server_decoration_manager", 1)
[22:46:38.282764] {Default Queue} wl_registry#2.global(16, "zxdg_decoration_manager_v1", 1)
[22:46:38.282768] {Default Queue} wl_registry#2.global(17, "zwp_relative_pointer_manager_v1", 1)
[22:46:38.282772] {Default Queue} wl_registry#2.global(18, "zwp_pointer_constraints_v1", 1)
[22:46:38.282776] {Default Queue} wl_registry#2.global(19, "wp_presentation", 2)
[22:46:38.282780] {Default Queue} wl_registry#2.global(20, "wp_alpha_modifier_v1", 1)
[22:46:38.282784] {Default Queue} wl_registry#2.global(21, "zwlr_output_manager_v1", 4)
[22:46:38.282788] {Default Queue} wl_registry#2.global(22, "zwlr_output_power_manager_v1", 1)
[22:46:38.282792] {Default Queue} wl_registry#2.global(23, "zwp_input_method_manager_v2", 1)
[22:46:38.282796] {Default Queue} wl_registry#2.global(24, "zwp_text_input_manager_v3", 1)
[22:46:38.282799] {Default Queue} wl_registry#2.global(25, "ext_foreign_toplevel_list_v1", 1)
[22:46:38.282803] {Default Queue} wl_registry#2.global(26, "zwlr_foreign_toplevel_manager_v1", 3)
[22:46:38.282807] {Default Queue} wl_registry#2.global(27, "ext_session_lock_manager_v1", 1)
[22:46:38.282812] {Default Queue} wl_registry#2.global(28, "ext_workspace_manager_v1", 1)
[22:46:38.282816] {Default Queue} wl_registry#2.global(29, "zwlr_export_dmabuf_manager_v1", 1)
[22:46:38.282820] {Default Queue} wl_registry#2.global(30, "zwlr_screencopy_manager_v1", 3)
[22:46:38.282824] {Default Queue} wl_registry#2.global(31, "ext_image_copy_capture_manager_v1", 1)
[22:46:38.282828] {Default Queue} wl_registry#2.global(32, "ext_output_image_capture_source_manager_v1", 1)
[22:46:38.282832] {Default Queue} wl_registry#2.global(33, "zwlr_data_control_manager_v1", 2)
[22:46:38.282836] {Default Queue} wl_registry#2.global(34, "ext_data_control_manager_v1", 1)
[22:46:38.282840] {Default Queue} wl_registry#2.global(35, "wp_security_context_manager_v1", 1)
[22:46:38.282844] {Default Queue} wl_registry#2.global(36, "wp_viewporter", 1)
[22:46:38.282848] {Default Queue} wl_registry#2.global(37, "wp_single_pixel_buffer_manager_v1", 1)
[22:46:38.282852] {Default Queue} wl_registry#2.global(38, "wp_content_type_manager_v1", 1)
[22:46:38.282856] {Default Queue} wl_registry#2.global(39, "wp_fractional_scale_manager_v1", 1)
[22:46:38.282860] {Default Queue} wl_registry#2.global(40, "ext_foreign_toplevel_image_capture_source_manager_v1", 1)
[22:46:38.282872] {Default Queue} wl_registry#2.global(41, "wp_tearing_control_manager_v1", 1)
[22:46:38.282886] {Default Queue} wl_registry#2.global(42, "zxdg_exporter_v1", 1)
[22:46:38.282890] {Default Queue} wl_registry#2.global(43, "zxdg_importer_v1", 1)
[22:46:38.282894] {Default Queue} wl_registry#2.global(44, "zxdg_exporter_v2", 1)
[22:46:38.282898] {Default Queue} wl_registry#2.global(45, "zxdg_importer_v2", 1)
[22:46:38.282903] {Default Queue} wl_registry#2.global(46, "xdg_activation_v1", 1)
[22:46:38.282907] {Default Queue} wl_registry#2.global(47, "xdg_toplevel_tag_manager_v1", 1)
[22:46:38.282911] {Default Queue} wl_registry#2.global(48, "wp_cursor_shape_manager_v1", 2)
[22:46:38.282915] {Default Queue} wl_registry#2.global(49, "wp_color_representation_manager_v1", 1)
[22:46:38.282919] {Default Queue} wl_registry#2.global(50, "zwp_virtual_keyboard_manager_v1", 1)
[22:46:38.282923] {Default Queue} wl_registry#2.global(51, "zwlr_virtual_pointer_manager_v1", 2)
[22:46:38.282927] {Default Queue} wl_registry#2.global(52, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
[22:46:38.282931] {Default Queue} wl_registry#2.global(53, "zwp_pointer_gestures_v1", 3)
[22:46:38.282935] {Default Queue} wl_registry#2.global(54, "ext_transient_seat_manager_v1", 1)
[22:46:38.282939] {Default Queue} wl_registry#2.global(55, "wl_seat", 9)
[22:46:38.282944] {Default Queue} -> wl_registry#2.bind(55, "wl_seat", 5, new id [unknown]#7)
[22:46:38.282948] {Default Queue} wl_registry#2.global(57, "zwp_primary_selection_device_manager_v1", 1)
[22:46:38.282953] {Default Queue} wl_registry#2.global(58, "wl_output", 4)
[22:46:38.282957] {Default Queue} wl_registry#2.global(59, "wl_output", 4)
[22:46:38.282961] {Default Queue} wl_callback#3.done(16)
[22:46:38.282966] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
[22:46:38.283205] {Display Queue} wl_display#1.delete_id(3)
[22:46:38.283217] {Default Queue} discarded wl_shm#4.format(0)
[22:46:38.283222] {Default Queue} discarded wl_shm#4.format(1)
[22:46:38.283227] {Default Queue} discarded wl_shm#4.format(875709016)
[22:46:38.283231] {Default Queue} discarded wl_shm#4.format(875708993)
[22:46:38.283235] {Default Queue} discarded wl_shm#4.format(875710274)
[22:46:38.283239] {Default Queue} discarded wl_shm#4.format(842094674)
[22:46:38.283242] {Default Queue} discarded wl_shm#4.format(842088786)
[22:46:38.283246] {Default Queue} discarded wl_shm#4.format(892426322)
[22:46:38.283249] {Default Queue} discarded wl_shm#4.format(892420434)
[22:46:38.283253] {Default Queue} discarded wl_shm#4.format(909199186)
[22:46:38.283257] {Default Queue} discarded wl_shm#4.format(808665688)
[22:46:38.283260] {Default Queue} discarded wl_shm#4.format(808665665)
[22:46:38.283264] {Default Queue} discarded wl_shm#4.format(1213351746)
[22:46:38.283268] {Default Queue} discarded wl_shm#4.format(1211384408)
[22:46:38.283271] {Default Queue} discarded wl_shm#4.format(1211384385)
[22:46:38.283275] {Default Queue} discarded wl_shm#4.format(942950210)
[22:46:38.283279] {Default Queue} discarded wl_shm#4.format(942948952)
[22:46:38.283282] {Default Queue} discarded wl_shm#4.format(942948929)
[22:46:38.283286] {Default Queue} wl_seat#7.name("seat0")
[22:46:38.283291] {Default Queue} wl_seat#7.capabilities(1)
[22:46:38.283296] {Default Queue} -> wl_seat#7.get_pointer(new id wl_pointer#8)
[22:46:38.283300] {Default Queue} wl_callback#3.done(16)
[22:46:38.283305] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#3)
[22:46:38.283310] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#9, wl_surface#3)
[22:46:38.283319] {Default Queue} -> xdg_surface#9.get_toplevel(new id xdg_toplevel#10)
[22:46:38.283324] {Default Queue} -> xdg_toplevel#10.set_title("Mosaic Nested Grab Probe")
[22:46:38.283328] {Default Queue} -> xdg_toplevel#10.set_app_id("org.mosaic.NestedGrabProbe")
TOPLEVEL CREATE wl=3 xdg=9 role=10 placement=v_both_cross
[22:46:38.283338] {Default Queue} -> wl_surface#3.commit()
[22:46:38.283679] {Default Queue} xdg_wm_base#6.ping(17)
[22:46:38.283698] {Default Queue} -> xdg_wm_base#6.pong(17)
[22:46:38.283708] {Default Queue} xdg_toplevel#10.configure(0, 0, array[0])
TOPLEVEL ROLE_CONFIGURE w=0 h=0
[22:46:38.283716] {Default Queue} xdg_surface#9.configure(18)
TOPLEVEL XDG_CONFIGURE serial=18 ack
[22:46:38.283724] {Default Queue} -> xdg_surface#9.ack_configure(18)
[22:46:38.284502] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#11, fd 5, 966000)
[22:46:38.284513] {Default Queue} -> wl_shm_pool#11.create_buffer(new id wl_buffer#12, 0, 420, 575, 1680, 1)
[22:46:38.284520] {Default Queue} -> wl_surface#3.attach(wl_buffer#12, 0, 0)
[22:46:38.284525] {Default Queue} -> wl_surface#3.damage_buffer(0, 0, 420, 575)
[22:46:38.284530] {Default Queue} -> wl_surface#3.commit()
[22:46:38.284534] {Default Queue} -> wl_buffer#12.destroy()
[22:46:38.284538] {Default Queue} -> wl_shm_pool#11.destroy()
TOPLEVEL MAP wl=3 xdg=9
[22:46:38.285508] discarded [unknown]#12.[event 0](0 fd, 8 byte)
[22:46:38.285514] {Display Queue} wl_display#1.delete_id(12)
[22:46:38.285519] {Display Queue} wl_display#1.delete_id(11)
[22:46:38.285524] {Default Queue} xdg_toplevel#10.configure(636, 573, array[20])
TOPLEVEL ROLE_CONFIGURE w=636 h=573
[22:46:38.285532] {Default Queue} xdg_surface#9.configure(19)
TOPLEVEL XDG_CONFIGURE serial=19 ack
[22:46:38.285539] {Default Queue} -> xdg_surface#9.ack_configure(19)
[22:46:38.583097] {Default Queue} wl_pointer#8.enter(24, wl_surface#3, 238.00000000, 275.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=24 x=238.00 y=275.00
[22:46:38.583124] {Default Queue} wl_pointer#8.frame()
[22:46:38.583129] {Default Queue} wl_pointer#8.leave(25, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=25
[22:46:38.583137] {Default Queue} wl_pointer#8.frame()
[22:46:39.867771] {Default Queue} wl_pointer#8.enter(30, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=30 x=3.00 y=5.00
[22:46:39.867787] {Default Queue} wl_pointer#8.frame()
[22:46:39.867792] {Default Queue} wl_pointer#8.frame()
[22:46:39.968604] {Default Queue} wl_pointer#8.leave(31, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=31
[22:46:39.968649] {Default Queue} wl_pointer#8.frame()
[22:46:40.117908] {Default Queue} wl_pointer#8.enter(32, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=32 x=3.00 y=5.00
[22:46:40.117939] {Default Queue} wl_pointer#8.frame()
[22:46:40.117943] {Default Queue} wl_pointer#8.button(33, 1002, 272, 1)
POINTER_BUTTON target=TOPLEVEL serial=33 button=272 state=1
[22:46:40.117951] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#11)
[22:46:40.117956] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#12, wl_surface#11)
[22:46:40.117960] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#13)
[22:46:40.117964] {Default Queue} -> xdg_positioner#13.set_size(250, 170)
[22:46:40.117967] {Default Queue} -> xdg_positioner#13.set_anchor_rect(50, 480, 1, 1)
[22:46:40.117970] {Default Queue} -> xdg_positioner#13.set_anchor(5)
[22:46:40.117973] {Default Queue} -> xdg_positioner#13.set_gravity(8)
[22:46:40.117976] {Default Queue} -> xdg_surface#12.get_popup(new id xdg_popup#14, xdg_surface#9, xdg_positioner#13)
POPUP_A CREATE wl=11 xdg=12 role=14 parent=TOPLEVEL parent_xdg=9
POPUP_A GRAB_REQUEST origin=wl_pointer.button target=TOPLEVEL serial=33 used=33
[22:46:40.117984] {Default Queue} -> xdg_popup#14.grab(wl_seat#7, 33)
[22:46:40.117988] {Default Queue} -> wl_surface#11.commit()
[22:46:40.117991] {Default Queue} -> xdg_positioner#13.destroy()
[22:46:40.117994] {Default Queue} wl_pointer#8.frame()
[22:46:40.118126] {Display Queue} wl_display#1.delete_id(13)
[22:46:40.118129] {Default Queue} xdg_popup#14.configure(50, 480, 250, 170)
POPUP_A ROLE_CONFIGURE x=50 y=480 w=250 h=170
[22:46:40.118135] {Default Queue} xdg_surface#12.configure(34)
POPUP_A XDG_CONFIGURE serial=34 ack
[22:46:40.118140] {Default Queue} -> xdg_surface#12.ack_configure(34)
[22:46:40.118286] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#13, fd 5, 170000)
[22:46:40.118298] {Default Queue} -> wl_shm_pool#13.create_buffer(new id wl_buffer#15, 0, 250, 170, 1000, 1)
[22:46:40.118313] {Default Queue} -> wl_surface#11.attach(wl_buffer#15, 0, 0)
[22:46:40.118317] {Default Queue} -> wl_surface#11.damage_buffer(0, 0, 250, 170)
[22:46:40.118320] {Default Queue} -> wl_surface#11.commit()
[22:46:40.118322] {Default Queue} -> wl_buffer#15.destroy()
[22:46:40.118325] {Default Queue} -> wl_shm_pool#13.destroy()
POPUP_A MAP wl=11 xdg=12
[22:46:40.118959] discarded [unknown]#15.[event 0](0 fd, 8 byte)
[22:46:40.118962] {Display Queue} wl_display#1.delete_id(15)
[22:46:40.118965] {Display Queue} wl_display#1.delete_id(13)
[22:46:40.197365] {Default Queue} wl_pointer#8.button(35, 1003, 272, 0)
POINTER_BUTTON target=TOPLEVEL serial=35 button=272 state=0
[22:46:40.197412] {Default Queue} wl_pointer#8.frame()
[22:46:40.298184] {Default Queue} wl_pointer#8.leave(36, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=36
[22:46:40.298225] {Default Queue} wl_pointer#8.frame()
[22:46:40.585174] {Default Queue} wl_pointer#8.enter(37, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=37 x=3.00 y=5.00
[22:46:40.585201] {Default Queue} wl_pointer#8.frame()
[22:46:40.585208] {Default Queue} wl_pointer#8.leave(38, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=38
[22:46:40.585221] {Default Queue} wl_pointer#8.frame()
[22:46:40.585227] {Default Queue} wl_pointer#8.enter(39, wl_surface#11, 4.00000000, 5.00000000)
POINTER_ENTER target=POPUP_A wl=11 serial=39 x=4.00 y=5.00
[22:46:40.585240] {Default Queue} wl_pointer#8.frame()
[22:46:40.585246] {Default Queue} wl_pointer#8.frame()
[22:46:40.685838] {Default Queue} wl_pointer#8.leave(40, wl_surface#11)
POINTER_LEAVE target=POPUP_A serial=40
[22:46:40.685909] {Default Queue} wl_pointer#8.frame()
[22:46:40.835646] {Default Queue} wl_pointer#8.enter(41, wl_surface#11, 4.00000000, 5.00000000)
POINTER_ENTER target=POPUP_A wl=11 serial=41 x=4.00 y=5.00
[22:46:40.835675] {Default Queue} wl_pointer#8.frame()
[22:46:40.835682] {Default Queue} wl_pointer#8.button(42, 1005, 272, 1)
POINTER_BUTTON target=POPUP_A serial=42 button=272 state=1
[22:46:40.835696] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#13)
[22:46:40.835704] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#15, wl_surface#13)
[22:46:40.835712] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#16)
[22:46:40.835719] {Default Queue} -> xdg_positioner#16.set_size(170, 130)
[22:46:40.835726] {Default Queue} -> xdg_positioner#16.set_anchor_rect(20, 80, 1, 1)
[22:46:40.835733] {Default Queue} -> xdg_positioner#16.set_anchor(5)
[22:46:40.835738] {Default Queue} -> xdg_positioner#16.set_gravity(8)
[22:46:40.835745] {Default Queue} -> xdg_surface#15.get_popup(new id xdg_popup#17, xdg_surface#12, xdg_positioner#16)
POPUP_B CREATE wl=13 xdg=15 role=17 parent=POPUP_A parent_xdg=12
POPUP_B GRAB_REQUEST origin=wl_pointer.button target=POPUP_A serial=42 used=42
[22:46:40.835761] {Default Queue} -> xdg_popup#17.grab(wl_seat#7, 42)
[22:46:40.835768] {Default Queue} -> wl_surface#13.commit()
[22:46:40.835775] {Default Queue} -> xdg_positioner#16.destroy()
[22:46:40.835781] {Default Queue} wl_pointer#8.frame()
[22:46:40.835930] {Display Queue} wl_display#1.delete_id(16)
[22:46:40.835937] {Default Queue} xdg_popup#17.configure(20, 80, 170, 130)
POPUP_B ROLE_CONFIGURE x=20 y=80 w=170 h=130
[22:46:40.835949] {Default Queue} xdg_surface#15.configure(43)
POPUP_B XDG_CONFIGURE serial=43 ack
[22:46:40.835960] {Default Queue} -> xdg_surface#15.ack_configure(43)
[22:46:40.836118] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#16, fd 5, 88400)
[22:46:40.836128] {Default Queue} -> wl_shm_pool#16.create_buffer(new id wl_buffer#18, 0, 170, 130, 680, 1)
[22:46:40.836135] {Default Queue} -> wl_surface#13.attach(wl_buffer#18, 0, 0)
[22:46:40.836142] {Default Queue} -> wl_surface#13.damage_buffer(0, 0, 170, 130)
[22:46:40.836149] {Default Queue} -> wl_surface#13.commit()
[22:46:40.836155] {Default Queue} -> wl_buffer#18.destroy()
[22:46:40.836168] {Default Queue} -> wl_shm_pool#16.destroy()
POPUP_B MAP wl=13 xdg=15

View file

@ -0,0 +1,19 @@
--- a/scene.c
+++ b/scene.c
@@ -983 +983 @@
-void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip) {
+bool wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip) {
@@ -989,2 +989,2 @@
- if (!clip && !entry) return;
- if (clip && entry && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return;
+ if (!clip && !entry) return true;
+ if (clip && entry && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return true;
@@ -1000 +1000,5 @@
- abort();
+ // WHY: a mandatory clip must not fail open even if callers ignore false.
+ // CONTRACT: mask synchronously; caller owns future visibility admission.
+ // INVARIANT: never automatically re-enable an intentionally hidden tree.
+ wlr_scene_node_set_enabled(&tree->node, false);
+ return false;
@@ -1013,0 +1018 @@
+ return true;

View file

@ -0,0 +1,5 @@
--- a/scene.h
+++ b/scene.h
@@ -472 +472 @@
-void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip);
+bool wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip);

View file

@ -0,0 +1,60 @@
--- a/transaction.c
+++ b/transaction.c
@@ -911,0 +912,2 @@
+static void probe_clip_enable_tiling(struct sway_workspace *ws, bool desired);
+
@@ -963 +965 @@
- wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs);
+ probe_clip_enable_tiling(child, !fs);
@@ -1009 +1011 @@
- wlr_scene_tree_set_clip(child->layers.tiling, &clip);
+ if (!wlr_scene_tree_set_clip(child->layers.tiling, &clip)) return;
@@ -1011 +1013 @@
- wlr_scene_tree_set_clip(child->layers.tiling, NULL);
+ if (!wlr_scene_tree_set_clip(child->layers.tiling, NULL)) return;
@@ -1013,0 +1016 @@
+ probe_clip_enable_tiling(child, true);
@@ -1024 +1027 @@
- wlr_scene_node_set_enabled(&child->layers.tiling->node, true);
+ probe_clip_enable_tiling(child, true);
@@ -1035 +1038 @@
- wlr_scene_node_set_enabled(&child->layers.tiling->node, false);
+ probe_clip_enable_tiling(child, false);
@@ -1042,0 +1046,10 @@
+}
+
+// WHY: fading-out also enables tiling without retrying clip installation.
+// CONTRACT: upstream decides desired visibility; absent mandatory clip vetoes it.
+// INVARIANT: false desired never becomes true; no OOM latch allocation is needed.
+static void probe_clip_enable_tiling(struct sway_workspace *ws, bool desired) {
+ struct wlr_box clip;
+ bool admitted = ws->current.layout != L_MOSAIC || !ws->mosaic ||
+ wlr_scene_tree_get_clip(ws->layers.tiling, &clip);
+ wlr_scene_node_set_enabled(&ws->layers.tiling->node, desired && admitted);
@@ -1098 +1111 @@
- wlr_scene_tree_set_clip(popup_tree, NULL);
+ if (!wlr_scene_tree_set_clip(popup_tree, NULL)) return;
@@ -1106 +1119 @@
- wlr_scene_tree_set_clip(popup_tree, NULL);
+ if (!wlr_scene_tree_set_clip(popup_tree, NULL)) return;
@@ -1114 +1127 @@
- wlr_scene_tree_set_clip(popup_tree, NULL);
+ if (!wlr_scene_tree_set_clip(popup_tree, NULL)) return;
@@ -1125 +1138 @@
- wlr_scene_tree_set_clip(popup_tree, &empty);
+ if (!wlr_scene_tree_set_clip(popup_tree, &empty)) return;
@@ -1133 +1146 @@
- wlr_scene_tree_set_clip(popup_tree, &empty);
+ if (!wlr_scene_tree_set_clip(popup_tree, &empty)) return;
@@ -1144 +1157 @@
- wlr_scene_tree_set_clip(popup_tree, &empty);
+ if (!wlr_scene_tree_set_clip(popup_tree, &empty)) return;
@@ -1165 +1178 @@
- wlr_scene_tree_set_clip(popup_tree, &empty);
+ if (!wlr_scene_tree_set_clip(popup_tree, &empty)) return;
@@ -1178 +1191 @@
- wlr_scene_tree_set_clip(popup_tree, &empty);
+ if (!wlr_scene_tree_set_clip(popup_tree, &empty)) return;
@@ -1183 +1196 @@
- wlr_scene_tree_set_clip(popup_tree, &global);
+ if (!wlr_scene_tree_set_clip(popup_tree, &global)) return;

View file

@ -0,0 +1,68 @@
--- a/scene.c
+++ b/scene.c
@@ -57,0 +58 @@
+ bool active; // Private sidecar only: no installed tree/node ABI change.
@@ -62 +63 @@
-static struct scene_tree_clip *scene_tree_clip_find(struct wlr_scene_tree *tree) {
+static struct scene_tree_clip *scene_tree_clip_storage(struct wlr_scene_tree *tree) {
@@ -66,0 +68,8 @@
+}
+
+// WHY: reservation must survive clip removal to make later admission allocation-free.
+// CONTRACT: inactive reservations are absent to render/hit/get callers.
+// INVARIANT: lookup never exposes an uninitialized clip rectangle.
+static struct scene_tree_clip *scene_tree_clip_find(struct wlr_scene_tree *tree) {
+ struct scene_tree_clip *entry = scene_tree_clip_storage(tree);
+ return entry && entry->active ? entry : NULL;
@@ -286,0 +296,5 @@
+ // WHY: failure must occur before tree publication to parent scene children.
+ // CONTRACT: both tree and reservation are acquired before scene_tree_init.
+ // INVARIANT: failed reservation frees unpublished tree, no listener attached.
+ struct scene_tree_clip *entry = calloc(1, sizeof(*entry));
+ if (!entry) { free(tree); return NULL; }
@@ -287,0 +302,5 @@
+ entry->tree = tree;
+ entry->destroy.notify = scene_tree_clip_destroy;
+ wl_signal_add(&tree->node.events.destroy, &entry->destroy);
+ entry->next = scene_tree_clips;
+ scene_tree_clips = entry;
@@ -984,29 +1003,10 @@
- // CONTRACT: tree is required; a supplied box is copied and must have
- // nonnegative dimensions. NULL removes the entry, while a zero box masks.
- assert(tree);
- assert(!clip || (clip->width >= 0 && clip->height >= 0));
- struct scene_tree_clip *entry = scene_tree_clip_find(tree);
- if (!clip && !entry) return;
- if (clip && entry && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return;
- wlr_log(WLR_INFO, "SPIKE_CLIP tree=%p rect=%d,%d %dx%d",
- (void *)tree, clip ? clip->x : 0, clip ? clip->y : 0,
- clip ? clip->width : 0, clip ? clip->height : 0);
- if (clip && !entry) {
- entry = calloc(1, sizeof(*entry));
- // WHY: this void API cannot report an uninstalled mandatory clip.
- // CONTRACT/INVARIANT: allocation failure must never expose unclipped content.
- if (!entry) {
- wlr_log(WLR_ERROR, "Failed to allocate mandatory scene tree clip");
- abort();
- }
- entry->tree = tree;
- entry->destroy.notify = scene_tree_clip_destroy;
- wl_signal_add(&tree->node.events.destroy, &entry->destroy);
- entry->next = scene_tree_clips;
- scene_tree_clips = entry;
- }
- if (clip) {
- entry->box = *clip;
- } else {
- scene_tree_clip_remove(entry);
- }
+ // WHY: no heap allocation may occur when exposing an existing tree.
+ // CONTRACT: trees must come from the private reservation-aware create path.
+ // INVARIANT: NULL deactivates but never releases the reservation.
+ assert(tree && (!clip || (clip->width >= 0 && clip->height >= 0)));
+ struct scene_tree_clip *entry = scene_tree_clip_storage(tree);
+ assert(entry);
+ if (!clip && !entry->active) return;
+ if (clip && entry->active && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return;
+ entry->active = clip != NULL;
+ if (clip) entry->box = *clip;

View file

@ -0,0 +1,188 @@
# STEP20C — alternatives OOM privées sur corps C réels
**EXPERIMENTAL, source-only. CORE_PRODUCTION_OOM_POLICY=UNDECIDED.**
Aucune modification production, aucun nouveau build compositeur/SceneFX, aucune
réservation runtime, aucune adoption de politique. Le CORE historique exact
`29baf31aa3eeed259825c87d0799fa5d3dc83710afb967154604eb1c6657f965`
reste inchangé. A reste **OOM_ABORT_EXPERIMENTAL** ; la disponibilité production
est **PRODUCTION_AVAILABILITY_NOT_APPROVED**.
## Reproduction et provenance
Depuis la racine du dépôt, choisir un **nouveau** dossier privé hors Git :
```sh
PYTHONDONTWRITEBYTECODE=1 python3 tooling/probes/step20c-core-recovery/oom/run.py /chemin/prive/nouveau-oom
PYTHONDONTWRITEBYTECODE=1 python3 tooling/probes/step20c-core-recovery/oom/negative.py /chemin/prive/nouveau-oom
```
Le parent historique en lecture seule est
`/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/`. `prepare.py` exige les hashes
connus des sources complètes, construit trois **copies complètes privées**
`{A,B,C}/scene.c`, `transaction.c`, `scene.h`, puis extrait leurs intervalles
exacts. Les noms complets ne sont jamais ceux du harnais (`harness-scene.c`).
Les sources complètes, slices, binaires et logs restent hors Git. Les petits
patches sans contexte préservés ici sont les deltas exacts des copies privées ; ce ne sont pas
le patch cumulatif production. `sources.json` lie chaque parent, prototype et
patch ; `bindings.json` lie les slices aux sources complètes, offsets et SHA.
Le header B change seulement la déclaration privée void→bool ; aucun header
installé n'est modifié. C conserve le header byte-identique.
Parent scène : `99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d`.
Parent transaction : `0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd`.
Parent header : `bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d`.
Les parents viennent du noyau privé minimal figé au ticket précédent, pas des
sources productives du dépôt ni d'un modèle d'allocation réinventé.
## A — arrêt contrôlé
Corps setter, sidecar, destroy, clips, hit et popup **byte-identiques** au parent.
Les injections ciblent uniquement son calloc : première sidecar, ou deuxième
après réussite d'une première sur un autre arbre. Résultat attendu et obtenu :
**-6 / SIGABRT** dans les trois modes normal/UBSan/ASan, `RLIMIT_CORE=(0,0)`.
L'arrêt refuse le fail-open mais tue le processus. Récupération de session,
retour au SwayFX quotidien, réouverture des applications : **NOT TESTED**.
Le test mutant retire abort et exige que cette mutation soit détectée via
sortie99, plutôt qu'un abort artificiel dans le harnais.
## B — bool, masquage synchrone et admission du propriétaire
Le setter exact modifié retourne true pour succès/NULL/idempotence. En OOM,
il appelle le vrai corps `wlr_scene_node_set_enabled(false)` extrait puis
retourne false. Il n'existe donc pas de retour false laissant cet arbre enabled.
Le setter ne réactive jamais l'arbre : le propriétaire conserve l'autorité de
visibilité. Un popup qui a subi OOM reste désactivé même après réussite ou
retour standard ; la récupération de cette disponibilité nécessitera un futur
contrat explicite. Ce compromis privé est intentionnel et n'est pas une UX
popup adoptée.
L'audit trouve **11 appels** setter dans transaction.c : deux pour tiling
(Mosaic/standard) et neuf pour popup (standard/no owner, ancêtre fullscreen
current, non-tiled/workspace fullscreen, inactive/missing clip/coords,
translation/intersection invalides, propriétaire caché, viewport final).
Chaque appel privé vérifie le résultat et retourne sur échec. Le retour
arrange_output n'empêche pas arrange_root d'arranger ensuite les popups ; un
clip tiling absent mène au masque popup. Cette chaîne complète est auditée dans
les sources mais n'est pas exécutée par un compositeur dans ce ticket.
L'audit des enable tiling révèle le risque de réactivation par fading-out
(parent ligne1024) sans nouveau setter. Le helper privé
`probe_clip_enable_tiling(ws, desired)` exige un clip appliqué si Mosaic est
courant. Les demandes active, fading-out et inactive passent par ce helper ;
une installation réussie est suivie d'une admission active explicite.
`desired=false` reste false. NULL standard peut être admis par la demande
upstream suivante ; un clip vide reste un clip valide et masque les hits.
La régression compile le **corps exact du helper** et la **statement exacte de
la branche fading-out**, avec état workspace réduit. Le corps arrange_output
complet n'est pas compilé et la cohérence transaction/animation reste à tester.
Les autres enable de transaction.c concernent des descendants de conteneurs,
fullscreen/floating ou les couches root/output : ils ne réactivent pas le
workspace tiling désactivé. `disable_workspace` désactive seulement. L'audit
`set_enabled` du Sway privé ne trouve pas de réactivation directe du wrapper
popup XDG dans xdg_shell.c ; son arbre interne de surface et les popups IME
sont des arbres distincts. Les animations de workspace passent par
arrange_root/arrange_output. Reparent ne remplace pas l'identité du wrapper ni
son enabled. Ce constat de source n'est pas une preuve universelle de tous les
consommateurs SceneFX possibles ; aucun API général fail-closed n'est annoncé.
Le vrai enabled extrait collecte l'ancienne visibilité avant disabled puis
appelle scene_node_update avec cette région. Le harnais vérifie la transmission
d'une région antérieure non vide au callback réduit. Les changements de clip
réussis gardent scene_node_update du parent. La visibilité/damage réelle,
effects, ancien/nouveau viewport et pixels périmés sont **NOT PROVEN** :
scene_node_visibility et scene_node_update sont des supports, pas le moteur.
## C — réservation sidecar avant publication
Le vrai corps create est modifié dans une copie privée : première allocation
arbre, deuxième allocation réservation ; échec de la seconde libère l'arbre
**avant scene_tree_init**, sans listener ni publication au parent. Après les
deux succès seulement, init publie et le listener de destroy est attaché.
Le setter réutilise cette réservation et ne contient plus d'allocation. NULL
la rend inactive, un zéro reste actif, destroy la libère. Les walkers/get ne
voient que les entrées actives ; une ancienne box inactive ne peut pas devenir
un clip effectif. Un clip nouveau écrase la box avant la mise à jour.
Ce contrat exige des arbres créés par ce create privé. Le root embarqué du
scene n'y passe pas et ne peut pas être donné au setter C ; aucun appel actuel
Mosaic ne clippe ce root. Les chemins de création externes/embarqués et tous
les consommateurs de create doivent être qualifiés avant adoption. Un appelant
qui ne traite déjà pas NULL peut échouer ailleurs : la survie d'une session
complète n'est **pas** acquise par le retour NULL de ce create.
Aucun champ tree/node public n'est ajouté. `bool active` appartient à la
sidecar privée. `memory.c` compile sa déclaration **exacte** avec le vrai
`wlr_box` des headers wlroots0.20 et le vrai `wl_listener` Wayland :
A/B **56 octets par arbre effectivement clippé** ; C **64 octets pour chaque
arbre créé**, même jamais clippé, plus allocations/metadata allocateur non
mesurées. Pointeurs8, box offset16, listener offset32 (A) /40 (C).
La taille tree historique128 n'est pas remesurée ici, et les types réduits de
harnais ne servent jamais de preuve ABI. C ajoute une allocation à chaque
create et retient la mémoire jusqu'au destroy ; coût total proportionnel au
nombre de tous les arbres, non seulement aux fenêtres Mosaic.
## Tests obtenus et frontière de preuve
LAB exécuté : `/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/`.
`commands.json` contient **40 commandes aux codes attendus** : neuf builds
normal/UBSan/ASan, 27 exécutions (defined + injections1/2), deux builds et deux
exécutions de taille sidecar. Tous B/C retournent0. ASan leak detection activée,
UBSan halt-on-error ; aucun diagnostic sanitaire hors arrêts volontaires.
Listes, listeners et signaux destroy sont Wayland natif, régions Pixman natives.
Ce ne sont pas des builds sanitaires du compositeur ni de ces bibliothèques.
A/B réutilisent les tests lifecycle : copie/idempotence, NULL/zéro/retrait,
parent-child, entrée après exclusion clip, destructions parent/child. B ajoute
OOM sans hit descendant et transmission ancienne région, aucune réactivation
implicite, activation/fading/standard admis seulement selon desired/clip.
C teste le vrai create extrait sur chaque allocation, aucune publication ni
listener à l'échec, NULL→clip→zéro→retrait→réinstallation sans allocation,
parent-child, hit et changement de pointeur parent, destroy parent avant child.
Le reparent fixture n'est **pas** le vrai corps reparent/damage.
A/B/C exécutent le vrai probe_clip_popup : current fullscreen ancêtre et
pending contradictoire, standard, workspace inactif, propriétaire complètement
caché puis revenu, fullscreen workspace, propriétaire non tiled, clip tiling
absent et owner absent ; nested hérite le clip root. Les arbres stack de ce
test C reçoivent une réservation par `reserve_fixture`, support explicite et
non preuve du create réel ; celui-ci est vérifié séparément avec allocations.
Aucun XDG/grab/serial réel ni saved-buffer natif n'est exercé. Les saved buffers
héritent le clip tiling par leur arbre selon l'audit historique, pas par une
nouvelle mesure runtime OOM dans ce lot.
`negative.py` compile six mutants puis exige leur rejet : admission ignorée,
abort retiré, false sans masque, réactivation après setter, publication avant
réservation, et clip actif supprimé. Résultats bruts respectifs :
`-6,99,-6,-6,-6,-6`. Ces SIGABRT sont des assertions de régression intentionnelles,
distinctes du crash A. Le premier essai de l'ajout fading extrayait trop de
texte et échouait à compiler ; les commandes/stderr sont préservés dans
`LAB/oom/attempt-4/`. La correction resserre le marqueur à la statement exacte.
Aucun échec de compilation n'est reclassé comme résultat produit.
**CORE_OOM_C_TESTS=PARTIALLY_FEASIBLE** pour ces chemins C.
**CORE_OOM_ALTERNATIVES_REVIEW=NOT_PROVEN**, revue indépendante à acquérir.
**CORE_PRODUCTION_OOM_POLICY=UNDECIDED**.
## Recommandation technique, décision humaine restante
C évite l'allocation au moment de l'exposition et préserve la signature, au prix
de mémoire sur tous les arbres et d'une nouvelle obligation des create callers.
B réduit ce coût aux arbres effectivement clippés et rend l'échec explicite,
avec une signature privée différente, un audit d'admission obligatoire et une
perte de disponibilité locale possible des popups. A est le plus simple mais
transforme une allocation locale en perte du compositeur entier. Préférer
l'étude future de C pour un chemin d'exposition sans allocation, comparer son
coût réel, et garder B comme alternative si l'admission/recovery des owners peut
être qualifiée. Ce conseil ne tranche pas le choix disponibilité/fail-closed.
Prochain plus petit pas : revue indépendante de ces octets puis, sous un futur
ticket explicite, qualification d'un seul choix sur compositeur privé complet :
injections réelles premières/deuxièmes allocations, hide/return et fullscreen,
popups nested avec grabs, saved timeout, reparent/destroy, animations et retour
standard, ancien/nouveau damage à chaque échec, input/applied transaction/present,
pixels périmés/FX, shutdown et récupération session. Aucun de ces tests natifs
n'est autorisé ou annoncé PASS par cette fixture.
STEP20A DONE ; STEP20B EXPERIMENTAL ; STEP20C NEXT ; STEP20D–G PLANNED ;
PRODUCTION_MULTI_OUTPUT NOT_YET_SUPPORTED ; PRODUCTION_AUTHORIZED FALSE.

View file

@ -0,0 +1,252 @@
[
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 1904,
"end_byte_exclusive": 2043,
"start_line": 54,
"slice_sha256": "324134e46f46071429e32811f546e29366005d4bbaba562d3d42c4dea1d93d7b",
"output": "size-struct.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 60610,
"end_byte_exclusive": 60961,
"start_line": 1888,
"slice_sha256": "a43a90db7447e90a450bf23ace8fc10b6db3a4bb3aa70f9ad3cd534b403e0803",
"output": "enabled.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 1904,
"end_byte_exclusive": 2939,
"start_line": 54,
"slice_sha256": "d4c8f7cf78bfcc0e3177942be1d39019c1f2fbd43c35cdbdff586a1f10c4b34b",
"output": "sidecar.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 23041,
"end_byte_exclusive": 25162,
"start_line": 721,
"slice_sha256": "78c83c3f11cba654b0a099aca06d10b36299f5a6a199ba2cd08df8943da02923",
"output": "clip.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 31395,
"end_byte_exclusive": 33052,
"start_line": 983,
"slice_sha256": "1e70ed409c8a55d67fea7f1d166edeaa8fd5fe1237aecd9be64cef20c75ed997",
"output": "setter.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 64327,
"end_byte_exclusive": 65551,
"start_line": 2037,
"slice_sha256": "31423e212f75590085a4ad75c3ce60555c849e579e0bc0497c04d03b71d0104b",
"output": "hit.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/scene.c",
"source_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"start_byte": 8966,
"end_byte_exclusive": 9208,
"start_line": 279,
"slice_sha256": "fc7de10f87ac63d5bacc88db28fddbfb70f0e6ddeef30266785a12c266c2c46c",
"output": "create.inc"
},
{
"mode": "A",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/A/transaction.c",
"source_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"start_byte": 38141,
"end_byte_exclusive": 43558,
"start_line": 1051,
"slice_sha256": "cc962a736fa68496753485de7fd3dd971857cce894c977a7a3fbfd2837238511",
"output": "popup.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 1904,
"end_byte_exclusive": 2043,
"start_line": 54,
"slice_sha256": "324134e46f46071429e32811f546e29366005d4bbaba562d3d42c4dea1d93d7b",
"output": "size-struct.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 60921,
"end_byte_exclusive": 61272,
"start_line": 1893,
"slice_sha256": "a43a90db7447e90a450bf23ace8fc10b6db3a4bb3aa70f9ad3cd534b403e0803",
"output": "enabled.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 1904,
"end_byte_exclusive": 2939,
"start_line": 54,
"slice_sha256": "d4c8f7cf78bfcc0e3177942be1d39019c1f2fbd43c35cdbdff586a1f10c4b34b",
"output": "sidecar.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 23041,
"end_byte_exclusive": 25162,
"start_line": 721,
"slice_sha256": "78c83c3f11cba654b0a099aca06d10b36299f5a6a199ba2cd08df8943da02923",
"output": "clip.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 31395,
"end_byte_exclusive": 33363,
"start_line": 983,
"slice_sha256": "4a768da90342006934a4bfd27fe8178d1e8028644f02402afd672c107503bf56",
"output": "setter.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 64638,
"end_byte_exclusive": 65862,
"start_line": 2042,
"slice_sha256": "31423e212f75590085a4ad75c3ce60555c849e579e0bc0497c04d03b71d0104b",
"output": "hit.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/scene.c",
"source_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"start_byte": 8966,
"end_byte_exclusive": 9208,
"start_line": 279,
"slice_sha256": "fc7de10f87ac63d5bacc88db28fddbfb70f0e6ddeef30266785a12c266c2c46c",
"output": "create.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/transaction.c",
"source_sha256": "ac4ffbb848e09853898e3da2e2448e35e0ec58b65cf9d6c06dc52e15c25e1b8c",
"start_byte": 38012,
"end_byte_exclusive": 44293,
"start_line": 1051,
"slice_sha256": "67036e400af85dfc3ae2fd703dc1365cfae38b8a45c9cb36e1f68a23be996b31",
"output": "popup.inc"
},
{
"mode": "B",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/B/transaction.c",
"source_sha256": "ac4ffbb848e09853898e3da2e2448e35e0ec58b65cf9d6c06dc52e15c25e1b8c",
"start_byte": 37184,
"end_byte_exclusive": 37226,
"start_line": 1026,
"slice_sha256": "9e4c3b6bb025e3afafebdc95f0e3c50c8d88fc686578a741768960857d9a8842",
"output": "fading.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 1904,
"end_byte_exclusive": 2117,
"start_line": 54,
"slice_sha256": "fd0bcb383fc91eb4b94a740506f3b571640bf101a4ea0578911b3ee0a85f687b",
"output": "size-struct.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 61042,
"end_byte_exclusive": 61393,
"start_line": 1888,
"slice_sha256": "a43a90db7447e90a450bf23ace8fc10b6db3a4bb3aa70f9ad3cd534b403e0803",
"output": "enabled.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 1904,
"end_byte_exclusive": 3441,
"start_line": 54,
"slice_sha256": "f2db5faadee25905b430cd506073482170994fb90acf886475ab7c9606cc9114",
"output": "sidecar.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 24072,
"end_byte_exclusive": 26193,
"start_line": 740,
"slice_sha256": "78c83c3f11cba654b0a099aca06d10b36299f5a6a199ba2cd08df8943da02923",
"output": "clip.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 32426,
"end_byte_exclusive": 33484,
"start_line": 1002,
"slice_sha256": "f0102ce00c5a6e0ddbda91f2885e100769b3b825dab98c85bcdb3f4f275b0011",
"output": "setter.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 64759,
"end_byte_exclusive": 65983,
"start_line": 2037,
"slice_sha256": "31423e212f75590085a4ad75c3ce60555c849e579e0bc0497c04d03b71d0104b",
"output": "hit.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/scene.c",
"source_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"start_byte": 9468,
"end_byte_exclusive": 10239,
"start_line": 288,
"slice_sha256": "532fa4c7c6baa7ed02118d30e348e86123da86b044774911e4a848d855c893e3",
"output": "create.inc"
},
{
"mode": "C",
"source": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5/oom/C/transaction.c",
"source_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"start_byte": 38141,
"end_byte_exclusive": 43558,
"start_line": 1051,
"slice_sha256": "cc962a736fa68496753485de7fd3dd971857cce894c977a7a3fbfd2837238511",
"output": "popup.inc"
}
]

View file

@ -0,0 +1,56 @@
#include "support.h"
// WHY: popup ownership is tested without transaction/scene construction.
// CONTRACT: each reduced state exposes precisely the fields used by popup.inc.
// INVARIANT: current ancestry is distinct from deliberately contradictory pending state.
enum {L_STANDARD,L_MOSAIC,FULLSCREEN_NONE=0,FULLSCREEN_WORKSPACE=1};
struct sway_workspace;
struct sway_container {struct {struct sway_workspace *workspace;struct sway_container *parent;int fullscreen_mode;} current,pending; struct {int current_width,current_height;} animation_state;struct wlr_scene_tree *scene_tree;};
struct list {void **items;int length;};
static int list_find(struct list *l,void *p){for(int i=0;i<l->length;i++)if(l->items[i]==p)return i;return -1;}
struct sway_output {int lx,ly,width,height;struct {struct sway_workspace *active_workspace;} current;};
struct sway_workspace {struct {int layout;struct list *tiling;struct sway_container *fullscreen;struct sway_output *output;} current;void *mosaic;struct {struct wlr_scene_tree *tiling;} layers;};
struct sway_view {struct sway_container *container;};
struct sway_popup_desc {struct sway_view *view;};
#include "popup.inc"
static unsigned input_calls;
static bool input(struct wlr_scene_buffer *b,double *x,double *y){(void)b;(void)x;(void)y;input_calls++;return true;}
static void expect_box(struct wlr_scene_tree *t,int x,int y,int w,int h){struct wlr_box b;assert(wlr_scene_tree_get_clip(t,&b));assert(b.x==x&&b.y==y&&b.width==w&&b.height==h);}
static void destroy(struct wlr_scene_tree *t){wl_signal_emit_mutable(&t->node.events.destroy,NULL);assert(wl_list_empty(&t->node.events.destroy.listener_list));}
// WHY: native Pixman may retain a zero-area rectangle as a nonempty region.
// CONTRACT: masking requires zero pixels, not a particular region encoding.
// INVARIANT: any rectangle with positive width AND height fails this check.
static bool has_pixels(pixman_region32_t *r){int count;pixman_box32_t *boxes=pixman_region32_rectangles(r,&count);for(int i=0;i<count;i++)if(boxes[i].x2>boxes[i].x1&&boxes[i].y2>boxes[i].y1)return true;return false;}
static void lifecycle(void){
struct wlr_scene_tree p,c;init_node(&p.node,WLR_SCENE_NODE_TREE,NULL,10,20);init_node(&c.node,WLR_SCENE_NODE_TREE,&p,5,5);
struct wlr_scene_buffer leaf={.point_accepts_input=input};init_node(&leaf.node,WLR_SCENE_NODE_BUFFER,&c,0,0);
struct wlr_box out,b={0,0,100,100};assert(!wlr_scene_tree_get_clip(&p,&out));wlr_scene_tree_set_clip(&p,NULL);assert(updates==0&&allocations==0);
wlr_scene_tree_set_clip(&p,&b);assert(allocations==1&&clip_count()==1&&wl_list_length(&p.node.events.destroy.listener_list)==1);
b.width=80;wlr_scene_tree_set_clip(&p,&b);assert(allocations==1&&clip_count()==1&&updates==2&&wl_list_length(&p.node.events.destroy.listener_list)==1);
wlr_scene_tree_set_clip(&p,&b);assert(updates==2);b.width=1;expect_box(&p,0,0,80,100);
b=(struct wlr_box){0,0,100,20};wlr_scene_tree_set_clip(&c,&b);assert(clip_count()==2);
assert(inside(&leaf.node,16,26));assert(!inside(&leaf.node,95,30));assert(hit(&leaf.node,16,26)&&input_calls==1);assert(!hit(&leaf.node,95,30)&&input_calls==1);
pixman_region32_t r;pixman_region32_init_rect(&r,-100,-100,400,400);intersect(&leaf.node,&r);pixman_box32_t *ext=pixman_region32_extents(&r);assert(ext->x1==15&&ext->y1==25&&ext->x2==90&&ext->y2==45);pixman_region32_fini(&r);
b=(struct wlr_box){0};wlr_scene_tree_set_clip(&c,&b);expect_box(&c,0,0,0,0);assert(!hit(&leaf.node,16,26)&&input_calls==1);
pixman_region32_init_rect(&r,0,0,100,100);intersect(&leaf.node,&r);assert(!has_pixels(&r));pixman_region32_fini(&r);
wlr_scene_tree_set_clip(&c,NULL);assert(!wlr_scene_tree_get_clip(&c,&out)&&clip_count()==1);unsigned before=updates;wlr_scene_tree_set_clip(&c,NULL);assert(updates==before);assert(hit(&leaf.node,16,26)&&input_calls==2);
wlr_scene_tree_set_clip(&c,&b);destroy(&p);assert(clip_count()==1);destroy(&c);assert(clip_count()==0);
b=(struct wlr_box){1,2,3,4};assert(translate(&b,10,20,&out)&&out.x==11&&out.y==22);assert(!translate(&b,INT_MAX,0,&out));b.width=-1;assert(!translate(&b,0,0,&out));
// Exact translation rejection must empty render region and reject input too.
b=(struct wlr_box){INT_MAX,0,1,1};wlr_scene_tree_set_clip(&p,&b);assert(!inside(&p.node,0,0));pixman_region32_init_rect(&r,0,0,10,10);intersect(&p.node,&r);assert(!has_pixels(&r));pixman_region32_fini(&r);destroy(&p);
puts("PASS lifecycle: NULL/zero/copy/idempotence/single allocation/listener/removal/native destroy/native Pixman/inherited hit-before-input/translation rejection");
}
static void popups(void){
struct wlr_scene_tree tiling,owner_scene,popup,nested;init_node(&tiling.node,WLR_SCENE_NODE_TREE,NULL,10,20);init_node(&owner_scene.node,WLR_SCENE_NODE_TREE,NULL,20,30);init_node(&popup.node,WLR_SCENE_NODE_TREE,NULL,25,35);init_node(&nested.node,WLR_SCENE_NODE_TREE,&popup,0,0);
struct sway_output output={.width=200,.height=200};struct sway_container parent={.scene_tree=&owner_scene,.animation_state={30,30}},child={0};void *items[]={&parent};struct list list={items,1};struct sway_workspace ws={.current={.layout=L_MOSAIC,.tiling=&list,.output=&output},.mosaic=&ws,.layers={&tiling}};output.current.active_workspace=&ws;child.current.workspace=&ws;child.current.parent=&parent;struct sway_view view={&child};struct sway_popup_desc desc={&view};struct wlr_box b={0,0,100,100},out;wlr_scene_tree_set_clip(&tiling,&b);
child.pending.fullscreen_mode=FULLSCREEN_WORKSPACE;probe_clip_popup(&popup.node,&desc);expect_box(&popup,-15,-15,100,100);assert(inside(&nested.node,30,40)&&!inside(&nested.node,150,40));
parent.current.fullscreen_mode=FULLSCREEN_WORKSPACE;probe_clip_popup(&popup.node,&desc);assert(!wlr_scene_tree_get_clip(&popup,&out));parent.current.fullscreen_mode=FULLSCREEN_NONE;
ws.current.layout=L_STANDARD;probe_clip_popup(&popup.node,&desc);assert(!wlr_scene_tree_get_clip(&popup,&out));ws.current.layout=L_MOSAIC;
output.current.active_workspace=NULL;probe_clip_popup(&popup.node,&desc);expect_box(&popup,0,0,0,0);output.current.active_workspace=&ws;
owner_scene.node.x=150;probe_clip_popup(&popup.node,&desc);expect_box(&popup,0,0,0,0);assert(!inside(&nested.node,30,40));owner_scene.node.x=20;probe_clip_popup(&popup.node,&desc);expect_box(&popup,-15,-15,100,100);
ws.current.fullscreen=&parent;probe_clip_popup(&popup.node,&desc);assert(!wlr_scene_tree_get_clip(&popup,&out));ws.current.fullscreen=NULL;
list.length=0;probe_clip_popup(&popup.node,&desc);assert(!wlr_scene_tree_get_clip(&popup,&out));list.length=1;
wlr_scene_tree_set_clip(&tiling,NULL);probe_clip_popup(&popup.node,&desc);expect_box(&popup,0,0,0,0);
desc.view=NULL;probe_clip_popup(&popup.node,&desc);assert(!wlr_scene_tree_get_clip(&popup,&out));destroy(&tiling);destroy(&popup);destroy(&nested);destroy(&owner_scene);assert(clip_count()==0);
puts("PASS popup: real root clip/current fullscreen ancestor/pending contradiction/standard/inactive/hidden owner/restoration/workspace fullscreen/not tiled/missing clip/no owner; nested clip inheritance (unit only)");
}
int main(int argc,char **argv){if(argc==2&&strcmp(argv[1],"--allocation-unavailable")==0){struct wlr_scene_tree tree,prior;init_node(&tree.node,WLR_SCENE_NODE_TREE,NULL,0,0);init_node(&prior.node,WLR_SCENE_NODE_TREE,NULL,0,0);struct wlr_box b={0,0,10,10};wlr_scene_tree_set_clip(&prior,&b);assert(allocations==1&&clip_count()==1&&wl_list_length(&prior.node.events.destroy.listener_list)==1);allocation_unavailable=true;puts("INJECTION prior sidecar succeeded; next sidecar calloc returns NULL; expecting experimental abort");fflush(stdout);wlr_scene_tree_set_clip(&tree,&b);return 99;}lifecycle();popups();return 0;}

View file

@ -0,0 +1,8 @@
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <wayland-server-core.h>
#include <wlr/util/box.h>
struct wlr_scene_tree;
#include "size-struct.inc"
int main(void){printf("sidecar_bytes=%zu box_offset=%zu listener_offset=%zu pointer_bytes=%zu\n",sizeof(struct scene_tree_clip),offsetof(struct scene_tree_clip,box),offsetof(struct scene_tree_clip,destroy),sizeof(void *));return 0;}

View file

@ -0,0 +1,16 @@
"""Adversarial mutations of the exact extracted bodies, never historical inputs."""
import pathlib,subprocess,shutil,json,resource,argparse
p=argparse.ArgumentParser();p.add_argument('lab',type=pathlib.Path);L=p.parse_args().lab.resolve();N=L/'negative';N.mkdir(exist_ok=True);records=[]
pkg=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-server','pixman-1'],text=True).split()
mutants=[('B','popup.inc','desired && admitted','desired',''),('A','setter.inc','abort();','return;','1'),('B','setter.inc','wlr_scene_node_set_enabled(&tree->node, false);','/* mutant: false without admission mask */','1'),('B','setter.inc','\treturn true;\n}\n\nbool wlr_scene_tree_get_clip','\twlr_scene_node_set_enabled(&tree->node, true);\n\treturn true;\n}\n\nbool wlr_scene_tree_get_clip','1'),('C','create.inc','struct scene_tree_clip *entry = calloc(1, sizeof(*entry));','scene_tree_init(tree, parent);\n\tstruct scene_tree_clip *entry = calloc(1, sizeof(*entry));','2'),('C','setter.inc','entry->active = clip != NULL;','entry->active = false;','')]
for i,(mode,file,old,new,arg) in enumerate(mutants):
d=N/str(i);shutil.copytree(L/mode,d,ignore=shutil.ignore_patterns('A-*','B-*','C-*'),dirs_exist_ok=True)
f=d/file;s=f.read_text();assert old in s;f.write_text(s.replace(old,new,1));args=['cc','-std=c17','-O1','-DMODE_'+mode,'harness-scene.c','options.c',*pkg,'-o',str(d/'mutant')]
c=subprocess.run(args,cwd=d,capture_output=True);assert c.returncode==0,c.stderr
def limit():resource.setrlimit(resource.RLIMIT_CORE,(0,0))
argv=[str(d/'mutant')]+([arg] if arg else []);r=subprocess.run(argv,cwd=d,capture_output=True,preexec_fn=limit)
(d/'stdout').write_bytes(r.stdout);(d/'stderr').write_bytes(r.stderr)
# A must return 99 rather than expected SIGABRT; others must trip assertions.
rejected=r.returncode==99 if mode=='A' else r.returncode==-6
records.append(dict(mode=mode,file=file,mutation=old+' => '+new,argv=argv,returncode=r.returncode,rejected=rejected))
(N/'results.json').write_text(json.dumps(records,indent=2)+'\n');print(json.dumps(records,indent=2));raise SystemExit(not all(r['rejected'] for r in records))

View file

@ -0,0 +1,90 @@
#define main historical_main
#include "lifecycle.c"
#undef main
extern unsigned fail_at,publications;
#ifdef MODE_C
struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *);
#endif
static void oom(unsigned nth){
struct wlr_scene_tree root;init_node(&root.node,WLR_SCENE_NODE_TREE,NULL,0,0);
struct wlr_box b={0,0,10,10},out;
#ifdef MODE_C
unsigned before=publications;fail_at=allocations+nth;
struct wlr_scene_tree *t=wlr_scene_tree_create(&root);
assert(!t && publications==before && clip_count()==0);
assert(wl_list_empty(&root.node.events.destroy.listener_list));
printf("PASS C reservation allocation %u: NULL before publication/listener; no exposed tree\n",nth);
#else
struct wlr_scene_tree prior;init_node(&prior.node,WLR_SCENE_NODE_TREE,NULL,0,0);
if(nth==2)wlr_scene_tree_set_clip(&prior,&b);
fail_at=allocations+1;
#ifdef MODE_B
unsigned old_updates=old_region_updates;
assert(!wlr_scene_tree_set_clip(&root,&b));assert(!root.node.enabled);assert(old_region_updates==old_updates+1);
struct wlr_scene_buffer leaf={.point_accepts_input=input};init_node(&leaf.node,WLR_SCENE_NODE_BUFFER,&root,0,0);
assert(!hit(&leaf.node,1,1) && input_calls==0);
root.node.parent=&prior;assert(!hit(&leaf.node,1,1));root.node.parent=NULL;assert(!hit(&leaf.node,1,1));
assert(!wlr_scene_tree_get_clip(&root,&out));
fail_at=0;assert(wlr_scene_tree_set_clip(&root,&b));assert(!root.node.enabled);
assert(wlr_scene_tree_set_clip(&root,NULL));assert(!root.node.enabled);
destroy(&root);destroy(&prior);assert(clip_count()==0);
printf("PASS B allocation %u: false masks ancestor before hit; recovery/NULL do not enable\n",nth);
#else
puts("EXPECT SIGABRT core_limit=0");fflush(stdout);wlr_scene_tree_set_clip(&root,&b);exit(99);
#endif
#endif
}
#ifdef MODE_C
static void reserved(void){
struct wlr_scene_tree root;init_node(&root.node,WLR_SCENE_NODE_TREE,NULL,0,0);
struct wlr_scene_tree *p=wlr_scene_tree_create(&root),*c=wlr_scene_tree_create(p),*other=wlr_scene_tree_create(&root);assert(p&&c&&other);
struct wlr_scene_buffer leaf={.point_accepts_input=input};init_node(&leaf.node,WLR_SCENE_NODE_BUFFER,c,0,0);
struct wlr_box b={0,0,10,10},out;unsigned n=allocations;allocation_unavailable=true;
wlr_scene_tree_set_clip(p,NULL);assert(!wlr_scene_tree_get_clip(p,&out));
wlr_scene_tree_set_clip(p,&b);assert(hit(&leaf.node,1,1)&&!hit(&leaf.node,11,1));
b=(struct wlr_box){0};wlr_scene_tree_set_clip(c,&b);assert(!hit(&leaf.node,1,1));
wlr_scene_tree_set_clip(c,NULL);assert(hit(&leaf.node,1,1));
// WHY: clips follow current parent pointers, not stale ownership snapshots.
// CONTRACT: this direct fixture reparent exercises exact ancestry walkers only.
// INVARIANT: no claim is made about native reparent damage or scene children.
c->node.parent=other;assert(hit(&leaf.node,11,1));c->node.parent=p;assert(!hit(&leaf.node,11,1));
wlr_scene_tree_set_clip(p,NULL);wlr_scene_tree_set_clip(p,&b);assert(!hit(&leaf.node,1,1));assert(allocations==n);
destroy(p);destroy(c);destroy(other);free(p);free(c);free(other);assert(clip_count()==0);
puts("PASS C NULL/clip/zero/removal/reinstall/inherited hit/reparent/parent-before-child destroy; zero setter allocations");
}
#endif
#ifdef MODE_B
static void admission(void){
struct wlr_scene_tree tree;init_node(&tree.node,WLR_SCENE_NODE_TREE,NULL,0,0);
struct sway_workspace ws={.current={.layout=L_MOSAIC},.mosaic=&ws,.layers={&tree}};
struct sway_workspace *child=&ws;
struct wlr_box box={0,0,10,10};
fail_at=allocations+1;assert(!wlr_scene_tree_set_clip(&tree,&box));assert(!tree.node.enabled);
// Exact statement from the private fading-out branch, plus exact helper body.
#include "fading.inc"
assert(!tree.node.enabled);
fail_at=0;assert(wlr_scene_tree_set_clip(&tree,&box));assert(!tree.node.enabled);
probe_clip_enable_tiling(&ws,false);assert(!tree.node.enabled);
#include "fading.inc"
assert(tree.node.enabled);
assert(wlr_scene_tree_set_clip(&tree,NULL));
#include "fading.inc"
assert(!tree.node.enabled);
ws.current.layout=L_STANDARD;
#include "fading.inc"
assert(tree.node.enabled);
probe_clip_enable_tiling(&ws,false);assert(!tree.node.enabled);
destroy(&tree);assert(clip_count()==0);
puts("PASS B exact fading admission: OOM cannot re-enable; desired false preserved; repaired clip/standard admit only explicit desired true");
}
#endif
int main(int argc,char **argv){if(argc==2){oom((unsigned)atoi(argv[1]));return 0;}
#ifdef MODE_C
reserved();allocation_unavailable=false;popups();
#else
lifecycle();popups();
#ifdef MODE_B
admission();
#endif
#endif
return 0;}

View file

@ -0,0 +1,107 @@
"""Private full-file prototypes followed by exact byte extraction; no build of a compositor."""
import argparse, pathlib, hashlib, json, difflib, shutil
P=pathlib.Path(__file__).resolve().parent
H=pathlib.Path('/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_')
def sha(b): return hashlib.sha256(b).hexdigest()
def replace(s,a,b):
assert a in s,a
return s.replace(a,b)
def prepare(lab):
lab.mkdir(parents=True,exist_ok=True); bindings=[]; sources=[]
original=(H/'scene-core/types/scene/wlr_scene.c').read_text()
txn=(H/'mosaic-fixed/sway/desktop/transaction.c').read_text()
assert sha(original.encode())=='99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d'
assert sha(txn.encode())=='0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd'
for mode in 'ABC':
d=lab/mode;d.mkdir(exist_ok=True);s=original;t=txn
if mode=='B':
s=replace(s,'void wlr_scene_tree_set_clip(', 'bool wlr_scene_tree_set_clip(')
s=replace(s,'if (!clip && !entry) return;', 'if (!clip && !entry) return true;')
s=replace(s,'sizeof(*clip)) == 0) return;', 'sizeof(*clip)) == 0) return true;')
s=replace(s,'\t\t\tabort();', '''\t\t\t// WHY: a mandatory clip must not fail open even if callers ignore false.
\t\t\t// CONTRACT: mask synchronously; caller owns future visibility admission.
\t\t\t// INVARIANT: never automatically re-enable an intentionally hidden tree.
\t\t\twlr_scene_node_set_enabled(&tree->node, false);
\t\t\treturn false;''')
s=replace(s,'scene_node_update(&tree->node, NULL);\n}\n\nbool wlr_scene_tree_get_clip', 'scene_node_update(&tree->node, NULL);\n\treturn true;\n}\n\nbool wlr_scene_tree_get_clip')
import re
t=re.sub(r'(?m)^(\s*)wlr_scene_tree_set_clip\(([^;]+)\);',r'\1if (!wlr_scene_tree_set_clip(\2)) return;',t)
# Admission has no heap state: current Mosaic plus applied clip is the gate.
helper="""// WHY: fading-out also enables tiling without retrying clip installation.
// CONTRACT: upstream decides desired visibility; absent mandatory clip vetoes it.
// INVARIANT: false desired never becomes true; no OOM latch allocation is needed.
static void probe_clip_enable_tiling(struct sway_workspace *ws, bool desired) {
struct wlr_box clip;
bool admitted = ws->current.layout != L_MOSAIC || !ws->mosaic ||
wlr_scene_tree_get_clip(ws->layers.tiling, &clip);
wlr_scene_node_set_enabled(&ws->layers.tiling->node, desired && admitted);
}
"""
# Declaration precedes arrange_output; implementation precedes popup helpers.
t=t.replace('// WHY: int endpoint subtraction',helper+'// WHY: int endpoint subtraction',1)
pos=t.index('static void arrange_output(')
t=t[:pos]+'static void probe_clip_enable_tiling(struct sway_workspace *ws, bool desired);\n\n'+t[pos:]
t=t.replace('wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs);','probe_clip_enable_tiling(child, !fs);')
t=t.replace('wlr_scene_node_set_enabled(&child->layers.tiling->node, true);','probe_clip_enable_tiling(child, true);')
t=t.replace('wlr_scene_node_set_enabled(&child->layers.tiling->node, false);','probe_clip_enable_tiling(child, false);')
t=t.replace('\n\t\t\t\tarrange_workspace_tiling(child,','\n\t\t\t\tprobe_clip_enable_tiling(child, true);\n\t\t\t\tarrange_workspace_tiling(child,',1)
if mode=='C':
s=replace(s,'struct wlr_box box;\n\tstruct wl_listener destroy;', 'struct wlr_box box;\n\tbool active; // Private sidecar only: no installed tree/node ABI change.\n\tstruct wl_listener destroy;')
s=replace(s,'static struct scene_tree_clip *scene_tree_clip_find(', 'static struct scene_tree_clip *scene_tree_clip_storage(')
marker='static void scene_tree_clip_remove('
i=s.index(marker)
s=s[:i]+'''// WHY: reservation must survive clip removal to make later admission allocation-free.
// CONTRACT: inactive reservations are absent to render/hit/get callers.
// INVARIANT: lookup never exposes an uninitialized clip rectangle.
static struct scene_tree_clip *scene_tree_clip_find(struct wlr_scene_tree *tree) {
\tstruct scene_tree_clip *entry = scene_tree_clip_storage(tree);
\treturn entry && entry->active ? entry : NULL;
}
'''+s[i:]
s=replace(s,'\tscene_tree_init(tree, parent);\n\treturn tree;', '''\t// WHY: failure must occur before tree publication to parent scene children.
\t// CONTRACT: both tree and reservation are acquired before scene_tree_init.
\t// INVARIANT: failed reservation frees unpublished tree, no listener attached.
\tstruct scene_tree_clip *entry = calloc(1, sizeof(*entry));
\tif (!entry) { free(tree); return NULL; }
\tscene_tree_init(tree, parent);
\tentry->tree = tree;
\tentry->destroy.notify = scene_tree_clip_destroy;
\twl_signal_add(&tree->node.events.destroy, &entry->destroy);
\tentry->next = scene_tree_clips;
\tscene_tree_clips = entry;
\treturn tree;''')
a=s.index('void wlr_scene_tree_set_clip(');b=s.index('bool wlr_scene_tree_get_clip(',a)
s=s[:a]+'''void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip) {
\t// WHY: no heap allocation may occur when exposing an existing tree.
\t// CONTRACT: trees must come from the private reservation-aware create path.
\t// INVARIANT: NULL deactivates but never releases the reservation.
\tassert(tree && (!clip || (clip->width >= 0 && clip->height >= 0)));
\tstruct scene_tree_clip *entry = scene_tree_clip_storage(tree);
\tassert(entry);
\tif (!clip && !entry->active) return;
\tif (clip && entry->active && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return;
\tentry->active = clip != NULL;
\tif (clip) entry->box = *clip;
\tscene_node_update(&tree->node, NULL);
}
'''+s[b:]
header=(H/'scene-core/include/scenefx/types/wlr_scene.h').read_text()
private_header=header.replace('void wlr_scene_tree_set_clip(', 'bool wlr_scene_tree_set_clip(') if mode=='B' else header
for name,parent,body in [('scene.c',original,s),('transaction.c',txn,t),('scene.h',header,private_header)]:
(d/name).write_text(body)
patch=''.join(difflib.unified_diff(parent.splitlines(True),body.splitlines(True),fromfile='a/'+name,tofile='b/'+name,n=0))
(lab/(mode+'-'+name+'.patch')).write_text(patch)
sources.append(dict(mode=mode,file=name,parent_sha256=sha(parent.encode()),prototype_sha256=sha(body.encode()),patch_sha256=sha(patch.encode())))
ranges=[('scene.c','struct scene_tree_clip {','static struct scene_tree_clip *scene_tree_clips;','size-struct.inc'),('scene.c','void wlr_scene_node_set_enabled(','void wlr_scene_node_set_position(','enabled.inc'),('scene.c','struct scene_tree_clip {','struct wlr_scene_rect *wlr_scene_rect_from_node','sidecar.inc'),('scene.c','static bool scene_clip_translate(','static bool scene_node_update_iterator(','clip.inc'),('scene.c',('bool' if mode=='B' else 'void')+' wlr_scene_tree_set_clip(','struct wlr_scene_rect *wlr_scene_rect_create(','setter.inc'),('scene.c','struct node_at_data {','struct wlr_scene_node *wlr_scene_node_at(','hit.inc'),('scene.c','struct wlr_scene_tree *wlr_scene_tree_create(','typedef bool (*scene_node_box_iterator_func_t)','create.inc'),('transaction.c','static void probe_clip_enable_tiling(struct sway_workspace *ws, bool desired) {' if mode=='B' else 'static bool probe_clip_box_intersection(','void arrange_popups(','popup.inc')]
if mode=='B': ranges.append(('transaction.c','\n\t\t\tprobe_clip_enable_tiling(child, true);','\n\t\t\twlr_scene_node_set_enabled(&child->layers.fullscreen->node, false);','fading.inc'))
for source,start,end,out in ranges:
data=(d/source).read_bytes();a=data.index(start.encode());b=data.index(end.encode(),a);piece=data[a:b];(d/out).write_bytes(piece)
bindings.append(dict(mode=mode,source=str(d/source),source_sha256=sha(data),start_byte=a,end_byte_exclusive=b,start_line=data[:a].count(b'\n')+1,slice_sha256=sha(piece),output=out))
(lab/'bindings.json').write_text(json.dumps(bindings,indent=2)+'\n');(lab/'sources.json').write_text(json.dumps(sources,indent=2)+'\n')
return lab
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('lab',type=pathlib.Path);prepare(p.parse_args().lab)

View file

@ -0,0 +1,33 @@
import pathlib,subprocess,json,os,resource,shutil,argparse,hashlib,re
from prepare import prepare
P=pathlib.Path(__file__).resolve().parent
p=argparse.ArgumentParser();p.add_argument('lab',type=pathlib.Path);L=prepare(p.parse_args().lab.resolve());records=[]
def run(name,args,cwd,expected=0):
def limit():resource.setrlimit(resource.RLIMIT_CORE,(0,0))
env=os.environ.copy();env.update(ASAN_OPTIONS='detect_leaks=1:abort_on_error=1',UBSAN_OPTIONS='halt_on_error=1:print_stacktrace=1')
r=subprocess.run(args,cwd=cwd,env=env,capture_output=True,preexec_fn=limit)
(L/(name+'.stdout')).write_bytes(r.stdout);(L/(name+'.stderr')).write_bytes(r.stderr)
records.append(dict(name=name,argv=args,cwd=str(cwd),returncode=r.returncode,expected=expected,core_limit=0,pass_expected=r.returncode==expected))
(L/'commands.json').write_text(json.dumps(records,indent=2)+'\n')
return r.returncode==expected
pkg=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-server','pixman-1'],text=True).split()
for mode in 'ABC':
d=L/mode
for f in ('support.h','scene.c','options.c','lifecycle.c'):
# Full prototype sources retain distinct filenames; harness scene is separate.
shutil.copyfile(P/f,d/('harness-scene.c' if f=='scene.c' else f))
if mode=='C':
f=d/'lifecycle.c';text=f.read_text();text=re.sub(r'(init_node\(&([a-z_]+)\.node,WLR_SCENE_NODE_TREE,[^;]+;)',r'\1reserve_fixture(&\2);',text);f.write_text(text)
for variant,flags in [('normal',[]),('ubsan',['-fsanitize=undefined','-fno-sanitize-recover=all']),('asan',['-fsanitize=address'])]:
name=mode+'-'+variant;binary=str(d/name)
ok=run(name+'-compile',['cc','-std=c17','-g','-O1','-Wall','-Wextra','-Wno-unused-function','-Wno-unused-parameter','-fno-omit-frame-pointer','-DMODE_'+mode,*flags,'harness-scene.c','options.c',*pkg,'-o',binary],d)
if ok:
run(name+'-defined',[binary],d)
for nth in (1,2):run(name+'-oom'+str(nth),[binary,str(nth)],d,-6 if mode=='A' else 0)
for mode in ('A','C'):
d=L/mode;shutil.copyfile(P/'memory.c',d/'memory.c')
flags=subprocess.check_output(['pkg-config','--cflags','wlroots-0.20','wayland-server'],text=True).split()
binary=str(d/'memory')
if run(mode+'-memory-compile',['cc','-std=c17',*flags,'memory.c','-o',binary],d):run(mode+'-memory',[binary],d)
(L/'source-fixtures.json').write_text(json.dumps({f.name:hashlib.sha256(f.read_bytes()).hexdigest() for f in P.iterdir() if f.is_file()},indent=2)+'\n')
print(json.dumps(records,indent=2));raise SystemExit(not all(r['pass_expected'] for r in records))

View file

@ -0,0 +1,27 @@
#include "support.h"
unsigned updates, allocations, old_region_updates;
bool allocation_unavailable;
unsigned fail_at, publications;
static void *injected_calloc(size_t n,size_t z){ allocations++;if(allocation_unavailable || allocations==fail_at)return NULL;return calloc(n,z); }
#include "sidecar.inc"
#include "enabled.inc"
#define calloc injected_calloc
#include "setter.inc"
#ifdef MODE_C
// WHY: exact create body is compiled without publishing a native scene.
// CONTRACT: count the point at which the native implementation would attach.
// INVARIANT: failed allocations never reach this callback.
static void scene_tree_init(struct wlr_scene_tree *t,struct wlr_scene_tree *p){init_node(&t->node,WLR_SCENE_NODE_TREE,p,0,0);publications++;}
#include "create.inc"
// Fixture only: stack nodes in inherited popup tests need reserved storage.
// Native create is tested independently, including both prepublication failures.
void reserve_fixture(struct wlr_scene_tree *t){struct scene_tree_clip *e=calloc(1,sizeof(*e));assert(e);e->tree=t;e->destroy.notify=scene_tree_clip_destroy;wl_signal_add(&t->node.events.destroy,&e->destroy);e->next=scene_tree_clips;scene_tree_clips=e;}
#endif
#undef calloc
#include "clip.inc"
#include "hit.inc"
unsigned clip_count(void){unsigned n=0;for(struct scene_tree_clip *p=scene_tree_clips;p;p=p->next)n++;return n;}
bool inside(struct wlr_scene_node *n,double x,double y){return scene_node_point_inside_clip(n,x,y);}
void intersect(struct wlr_scene_node *n,pixman_region32_t *r){scene_node_intersect_clip(n,r);}
bool hit(struct wlr_scene_node *n,double x,double y){struct node_at_data d={.lx=x,.ly=y};int nx,ny;if(!wlr_scene_node_coords(n,&nx,&ny))return false;return scene_node_at_iterator(n,nx,ny,&d);}
bool translate(const struct wlr_box *b,int x,int y,struct wlr_box *g){return scene_clip_translate(b,x,y,g);}

View file

@ -0,0 +1,65 @@
[
{
"mode": "A",
"file": "scene.c",
"parent_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"prototype_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"mode": "A",
"file": "transaction.c",
"parent_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"prototype_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"mode": "A",
"file": "scene.h",
"parent_sha256": "bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d",
"prototype_sha256": "bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d",
"patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"mode": "B",
"file": "scene.c",
"parent_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"prototype_sha256": "21f0163b4cc46048cd2a1f1d95fe0095a3f51733b1c577ca5a7f51bc6601066f",
"patch_sha256": "b0e19476ac7836f1a31ab400db1594fcac4696cb3c5be41806d485faa905d308"
},
{
"mode": "B",
"file": "transaction.c",
"parent_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"prototype_sha256": "ac4ffbb848e09853898e3da2e2448e35e0ec58b65cf9d6c06dc52e15c25e1b8c",
"patch_sha256": "2e9da2fc14ef7d47860a79b319ffcd1ce240fc9f18962e14d744b220456f7857"
},
{
"mode": "B",
"file": "scene.h",
"parent_sha256": "bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d",
"prototype_sha256": "2730ac77f14b9c5d989aca5dda393003f567b883dbd0e92f06234128743f8663",
"patch_sha256": "d3c364109959dc58e488ecca211a59c73a21db5f254dffab2bdfc5f4d7e01550"
},
{
"mode": "C",
"file": "scene.c",
"parent_sha256": "99992429f9a1d2754042989d89904fb6d705f963d74f4456d737fc9da3e0927d",
"prototype_sha256": "b2eebcab19e012e8c1042aade2dcd35407877ac8162509e1672e12b71461791a",
"patch_sha256": "eed3daa95c18d18116a9e4c551b3b308ba2e65ea7232fbc2261d31909a9f8811"
},
{
"mode": "C",
"file": "transaction.c",
"parent_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"prototype_sha256": "0b1d31bb8458dceaff34cc2a2987044b3096e6ebc96e68b522c0ac3540bc8ecd",
"patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"mode": "C",
"file": "scene.h",
"parent_sha256": "bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d",
"prototype_sha256": "bc102f289c905995d142786412e6085c60b8d44d37184d67fa35fdb301c33e2d",
"patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]

View file

@ -0,0 +1,62 @@
#ifndef SUPPORT_H
#define SUPPORT_H
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-server-core.h>
#include <pixman.h>
// WHY: isolate static core functions without a compositor or GPU.
// CONTRACT: these reduced structs contain only fields read by exact slices;
// they are fixtures, never an ABI/layout assertion about wlroots or Sway.
// INVARIANT: all nodes, containers and workspace pointers are borrowed and
// live until their real Wayland destroy listeners have been emitted.
struct wlr_box { int x,y,width,height; };
enum { WLR_SCENE_NODE_TREE, WLR_SCENE_NODE_BUFFER, WLR_SCENE_NODE_RECT, WLR_SCENE_NODE_SHADOW, WLR_SCENE_NODE_OPTIMIZED_BLUR, WLR_SCENE_NODE_BLUR };
struct wlr_scene_tree;
struct wlr_scene_node { int type,x,y; struct wlr_scene_tree *parent; struct { struct wl_signal destroy; } events; bool coords_valid,enabled; };
struct wlr_scene_tree { struct wlr_scene_node node; };
struct wlr_scene_buffer { struct wlr_scene_node node; bool (*point_accepts_input)(struct wlr_scene_buffer *, double *, double *); };
struct wlr_scene_rect { struct wlr_scene_node node; bool accepts_input; struct { struct wlr_box area; } clipped_region; };
// WHY: coordinate lookup requires hierarchy but not compositor ownership.
// CONTRACT: use small fixture translations; failed lookup is injectable.
// INVARIANT: no pending geometry is substituted for applied node coordinates.
static bool wlr_scene_node_coords(struct wlr_scene_node *n,int *x,int *y) { *x=*y=0; for (;n;n=n->parent?&n->parent->node:NULL) {if(!n->coords_valid||!n->enabled)return false; *x+=n->x; *y+=n->y;} return true; }
static bool wlr_box_contains_point(const struct wlr_box *b,double x,double y) {return x>=b->x && y>=b->y && x<(double)b->x+b->width && y<(double)b->y+b->height;}
static bool wlr_box_empty(const struct wlr_box *b) {return b->width<=0||b->height<=0;}
static struct wlr_scene_buffer *wlr_scene_buffer_from_node(struct wlr_scene_node *n) {return (struct wlr_scene_buffer *)n;}
static struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *n) {return (struct wlr_scene_rect *)n;}
// WHY: mutation callbacks/loggers otherwise pull in the full scene engine.
// CONTRACT: only callback invocation is counted; no render/damage claim.
// INVARIANT: stubs neither install nor remove clips/listeners.
#define WLR_INFO 1
#define WLR_ERROR 2
#define SWAY_INFO 1
#define wlr_log(level, ...) ((void)(level))
#define sway_log(level, ...) ((void)(level))
extern unsigned updates, allocations, old_region_updates;
extern bool allocation_unavailable;
static void scene_node_update(struct wlr_scene_node *n,void *unused) {(void)n;if(unused && pixman_region32_not_empty(unused))old_region_updates++; updates++;}
void wlr_scene_node_set_enabled(struct wlr_scene_node *,bool);
static void scene_node_visibility(struct wlr_scene_node *n,pixman_region32_t *r){(void)n;pixman_region32_union_rect(r,r,0,0,10,10);}
#ifdef MODE_B
bool wlr_scene_tree_set_clip(struct wlr_scene_tree *,const struct wlr_box *);
#else
void wlr_scene_tree_set_clip(struct wlr_scene_tree *,const struct wlr_box *);
#endif
bool wlr_scene_tree_get_clip(struct wlr_scene_tree *,struct wlr_box *);
struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *);
#ifdef MODE_C
void reserve_fixture(struct wlr_scene_tree *);
#endif
unsigned clip_count(void);
bool inside(struct wlr_scene_node *,double,double);
void intersect(struct wlr_scene_node *,pixman_region32_t *);
bool hit(struct wlr_scene_node *,double,double);
bool translate(const struct wlr_box *,int,int,struct wlr_box *);
static void init_node(struct wlr_scene_node *n,int type,struct wlr_scene_tree *parent,int x,int y) {*n=(struct wlr_scene_node){.type=type,.parent=parent,.x=x,.y=y,.coords_valid=true,.enabled=true};wl_signal_init(&n->events.destroy);}
#endif

View file

@ -0,0 +1,39 @@
{
"status": "EXPERIMENTAL_SOURCE_ONLY",
"baseline": "70954e26f62fb3df25995e31b0c615762ff85a60",
"ticket": "SWAYFX_MOSAIC_STEP20C_CORE_RECOVERY_OOM_AND_DCC_QUALIFICATION_BATCH_V1",
"lab": "/home/fy59/swayfx-mosaic-core-recovery-qmhckba5",
"historical_hashes": {
"closeout.json": "5166d6442aae8dfa2a5e939eb5e83141f83c55e7be89f93670eb381e9956df0a",
"closeout-manifest.json": "4fd7ccedf00a59754db447d015fdca35fbcdda5b7de3820c3655b6e433765ce8",
"publication-receipt.json": "45396f706af9374547bda0d76edcb704298bca10a4ea300ae69b4fdb076de655"
},
"binary_hashes": {
"/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/stage1/usr/bin/swayfx-mosaic": "29baf31aa3eeed259825c87d0799fa5d3dc83710afb967154604eb1c6657f965",
"/home/fy59/swayfx-mosaic-optimized-edge-a55yjb8n/pristine-build/sway/sway": "8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494",
"/home/fy59/swayfx-mosaic-minimal-core-2l7riqt_/gpu-field/mosaic-build/sway/swayfx-mosaic": "1905a20fa72ebcc7ec47d1584a4653654280288096a20005199eb75673ac0383",
"/usr/lib/libwlroots-0.20.so": "7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837",
"/home/fy59/swayfx-mosaic-optimized-edge-a55yjb8n/scene0-build/libscenefx-0.5.so": "7a07c1f239043f7910039670006a2f6e9bb15608bf2a840576476c5f7b1378b3",
"/usr/lib/libscenefx-0.5.so": "63586ffbe268c6830b5782d5327e8ca3624dbd9b9c9b0e93e591e94ca28a6e62"
},
"private_receipts": {
"audit/initial.json": "b8f9aa72e52ebc34fa6866818916bf948e808e1258eb8f379a09b3fc941e8aba",
"audit/historical-native.json": "e4961706ea8056ada4bf6da3515b21c44f1523141ae77dd8a31f3431e58171df",
"audit/elf-stage.json": "0c682311adc8f24ad2bbe6cbb418d523849fa9758fd932e22378139bc91414d3",
"audit/new-run-collection.json": "0b5b48b2466be4e825b188bb0b362e3e104e487965639e7d9d51d255be608c35",
"graphics/pre-run-manifest.json": "ed0c2be50d067f273e884c338b607a47d066d3a290432df99a513c620346fbc7",
"graphics/historical-replay.json": "8f214f53e51812eeaf3fce4ce5e087905374c8d41890970b3fa92d9606ee5bfc",
"oom/final-fixture-manifest.json": "4a6dfda4f61d9c9f5a13588fe7ed7ac6ec6d9720cf2167a82e5f7c39f52d0c35",
"oom/commands.json": "1c867b9a4d9a013a78ebda7ad3e5cb24b606480071afde9bbc94d26bf6eed0b5",
"oom/negative/results.json": "c67f1d5902224f7d4a2b299d710222e439439389383523d0dca2a4b9217f5384",
"gpu-field/evidence.json": "219dc7ecc0dd29e5e93b735b98e14b606983573ea9e09100dea88843e6e9e613",
"gpu-field/replay-result.json": "72304c9c935fe14c6d8a8e658a18876a4b73afc15406c7f0624aa4ceaf7f323c",
"gpu-field/parent-delta-whitespace-repair.json": "77a6134c96764e3bb8075220fb5acf6a25fb34931b842a29229cf34711026d3b"
},
"source_manifest": "Private pre-run manifest covers executed graphics bytes; final public source manifest separately captured at closeout; no old budget reopened.",
"public_source_normalization": {
"receipt": "tooling/probes/step20c-core-recovery/evidence/public-source-eof-normalization.json",
"sha256": "b9612ce4d8e71593c217ecb569dca7aaa47c531e720fc3f13faebc038c397123",
"scope": "Three public Python files only; EOF suffix reversible, AST and bytecode equal; executed LAB sources/manifests remain unchanged."
}
}