@@ -181,9 +181,89 @@ cluster_catalog_prepare_xid_authority(const ControlFileData *cf,
181181 errhint (
182182 "Start the seed with cluster.enabled=off, stop it cleanly, then start joiners." )));
183183
184- if (cluster_catalog_backup_label_present ())
185- elog (LOG , "cluster shared_catalog: skipped XID prehistory adopt on backup_label boot" );
186- else {
184+ if (cluster_catalog_backup_label_present ()) {
185+ uint64 own_next = 0 ;
186+
187+ /*
188+ * PGRAC (GCS-race round-2 RC-E supply-side fix): a backup_label boot
189+ * used to skip the adopt unconditionally, assuming a post-seed
190+ * backup whose pg_xact already carries the native bits. A PRE-seed
191+ * base backup (the RACvsRAC S3 bring-up) breaks that assumption:
192+ * the clone's pg_xact predates every native seed xid, recovery
193+ * replays only the clone's own pre-seed WAL window, and the sealed
194+ * blob is the ONLY supply of the native outcomes -- skipping left
195+ * the joiner unable to prove any native xid (155k fail-closed storm
196+ * on xid 815).
197+ *
198+ * Adoption is lineage-safe exactly here: backup_label still present
199+ * means this node has NEVER completed a boot since it was cloned
200+ * (the first recovery renames the label), so its entire local
201+ * history is a subset of the seed lineage by construction -- a
202+ * clone that ran standalone would have consumed the label and takes
203+ * the anchor + prefix-check path below.
204+ *
205+ * Within the same lineage the adopt needs NO horizon comparison --
206+ * overwriting [blob start, native_hw) is idempotent same-lineage
207+ * truth on any clone that already carries (possibly torn) native
208+ * bits, and the post-recovery verify (StartupXLOG tail) re-proves
209+ * the whole range before the resolver may route native xids locally.
210+ *
211+ * One gate stands (round-2 review F3): the clone's OWN xid epoch. A
212+ * clone taken after an xid epoch rollover reuses the pg_xact
213+ * positions below the native high-water for cluster-era xids;
214+ * adopting native bits over them would corrupt live outcomes. The
215+ * clone's own pre-adopt nextFullXid comes from the local control
216+ * file when it is still per-node, or from the pre-migration epoch
217+ * witness under cluster.controlfile_shared_authority (the local
218+ * global/pg_control is then a symlink to the SHARED authority whose
219+ * checkpoint fields belong to the last permitted writer, not to
220+ * this clone -- reading it through the symlink would compare the
221+ * seed's own high-water against itself; the B3 trap).
222+ */
223+ if (!cluster_controlfile_shared_authority )
224+ own_next = U64FromFullTransactionId (cf -> checkPointCopy .nextXid );
225+ else if (!cluster_xid_epoch_witness_read (DataDir , & own_next ))
226+ ereport (FATAL ,
227+ (errcode (ERRCODE_CLUSTER_XID_AUTHORITY_UNAVAILABLE ),
228+ errmsg ("local xid epoch witness is unavailable for the backup_label "
229+ "prehistory adopt" ),
230+ errdetail ("Under cluster.controlfile_shared_authority the local control "
231+ "file is the shared symlink, and no pre-migration witness "
232+ "\"%s\" passes validation." ,
233+ CLUSTER_XID_EPOCH_WITNESS_REL_PATH ),
234+ errhint ("Re-provision this node from the seed lineage." )));
235+
236+ if (own_next >= auth .native_hw_full ) {
237+ /*
238+ * Round-3 review P0-2: the pre-adopt horizon is NOT donor-local
239+ * truth. pg_basebackup follows the shared-authority symlink
240+ * (basebackup.c Dc6), so the clone's control file -- and the
241+ * witness derived from it -- carries the LAST PERMITTED WRITER's
242+ * checkpointed nextXid, which can lag the actual donor (another
243+ * node may have allocated far past it, even across an epoch
244+ * rollover). The only value this horizon can PROVE is "the
245+ * authority was last checkpointed strictly before the seal", and
246+ * that is exactly own_next < native_hw_full: no cluster-era xid
247+ * (>= stripe floor >= hw) can exist anywhere under that reading,
248+ * so the clone's lineage is a seed-lineage subset and the adopt
249+ * is idempotent truth. Anything else -- post-seal backups
250+ * (native bits already carried), epoch rollovers (pg_xact
251+ * positions reused) -- skips: the coverage verify + repair path
252+ * (or 53R97) covers those without ever overwriting live
253+ * outcomes. Same predicate as the anchor path's adopt gate.
254+ */
255+ elog (LOG ,
256+ "cluster shared_catalog: skipped XID prehistory adopt on backup_label boot; "
257+ "pre-adopt nextXid %llu is not strictly below the native high-water %llu" ,
258+ (unsigned long long )own_next , (unsigned long long )auth .native_hw_full );
259+ } else {
260+ cluster_xid_prehistory_adopt (DataDir , auth .native_hw_full );
261+ elog (LOG ,
262+ "cluster shared_catalog: adopted XID prehistory through native high-water %llu "
263+ "on backup_label boot" ,
264+ (unsigned long long )auth .native_hw_full );
265+ }
266+ } else {
187267 ClusterRecoveryAnchor ra ;
188268 uint64 own_next ;
189269
@@ -215,8 +295,18 @@ cluster_catalog_prepare_xid_authority(const ControlFileData *cf,
215295 * and truncated away are no alibi for the surviving range, and a
216296 * missing local page inside the comparable range fails closed
217297 * (UNAVAILABLE) instead of passing as a shorter clone.
298+ *
299+ * Epoch gate (round-2 review F3): past an xid epoch rollover the
300+ * node's pg_xact positions below the native high-water belong to
301+ * cluster-era xids, so the byte compare against the native blob is
302+ * meaningless -- it would FATAL a legitimate wrap-era node. The
303+ * 32-bit oldestXid raw value cannot express the epoch, so gate on
304+ * the anchor's full nextXid instead; post-wrap the prehistory
305+ * machinery is dead anyway (the coverage latch refuses to engage
306+ * and the resolver's widen judge proves nothing native).
218307 */
219- if ((uint64 )ra .checkPointCopy .oldestXid <= auth .native_hw_full ) {
308+ if (own_next <= (uint64 )PG_UINT32_MAX
309+ && (uint64 )ra .checkPointCopy .oldestXid <= auth .native_hw_full ) {
220310 ClusterXidPrefixVerdict pv ;
221311
222312 pv = cluster_xid_prehistory_prefix_check (DataDir , auth .native_hw_full ,
0 commit comments