From f839aee5a39617231ecf643eebb2513b3df3feeb Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:37:43 -0800 Subject: [PATCH 01/19] G5A real POST: stable provider operating identity across wider source windows after schedule retime --- server/lib/disruption/fidsCensus_v3.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/lib/disruption/fidsCensus_v3.ts b/server/lib/disruption/fidsCensus_v3.ts index 4800231e7..b50cfdbaf 100644 --- a/server/lib/disruption/fidsCensus_v3.ts +++ b/server/lib/disruption/fidsCensus_v3.ts @@ -447,9 +447,11 @@ function normalizeFidsRecord(input: { const stable=createHash("sha256").update(JSON.stringify({ protocol:"G5A_FIRST_NATIVE_SCHEDULE_REVISION", providerId:sourceId,carrier:parsed.carrier,flightNumber:parsed.number, - origin:depIcao,destination:arrIcao, - frozenServiceStartUtc:params.serviceWindowStartUtc.toISOString(), - frozenServiceEndUtc:params.serviceWindowEndUtc.toISOString() + // A postflight FIDS query may cover a wider source window because + // the operating flight was retimed out of the original hour. Source + // window is query metadata, NOT physical identity. Positive provider + // flight ID plus carrier/route is the stable cross-window link. + origin:depIcao,destination:arrIcao })).digest("hex"); canonical="leg:g5a:"+stable; analyticIdentityId=canonical; From a562935e9f17ba9e3135490eaaa0f134fd437881 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:38:01 -0800 Subject: [PATCH 02/19] Regression: G5A stable provider identity persists when late retime exits original 1-hour FIDS frame --- ...e4_gate5_g5a_native_fids_identity_v39.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/phase4_gate5_g5a_native_fids_identity_v39.test.ts b/tests/phase4_gate5_g5a_native_fids_identity_v39.test.ts index 933dbbae9..6af8567d2 100644 --- a/tests/phase4_gate5_g5a_native_fids_identity_v39.test.ts +++ b/tests/phase4_gate5_g5a_native_fids_identity_v39.test.ts @@ -49,6 +49,22 @@ describe("G5A provider FIDS native identity, prospectively enabled only",()=>{ expect(first.query.rawPayload).toHaveProperty("g5aProtocol", "G5A_FIRST_PROVIDER_REPORTED_SCHEDULE_REVISION"); }); + it("a real late retime into a wider postflight source window retains one physical leg",async()=>{ + const original=await one(base(),leg("2026-11-05T02:20:00.000Z", + "G5A-OPERATING-LEG-121")); + const widened=base(); + widened.fromLocal="2026-11-05T09:00"; + widened.toLocal="2026-11-05T13:00"; + widened.serviceWindowStartUtc=new Date("2026-11-05T01:00:00.000Z"); + widened.serviceWindowEndUtc=new Date("2026-11-05T05:00:00.000Z"); + const after=await one(widened,leg("2026-11-05T03:40:00.000Z", + "G5A-OPERATING-LEG-121")); + expect(after.row.canonicalFlightInstanceId) + .toBe(original.row.canonicalFlightInstanceId); + expect(after.row.analyticIdentityId).toBe(original.row.analyticIdentityId); + expect(after.row.depScheduledUtc!.toISOString()) + .not.toBe(original.row.depScheduledUtc!.toISOString()); + }); it("different provider flight ID and destination must never silently merge",async()=>{ const a=await one(base(),leg("2026-11-05T02:20:00.000Z","P-1")); const b=await one(base(),leg("2026-11-05T02:20:00.000Z","P-2")); From 07426f4f13407e9c1abd2bdce2b7d87c40b1ff42 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:55:06 -0800 Subject: [PATCH 03/19] Stamp exact reserved G5A POST ledger UUID into reconstructed FIDS original source hash; retain stable retime identity --- server/lib/disruption/fidsCensus_v3.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/lib/disruption/fidsCensus_v3.ts b/server/lib/disruption/fidsCensus_v3.ts index b50cfdbaf..c58261e46 100644 --- a/server/lib/disruption/fidsCensus_v3.ts +++ b/server/lib/disruption/fidsCensus_v3.ts @@ -37,6 +37,8 @@ export interface FidsCensusParams { openapiSha256: string; /** Only prospective G5A: stable provider operating identity under retimes. */ g5aObservedScheduleProtocol?: boolean; + /** Separately ledger-reserved G5A POST source; never a PRE identifier. */ + g5aPostReservationId?:string; } /** @@ -585,6 +587,11 @@ export async function fetchFidsPopulation( bodyBase64: original.base64, }; } + if(params.g5aPostReservationId!==undefined&& + (params.g5aObservedScheduleProtocol!==true|| + params.fidsProtocolVersion!=="G5A-POST-20261103-v1"|| + !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(params.g5aPostReservationId))) + throw Error("G5A_POST_RESERVATION_SCOPE_INVALID_HOLD"); const retrievalUtc = (deps.now ?? (() => new Date()))(); const rawPayload = addCanonicalFidsEnvelopeHashV39({ airport: params.airportIcao, @@ -593,6 +600,10 @@ export async function fetchFidsPopulation( ianaTimezone: params.ianaTimezone, // Hash-bound application request intent, NOT original provider HTTP bytes. fidsRequestContract:frame, + ...(params.g5aPostReservationId?{ + g5aPostReservationId:params.g5aPostReservationId, + g5aPostPurpose:"PROVIDER_REPORTED_SCHEDULE_REVISION_NOT_ACTUAL_DELAY" + }:{}), g5aProtocol:params.g5aObservedScheduleProtocol===true? "G5A_FIRST_PROVIDER_REPORTED_SCHEDULE_REVISION":null, payloadRepresentation:"RECONSTRUCTED_RESPONSE_JSON_NOT_VERBATIM_HTTP_BYTES", From 4fd48d19c6ded7fea6742cd9dd57bd4eadfcbed7 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:56:06 -0800 Subject: [PATCH 04/19] G5A POST: one exact baseline REST attempt ledger SELECT/INSERT role grant, no new schema/no credentials/no provider calls --- ..._gate5_g5a_post_ledger_exact_grant_once.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts diff --git a/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts b/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts new file mode 100644 index 000000000..8868b58fb --- /dev/null +++ b/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts @@ -0,0 +1,109 @@ +/** + * Optional one-time *metadata permission only* for the 3 G5A POST FIDS GETs. + * No provider HTTP, role creation, flight Content, new schema, or secret read. + * The source role already exists from merged #371, approved by the owner. + */ +import pg from "pg"; +import {execFileSync} from "node:child_process"; +const ROLE="p4_g5_g5a_owner_20261103"; +const TABLE="clean.adb_rest_attempt_ledger"; +const INTENT="EXACT_G5A_POST_IMMUTABLE_LEDGER_INSERT_SELECT_NO_OTHER_PRIVILEGES"; +const SHA=/^[a-f0-9]{40}$/; +function hold(msg:string):never{throw Error("P4_G5_G5A_POST_GRANT_"+msg+"_HOLD")} +export async function main(){ + const e=process.env; + if(e.P4_G5_G5A_POST_GRANT_INTENT!==INTENT|| + !e.DATABASE_URL||!e.V39_DATABASE_RUNTIME_URL|| + e.DATABASE_URL===e.V39_DATABASE_RUNTIME_URL|| + e.ADB_AUTO_COLLECT!=="0"||e.V39_PHASE6_PROCESS_ARMED!=="0"|| + e.GITHUB_ACTIONS==="true") + hold("OPERATOR_INTENT_OR_DB_URL"); + const commit=execFileSync("git",["rev-parse","HEAD"], + {encoding:"utf8",timeout:4000}).trim(); + if(!SHA.test(commit))hold("SOURCE_PIN"); + const admin=new pg.Client({connectionString:e.DATABASE_URL, + connectionTimeoutMillis:8500,query_timeout:12000, + application_name:"g5a_post_ledger_exact_grant_once"}); + const runtime=new pg.Client({connectionString:e.V39_DATABASE_RUNTIME_URL, + connectionTimeoutMillis:8500,query_timeout:12000, + application_name:"g5a_post_ledger_runtime_restricted_ro"}); + await admin.connect();await runtime.connect(); + try{ + const [a,w]=await Promise.all([ + admin.query(`SELECT current_database() AS db,current_user AS role, + current_setting('server_version_num')::int>=160000 AS pg16, + (SELECT rolsuper OR rolcreaterole FROM pg_roles WHERE rolname=current_user) AS can_grant`), + runtime.query(`SELECT current_database() AS db,current_user AS role, + has_table_privilege(current_user,$1,'DELETE') AS can_delete_source`,[TABLE]) + ]); + const ar=a.rows[0],wr=w.rows[0]; + if(a.rows.length!==1||w.rows.length!==1|| + ar.pg16!==true||ar.can_grant!==true|| + ar.db!==wr.db||ar.role===wr.role) + hold("ROLE_SEPARATION_OR_PG16"); + const target=await admin.query(`SELECT COUNT(*)::int n FROM pg_roles + WHERE rolname=$1 AND NOT rolsuper AND NOT rolcreatedb + AND NOT rolcreaterole AND NOT rolreplication AND NOT rolbypassrls`,[ROLE]); + if(Number(target.rows[0]?.n)!==1)hold("SOURCE_OWNER_MISSING_OR_PRIVILEGED"); + const rel=await admin.query("SELECT to_regclass($1::text) AS found",[TABLE]); + if(rel.rows.length!==1||!rel.rows[0].found)hold("BASELINE_LEDGER_NOT_PRESENT"); + const old=await admin.query(`SELECT + has_table_privilege($1,$2,'SELECT') can_select, + has_table_privilege($1,$2,'INSERT') can_insert, + has_table_privilege($1,$2,'UPDATE') can_update, + has_table_privilege($1,$2,'DELETE') can_delete, + has_table_privilege($1,$2,'TRUNCATE') can_truncate, + has_table_privilege($1,$2,'REFERENCES') can_ref, + has_table_privilege($1,$2,'TRIGGER') can_trigger`,[ROLE,TABLE]); + if(old.rows.length!==1)hold("ORIGINAL_TABLE_ACL_UNVERIFIABLE"); + const prior=old.rows[0]; + if(prior.can_update||prior.can_delete||prior.can_truncate||prior.can_ref||prior.can_trigger) + hold("UNEXPECTED_EXISTING_DANGEROUS_GRANTS"); + let changed=false; + if(!(prior.can_select&&prior.can_insert)){ + await admin.query("BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ WRITE"); + let tx=true; + try{ + await admin.query("SET LOCAL statement_timeout='10000ms'"); + await admin.query("SET LOCAL lock_timeout='1500ms'"); + const lock=await admin.query("SELECT pg_try_advisory_xact_lock(76,97) AS ok"); + if(lock.rows[0]?.ok!==true)hold("CONCURRENT_GRANT"); + await admin.query("GRANT SELECT,INSERT ON TABLE "+TABLE+" TO "+ROLE); + await admin.query("COMMIT");tx=false;changed=true; + }finally{if(tx)await admin.query("ROLLBACK").catch(()=>undefined)} + } + const post=await admin.query(`SELECT + has_table_privilege($1,$2,'SELECT') s, + has_table_privilege($1,$2,'INSERT') i, + has_table_privilege($1,$2,'UPDATE') u, + has_table_privilege($1,$2,'DELETE') d, + has_table_privilege($1,$2,'TRUNCATE') t, + has_table_privilege($1,$2,'REFERENCES') r, + has_table_privilege($1,$2,'TRIGGER') g`,[ROLE,TABLE]); + const x=post.rows[0]; + if(post.rows.length!==1||!x.s||!x.i||x.u||x.d||x.t||x.r||x.g) + hold("POSTCOMMIT_TABLE_ACL"); + const pub=await admin.query(`SELECT + has_table_privilege('public',$1,'INSERT') AS public_insert`,[TABLE]); + if(pub.rows[0]?.public_insert===true)hold("PUBLIC_INSERT_ALREADY_ALLOWED"); + console.log(JSON.stringify({ + status:changed? + "G5A_POST_BASELINE_LEDGER_EXACT_SELECT_INSERT_GRANTED_ONCE": + "G5A_POST_BASELINE_LEDGER_GRANT_ALREADY_PRESENT_NO_REINSTALL", + sourceRoleNonAdmin:true,sourceRoleNoDelete:true, + sourceRoleNoSchemaDdlGranted:true, + publicInsertNotEnabled:true,providerCalls:0, + databaseContentWrites:0,phase5FreezeAuthorized:false + })); + }finally{ + await runtime.end().catch(()=>undefined); + await admin.end().catch(()=>undefined); + } +} +if(process.argv[1]?.endsWith("v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts")) + void main().catch(e=>{ + const msg=e instanceof Error?e.message:""; + console.error(/^P4_G5_G5A_POST_GRANT_[A-Z0-9_]+_HOLD$/.test(msg)? + msg:"P4_G5_G5A_POST_GRANT_UNKNOWN_NO_SECRETS_HOLD"); + process.exitCode=2; + }); From 5213199c7e6f9403cc055c7415f6dc7f92985fb9 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:56:28 -0800 Subject: [PATCH 05/19] Check PostgreSQL PUBLIC ledger privilege through ACL grantee zero, not nonexistent public role --- ...39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts b/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts index 8868b58fb..d284c2cb7 100644 --- a/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts +++ b/scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts @@ -83,8 +83,14 @@ export async function main(){ const x=post.rows[0]; if(post.rows.length!==1||!x.s||!x.i||x.u||x.d||x.t||x.r||x.g) hold("POSTCOMMIT_TABLE_ACL"); - const pub=await admin.query(`SELECT - has_table_privilege('public',$1,'INSERT') AS public_insert`,[TABLE]); + const pub=await admin.query(`SELECT EXISTS( + SELECT 1 FROM pg_class c + JOIN pg_namespace n ON n.oid=c.relnamespace + CROSS JOIN LATERAL aclexplode( + COALESCE(c.relacl,acldefault('r',c.relowner))) p + WHERE n.nspname='clean' AND c.relname='adb_rest_attempt_ledger' + AND p.grantee=0 AND p.privilege_type='INSERT' + ) AS public_insert`); if(pub.rows[0]?.public_insert===true)hold("PUBLIC_INSERT_ALREADY_ALLOWED"); console.log(JSON.stringify({ status:changed? From da62e4764e606e1ee371fb16e1b83564e0918ea8 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 05:59:26 -0800 Subject: [PATCH 06/19] G5A real prospective WSSS POST: 3 irrevocable 2-unit source reservations, original SHA, stable retime identity and W=T+2h measured label counts --- ...a_prospective_post_schedule_observation.ts | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts diff --git a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts new file mode 100644 index 000000000..1a736575c --- /dev/null +++ b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts @@ -0,0 +1,350 @@ +/** + * G5A one-time, Github-only, prospective WSSS POST source observation. + * Three immutable budget-ledger reservations (max 6 provider REST units). + * + * First original T is the per-flight provider-reported scheduled departure. + * Prediction label: schedule revision vs actual as-of PRE by W=T+2h; + * NOT actual wheels-off. No downstream models are trained here. + * + * 3 fixed POST GETs at 03:40, 04:10, 04:40Z 2026-11-05, within a fixed + * WSSS 01:00-05:00Z local source range (09:00-13:00 Singapore time). + * A missing/lost identity/schedule is UNKNOWN, not a negative/actual. + * + * This module does not run for PR, push, workflow_dispatch or in Replit. + * It requires verified real GitHub schedule metadata and preexisting role + * with SELECT/INSERT on clean.adb_rest_attempt_ledger. + */ +import {createHash} from "node:crypto"; +import {execFileSync} from "node:child_process"; +import {readFileSync} from "node:fs"; +import pg from "pg"; +import {fetchFidsPopulation,type FidsCensusParams,type FidsQueryObservation, + type PopulationRow,persistFidsObservation} + from "../server/lib/disruption/fidsCensus_v3"; +import {fetchFidsAirport,getBalanceEvidenceStrict} + from "../server/lib/disruption/aerodataboxLimiter_v3"; +const REPO="HKcode22/ReplitTranvr"; +const WF="Phase4 Gate5 G5A WSSS Real Prospective POST"; +const PATH=".github/workflows/phase4-gate5-g5a-wsss-real-post.yml"; +const CRON="10 2 5 11 *"; +const MANIFEST="data-collection/phase4/GATE5_G5A_WSSS_20261105_FROZEN_SOURCE_GRID.json"; +const FROM="2026-11-05T01:00:00.000Z",TO="2026-11-05T05:00:00.000Z"; +const POSTS=["2026-11-05T03:40:00.000Z","2026-11-05T04:10:00.000Z", + "2026-11-05T04:40:00.000Z"] as const; +const FINAL="2026-11-05T05:01:00.000Z"; +const END="2026-11-05T06:00:00.000Z"; +const SHA=/^[a-f0-9]{64}$/; +const GH=/^[a-f0-9]{40}$/; +const ms=(s:string)=>Date.parse(s); +const now=()=>new Date().toISOString(); +const hash=(s:string)=>createHash("sha256").update(s).digest("hex"); +const sleep=(m:number)=>new Promise(r=>setTimeout(r,m)); +function hold(code:string):never{throw Error("P4_G5_G5A_POST_"+code+"_HOLD")} +function safe(e:unknown){ + const s=e instanceof Error?e.message:""; + return /^P4_G5_G5A_POST_[A-Z0-9_]+_HOLD$/.test(s)?s: + "P4_G5_G5A_POST_INTERNAL_FAILURE_NO_SOURCE_PRINTED_HOLD"; +} +function object(x:unknown):Record{ + if(!x||typeof x!=="object"||Array.isArray(x))hold("BAD_PROOF_JSON"); + return x as Record; +} +export function postReservationUuidV39(manifestSha:string,slot:number){ + if(!SHA.test(manifestSha)||!Number.isInteger(slot)||slot<0||slot>2) + hold("RESERVATION_DIGEST"); + const b=Buffer.from(hash("G5A-POST|"+manifestSha+"|"+slot).slice(0,32),"hex"); + b[6]=(b[6]&15)|64;b[8]=(b[8]&63)|128; + const h=b.toString("hex"); + return h.slice(0,8)+"-"+h.slice(8,12)+"-"+h.slice(12,16)+"-"+ + h.slice(16,20)+"-"+h.slice(20,32); +} +function manifest(){ + const bytes=readFileSync(MANIFEST,"utf8"); + let doc:unknown;try{doc=JSON.parse(bytes)}catch{hold("FROZEN_JSON")} + const d=object(doc),g=object(d.grid); + if(bytes!==JSON.stringify(doc,null,2)+"\n"|| + d.cohortId!=="WSSS_G5A_20261105_0200Z"|| + g.airportIcao!=="WSSS"|| + g.serviceWindowStartUtc!=="2026-11-05T02:00:00.000Z"|| + g.serviceWindowEndUtc!=="2026-11-05T03:00:00.000Z"|| + g.projectRestApiUnitsBudget!==24|| + g.maxSourceQueries!==9||g.maxAttemptsPerQuery!==1) + hold("FROZEN_GRID_CHANGED"); + return {sha:hash(bytes),cohortId:String(d.cohortId)}; +} +export function assertG5APostRunnerContextV39(e:NodeJS.ProcessEnv,commit:string,at:number){ + if(!GH.test(commit)||!Number.isFinite(at)|| + e.GITHUB_ACTIONS!=="true"||e.GITHUB_REPOSITORY!==REPO|| + e.GITHUB_REF!=="refs/heads/main"||e.GITHUB_SHA!==commit|| + e.GITHUB_EVENT_NAME!=="schedule"||e.GITHUB_RUN_ATTEMPT!=="1"|| + e.GITHUB_JOB!=="post-source"||e.GITHUB_WORKFLOW!==WF|| + e.GITHUB_WORKFLOW_REF!==REPO+"/"+PATH+"@refs/heads/main"|| + e.G5A_POST_SCHEDULE!==CRON|| + e.G5A_POST_PAID_INTENT!=="G5A_NOV05_POST_THREE_2_UNIT_REQUESTS_MAX"|| + !/^\d{5,25}$/.test(e.GITHUB_RUN_ID??"")|| + !e.GITHUB_TOKEN||e.GITHUB_TOKEN.length<10|| + !e.AERODATABOX_API_KEY||e.AERODATABOX_API_KEY.length<10|| + !e.P4_G5_SOURCE_OWNER_DATABASE_URL|| + e.V39_DATABASE_RUNTIME_URL!==e.P4_G5_SOURCE_OWNER_DATABASE_URL|| + !!e.DATABASE_URL||!!e.GATE05_AUTOMATION_DATABASE_URL|| + e.ADB_AUTO_COLLECT!=="0"||e.V39_PHASE6_PROCESS_ARMED!=="0"|| + at=ms(END)) + hold("GITHUB_CONTEXT_OR_SECRETS"); +} +async function independentlyVerifyGithubRun(e:NodeJS.ProcessEnv,commit:string){ + const get=async(tail:string)=>{ + const res=await fetch("https://api.github.com/repos/"+REPO+tail,{ + method:"GET",redirect:"error",signal:AbortSignal.timeout(8500), + headers:{Authorization:"Bearer "+e.GITHUB_TOKEN, + "X-GitHub-Api-Version":"2022-11-28", + Accept:"application/vnd.github+json", + "User-Agent":"gate5-g5a-post-source"} + }); + if(!res.ok)hold("LIVE_GITHUB_RUN_NOT_VERIFIED"); + return object(await res.json()); + }; + const id=e.GITHUB_RUN_ID!; + const [run,jobs]=await Promise.all([ + get("/actions/runs/"+id),get("/actions/runs/"+id+"/jobs?per_page=100") + ]); + if(!Array.isArray(jobs.jobs))hold("GH_JOB_COUNT"); + const found=jobs.jobs.map(object).filter(x=>x.name==="post-source"); + if(found.length!==1)hold("GH_POST_JOB_MISSING"); + const job=found[0],start=ms(String(job.started_at)),current=Date.now(); + if(run.id!==Number(id)||run.head_sha!==commit|| + run.event!=="schedule"||run.name!==WF||run.path!==PATH|| + run.head_branch!=="main"||run.run_attempt!==1|| + run.status!=="in_progress"||run.conclusion!==null|| + object(run.repository).full_name!==REPO|| + job.run_id!==Number(id)||job.status!=="in_progress"|| + job.conclusion!==null|| + !Number.isFinite(start)||start>current|| + start>ms("2026-11-05T03:25:00.000Z")|| + current>ms("2026-11-05T03:25:00.000Z")|| + current-start>90*60000) + hold("GH_POST_NOT_ACTUALLY_PREARMED"); + return {githubRunId:id,realStartedAt: new Date(start).toISOString()}; +} +async function waitUntil(at:number){ + while(Date.now()24) + hold("PROJECT_24_UNIT_BUDGET"); + const prior=await db.query( + "SELECT reservation_id FROM clean.adb_rest_attempt_ledger "+ + "WHERE cycle_id=$1 AND category=$2",[cycle,category]); + if(prior.rows.length!==slot||prior.rows.some(x=>x.reservation_id===id)) + hold("POST_RESERVATION_REPLAY_OR_GAP"); + await db.query( + "UPDATE clean.phase4_g5_fids_cohort_budget_v39 "+ + "SET reserved_units=reserved_units+2 WHERE manifest_sha256=$1", + [manifestSha]); + const result=await db.query( + "INSERT INTO clean.adb_rest_attempt_ledger "+ + "(reservation_id,cycle_id,category,units,attempt_number,reserved_at) "+ + "VALUES ($1,$2,$3,2,1,clock_timestamp()) RETURNING reservation_id", + [id,cycle,category]); + if(result.rows.length!==1||result.rows[0].reservation_id!==id) + hold("POST_LEDGER_NOT_DURABLE"); + await db.query("COMMIT");tx=false; + return id; + }catch(e){ + if(tx)await db.query("ROLLBACK").catch(()=>undefined); + throw e; + } +} +export const G5A_POST_AGGREGATE_AUDIT_SQL_V39=` +SELECT pre.horizon, + count(*)::int AS expected_pre, + count(baseline.dep_scheduled_utc)::int AS baseline_sourced, + count(post.dep_scheduled_utc)::int AS post_observed, + count(*) FILTER(WHERE baseline.dep_scheduled_utc IS NOT NULL + AND post.dep_scheduled_utc IS NOT NULL + AND abs(extract(epoch from + (post.dep_scheduled_utc-baseline.dep_scheduled_utc))/60)>=15)::int AS revision_ge15, + count(*) FILTER(WHERE baseline.dep_scheduled_utc IS NOT NULL + AND post.dep_scheduled_utc IS NOT NULL + AND abs(extract(epoch from + (post.dep_scheduled_utc-baseline.dep_scheduled_utc))/60)<15)::int AS revision_lt15 +FROM ( + SELECT flight_instance_id,horizon,prediction_cutoff_utc, + selected_t_milestone_utc,population_query_id + FROM clean.flight_snapshots + WHERE frame_hash=$1 AND prediction_state='PRE_DEPARTURE' + AND selected_t_milestone_utc IS NOT NULL + AND flight_instance_id LIKE 'leg:g5a:%' +) pre +LEFT JOIN LATERAL( + SELECT p.dep_scheduled_utc + FROM clean.flight_population p + WHERE p.analytic_identity_id=pre.flight_instance_id + AND p.population_query_id::text=pre.population_query_id + AND p.fids_protocol_version='G5A-20261103-v1' + AND p.available_at<=pre.prediction_cutoff_utc + AND p.dep_scheduled_utc IS NOT NULL + ORDER BY p.available_at DESC,p.id DESC LIMIT 1 +) baseline ON TRUE +LEFT JOIN LATERAL( + SELECT p.dep_scheduled_utc + FROM clean.flight_population p + JOIN clean.fids_query_response q + ON q.population_query_id=p.population_query_id + WHERE p.analytic_identity_id=pre.flight_instance_id + AND p.fids_protocol_version='G5A-POST-20261103-v1' + AND p.batch_id=ANY($2::text[]) + AND q.raw_payload->>'g5aPostReservationId'=p.batch_id + AND p.available_at>pre.prediction_cutoff_utc + AND p.available_at<=pre.selected_t_milestone_utc+INTERVAL '2 hours' + AND p.dep_scheduled_utc IS NOT NULL + AND p.population_role='requested_airport_primary' + AND p.scope_classification='confirmed_core' + AND p.codeshare_resolution_status='confirmed_operating_leg' + ORDER BY p.available_at DESC,p.id DESC LIMIT 1 +) post ON TRUE +GROUP BY pre.horizon ORDER BY pre.horizon`; +async function readAgg(db:pg.Client,manifestSha:string,ids:string[]){ + let tx=false; + try{ + await db.query("BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY");tx=true; + const r=await db.query(G5A_POST_AGGREGATE_AUDIT_SQL_V39,[manifestSha,ids]); + await db.query("ROLLBACK");tx=false; + return r.rows.map(x=>{ + const expected=Number(x.expected_pre),base=Number(x.baseline_sourced); + const observed=Number(x.post_observed); + const positive=Number(x.revision_ge15),negative=Number(x.revision_lt15); + if(![expected,base,observed,positive,negative].every(n=> + Number.isSafeInteger(n)&&n>=0)||positive+negative>observed|| + observed>expected||base>expected) + hold("AGGREGATE_NUMERATOR_DENOMINATOR_INVALID"); + return { + horizon:String(x.horizon),persistedPRE:expected,asofBaseline:base, + observedPOST:observed, + observedRevisionGE15Minutes:positive, + observedRevisionLT15Minutes:negative, + unknownNoQualifiedPOSTOrPREBaseline:expected-positive-negative, + actualWheelsOffClaim:false + }; + }); + }finally{if(tx)await db.query("ROLLBACK").catch(()=>undefined)} +} +export async function main(){ + const e=process.env,head=execFileSync("git",["rev-parse","HEAD"], + {encoding:"utf8",timeout:4000}).trim(); + assertG5APostRunnerContextV39(e,head,Date.now()); + const {sha:manifestSha,cohortId}=manifest(); + const proof=await independentlyVerifyGithubRun(e,head); + const db=new pg.Client({connectionString:e.P4_G5_SOURCE_OWNER_DATABASE_URL, + connectionTimeoutMillis:9000,query_timeout:15000, + application_name:"g5a_real_post_source_reservation"}); + await db.connect(); + const ids=POSTS.map((_,i)=>postReservationUuidV39(manifestSha,i)); + let charged=0,originals=0,unknown=0,normalized=0; + try{ + for(const [i,planned] of POSTS.entries()){ + await waitUntil(ms(planned)); + if(Date.now()>ms(planned)+10*60000){unknown++;continue} + let quota:{apiUnitsRemaining:number;requestsRemaining:number; + observedAtUtc:string}; + try{quota=(await getBalanceEvidenceStrict()).quota} + catch{unknown++;continue} + if(Date.now()-ms(quota.observedAtUtc)>30000|| + quota.apiUnitsRemaining<2||quota.requestsRemaining<2){ + unknown++;continue; + } + const id=await reserveOne(db,manifestSha,cohortId,i); + charged++; + let callback=0; + const q:FidsCensusParams={ + airportIcao:"WSSS",fromLocal:"2026-11-05T09:00", + toLocal:"2026-11-05T13:00",ianaTimezone:"Asia/Singapore", + serviceWindowStartUtc:new Date(FROM), + serviceWindowEndUtc:new Date(TO),cutoffUtc:new Date(planned), + withCancelled:true,withCodeshared:true,withCargo:false,withPrivate:false, + direction:"Departure",restCategory:"outcome", + providerApiVersion:"RapidAPI-v1.15.4", + fidsProtocolVersion:"G5A-POST-20261103-v1", + openapiSha256:hash(readFileSync( + "data-collection/phase4/2026-11-03_G5A_PROSPECTIVE_PROVIDER_OBSERVABLE_PROTOCOL_AMENDMENT.md","utf8")), + g5aObservedScheduleProtocol:true,g5aPostReservationId:id,batchId:id + }; + try{ + const res=await fetchFidsPopulation(q,{ + fetchAirport:async(icao,from,to,opts)=>fetchFidsAirport(icao,from,to,{ + ...opts,budgetOwner:{async reserve(_category,units,attempt){ + if(callback!==0||units!==2||attempt!==1) + throw Error("G5A_POST_REPEAT_PAID_GET_FORBIDDEN"); + callback++;return id; + }} + }), + persist:async(query:FidsQueryObservation,rows:PopulationRow[])=>{ + const raw=query.rawPayload as Record; + if(raw?.g5aPostReservationId!==id|| + raw?.originalDecodedEntityBodyPreserved!==true) + hold("POST_SOURCE_ORIGINAL_NOT_AUTHENTICATED"); + return persistFidsObservation(query,rows); + } + }); + if(!res||callback!==1){unknown++;continue} + originals++;normalized+=res.persistedPopulationRows; + console.log(JSON.stringify({ + status:"G5A_POST_ORIGINAL_SOURCE_PERSISTED", + slot:i,reservationCommittedBeforeGET:true, + populationRows:res.persistedPopulationRows, + sourceQueryIdPrinted:false,sourceContentPrinted:false, + providerUnitsReservedCumulative:charged*2, + actualDepartureDelayClaimed:false + })); + }catch(ex){ + unknown++; + console.error(JSON.stringify({ + status:"G5A_POST_RESERVED_SLOT_OUTCOME_UNKNOWN", + slot:i,error:safe(ex),noRepeatedPaidGET:true + })); + } + } + await waitUntil(ms(FINAL)); + const agg=await readAgg(db,manifestSha,ids); + console.log(JSON.stringify({ + status:"G5A_POST_FROZEN_WATERMARK_AGGREGATE_OBSERVED_NOT_GATE5_VERDICT", + mainSha:head,githubRunId:proof.githubRunId, + verifiedJobStartedAtUtc:proof.realStartedAt, + frozenManifestSha256:manifestSha, + postSlotsPlanned:3,paidReservations:charged, + providerRestUnitsReserved:charged*2, + originalPostReceipts:originals,normalizedPostRows:normalized, + unknownSourceSlots:unknown,watermarkRule:"T_NATIVE_SCHEDULE_PLUS_2H", + outcomeKind:"PROVIDER_REPORTED_SCHEDULE_REVISION_NOT_ACTUAL_DEPARTURE", + labelCountsByHorizon:agg, + missingPostObservationsAreNotNegative:true, + independentReadOnlyAuditStillRequired:true, + strictGate5Passed:false,phase5FreezeAuthorized:false, + phase6Authorized:false + })); + }finally{await db.end().catch(()=>undefined)} +} +if(process.argv[1]?.endsWith("v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts")) + void main().catch(e=>{ + console.error(JSON.stringify({status:"G5A_GITHUB_POST_OWNER_HOLD", + reason:safe(e),providerContentPrinted:false, + strictGate5Passed:false,phase6Authorized:false})); + process.exitCode=2; + }); From 39b0e73eb2d5a04ee19a462384a81d4a7b418a29 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:00:04 -0800 Subject: [PATCH 07/19] Allow measured missed earlier POST slots while refusing repeated/foreign source reservations; enforce one budget UPDATE --- ...a_prospective_post_schedule_observation.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts index 1a736575c..9379522e5 100644 --- a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts +++ b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts @@ -152,12 +152,24 @@ async function reserveOne(db:pg.Client,manifestSha:string,cohortId:string,slot:n const prior=await db.query( "SELECT reservation_id FROM clean.adb_rest_attempt_ledger "+ "WHERE cycle_id=$1 AND category=$2",[cycle,category]); - if(prior.rows.length!==slot||prior.rows.some(x=>x.reservation_id===id)) - hold("POST_RESERVATION_REPLAY_OR_GAP"); - await db.query( + // Earlier POST slots may be genuinely UNOBSERVED (missed physical + // deadline or missing balance). Accept only previously reserved *earlier* + // fixed slots, never a duplicate, future or foreign reservation. + const validPrior=POSTS.slice(0,slot).map((_,n)=> + postReservationUuidV39(manifestSha,n)); + if(prior.rows.length>slot||prior.rows.some(x=> + !validPrior.includes(String(x.reservation_id)))|| + new Set(prior.rows.map(x=>String(x.reservation_id))).size!==prior.rows.length) + hold("POST_RESERVATION_REPLAY_OR_FOREIGN_SLOT"); + const changed=await db.query( "UPDATE clean.phase4_g5_fids_cohort_budget_v39 "+ - "SET reserved_units=reserved_units+2 WHERE manifest_sha256=$1", + "SET reserved_units=reserved_units+2 "+ + "WHERE manifest_sha256=$1 AND reserved_units+2<=project_unit_ceiling "+ + "RETURNING reserved_units", [manifestSha]); + if(changed.rows.length!==1|| + !Number.isSafeInteger(Number(changed.rows[0].reserved_units))) + hold("POST_BUDGET_RACE_OR_UPDATE_DRIFT"); const result=await db.query( "INSERT INTO clean.adb_rest_attempt_ledger "+ "(reservation_id,cycle_id,category,units,attempt_number,reserved_at) "+ From 66b3febc5e79340e7b58623a38797a5fe748c1c8 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:00:56 -0800 Subject: [PATCH 08/19] Expose genuine G5A POST PG16 reservation boundary for disposable ledger and budget regression --- ..._phase4_gate5_g5a_prospective_post_schedule_observation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts index 9379522e5..def2d34d6 100644 --- a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts +++ b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts @@ -128,7 +128,7 @@ async function independentlyVerifyGithubRun(e:NodeJS.ProcessEnv,commit:string){ async function waitUntil(at:number){ while(Date.now() Date: Tue, 3 Nov 2026 06:01:33 -0800 Subject: [PATCH 09/19] =?UTF-8?q?Schedule=20real=202026=20WSSS=203=C3=972-?= =?UTF-8?q?unit=20GitHub=20POST=20FIDS=20source=20with=20frozen=20T+2h=20a?= =?UTF-8?q?s-of=20label=20aggregates,=20no=20PR=20paid=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../phase4-gate5-g5a-wsss-real-post.yml | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/phase4-gate5-g5a-wsss-real-post.yml diff --git a/.github/workflows/phase4-gate5-g5a-wsss-real-post.yml b/.github/workflows/phase4-gate5-g5a-wsss-real-post.yml new file mode 100644 index 000000000..0f13b7d64 --- /dev/null +++ b/.github/workflows/phase4-gate5-g5a-wsss-real-post.yml @@ -0,0 +1,83 @@ +name: Phase4 Gate5 G5A WSSS Real Prospective POST +on: + pull_request: + branches: [main] + paths: + - ".github/workflows/phase4-gate5-g5a-wsss-real-post.yml" + - "scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts" + - "scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts" + - "server/lib/disruption/fidsCensus_v3.ts" + - "tests/phase4_gate5_g5a_post_source_v39.test.ts" + push: + branches: [main] + paths: + - ".github/workflows/phase4-gate5-g5a-wsss-real-post.yml" + - "scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts" + - "scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts" + - "server/lib/disruption/fidsCensus_v3.ts" + - "tests/phase4_gate5_g5a_post_source_v39.test.ts" + schedule: + # Actual UTC date must be 2026-11-05; code refuses replay in future years + - cron: "10 2 5 11 *" +permissions: + contents: read + actions: read +concurrency: + group: gate5-g5a-wsss-20261105-post-real + cancel-in-progress: false +jobs: + offline-post: + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} + runs-on: ubuntu-latest + timeout-minutes: 9 + env: + AERODATABOX_API_KEY: "" + P4_G5_SOURCE_OWNER_DATABASE_URL: "" + V39_DATABASE_RUNTIME_URL: "" + DATABASE_URL: "" + GATE05_AUTOMATION_DATABASE_URL: "" + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + - run: npm ci --include=dev --no-audit --no-fund + - name: Frozen G5A POST and source reservation guard tests, no provider + run: | + set -Eeuo pipefail + npx vitest run tests/phase4_gate5_g5a_post_source_v39.test.ts + echo 'PROVIDER_CALLS=0 DATABASE_CONTENT_WRITES=0 PAID_DISPATCH_DISABLED_ON_PR_PUSH' + post-source: + name: post-source + if: ${{ github.event_name == 'schedule' && github.event.schedule == '10 2 5 11 *' }} + runs-on: ubuntu-latest + timeout-minutes: 240 + environment: phase2g-paid + env: + ADB_AUTO_COLLECT: "0" + V39_PHASE6_PROCESS_ARMED: "0" + G5A_POST_SCHEDULE: "10 2 5 11 *" + G5A_POST_PAID_INTENT: G5A_NOV05_POST_THREE_2_UNIT_REQUESTS_MAX + AERODATABOX_API_KEY: ${{ secrets.AERODATABOX_API_KEY }} + P4_G5_SOURCE_OWNER_DATABASE_URL: ${{ secrets.P4_G5_SOURCE_OWNER_DATABASE_URL }} + V39_DATABASE_RUNTIME_URL: ${{ secrets.P4_G5_SOURCE_OWNER_DATABASE_URL }} + GITHUB_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + - run: npm ci --include=dev --no-audit --no-fund + - name: Actual GitHub scheduled 3× bounded POST originals and T+2h label counts + run: | + set -Eeuo pipefail + set +x + test -z "${DATABASE_URL:-}${GATE05_AUTOMATION_DATABASE_URL:-}${RAPIDAPI_KEY:-}" + test "$(git rev-parse HEAD)" = "${GITHUB_SHA}" + node --import tsx scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts From 6257efaaa626b30b91d7658deb393dc5a35ef632 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:02:29 -0800 Subject: [PATCH 10/19] Test G5A POST stable retime identity across 4h source frame, immutable reservation IDs, GH-only guard and W as-of label SQL --- .../phase4_gate5_g5a_post_source_v39.test.ts | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/phase4_gate5_g5a_post_source_v39.test.ts diff --git a/tests/phase4_gate5_g5a_post_source_v39.test.ts b/tests/phase4_gate5_g5a_post_source_v39.test.ts new file mode 100644 index 000000000..9163839dc --- /dev/null +++ b/tests/phase4_gate5_g5a_post_source_v39.test.ts @@ -0,0 +1,132 @@ +import {describe,it,expect} from "vitest"; +import {fetchFidsPopulation,type FidsCensusParams, + type PopulationRow,type FidsQueryObservation} + from "../server/lib/disruption/fidsCensus_v3"; +import { + postReservationUuidV39,assertG5APostRunnerContextV39, + G5A_POST_AGGREGATE_AUDIT_SQL_V39 +} from "../scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation"; + +const sha="a".repeat(64); +const git="b".repeat(40); +const id=postReservationUuidV39(sha,0); +const base=():FidsCensusParams=>({ + airportIcao:"WSSS",fromLocal:"2026-11-05T10:00", + toLocal:"2026-11-05T11:00",ianaTimezone:"Asia/Singapore", + serviceWindowStartUtc:new Date("2026-11-05T02:00:00.000Z"), + serviceWindowEndUtc:new Date("2026-11-05T03:00:00.000Z"), + cutoffUtc:new Date("2026-11-04T02:00:00.000Z"), + withCancelled:true,withCodeshared:true,withCargo:false,withPrivate:false, + direction:"Departure",restCategory:"fids_base", + providerApiVersion:"RapidAPI-v1.15.4",fidsProtocolVersion:"G5A-20261103-v1", + openapiSha256:sha,g5aObservedScheduleProtocol:true +}); +const flight=(time:string)=>({ + id:"G5A-OPERATING-9381",number:"SQ 115",codeshareStatus:"IsOperator", + departure:{scheduledTime:{utc:time}}, + arrival:{airport:{icao:"YSSY"},scheduledTime:{utc:"2026-11-05T08:00:00.000Z"}} +}); +async function readOne(p:FidsCensusParams,utc:string){ + let rows:PopulationRow[]=[];let query:FidsQueryObservation|null=null; + const result=await fetchFidsPopulation(p,{ + fetchAirport:async()=>({departures:[flight(utc)],arrivals:[]}), + persist:async(q,rr)=>{query=q;rows=rr;return rr.length}, + now:()=>new Date("2026-11-04T01:30:00.000Z") + }); + expect(result?.persistedPopulationRows).toBe(1); + return {result:result!,query:query!,row:rows[0]}; +} +const env=()=>({ + GITHUB_ACTIONS:"true",GITHUB_REPOSITORY:"HKcode22/ReplitTranvr", + GITHUB_REF:"refs/heads/main",GITHUB_SHA:git, + GITHUB_EVENT_NAME:"schedule",GITHUB_RUN_ATTEMPT:"1", + GITHUB_RUN_ID:"41600012345",GITHUB_JOB:"post-source", + GITHUB_WORKFLOW:"Phase4 Gate5 G5A WSSS Real Prospective POST", + GITHUB_WORKFLOW_REF:"HKcode22/ReplitTranvr/.github/workflows/phase4-gate5-g5a-wsss-real-post.yml@refs/heads/main", + G5A_POST_SCHEDULE:"10 2 5 11 *", + G5A_POST_PAID_INTENT:"G5A_NOV05_POST_THREE_2_UNIT_REQUESTS_MAX", + GITHUB_TOKEN:"test-not-real-github-token", + AERODATABOX_API_KEY:"test-not-real-provider-key", + P4_G5_SOURCE_OWNER_DATABASE_URL:"postgresql://writer:fake@localhost/test", + V39_DATABASE_RUNTIME_URL:"postgresql://writer:fake@localhost/test", + ADB_AUTO_COLLECT:"0",V39_PHASE6_PROCESS_ARMED:"0" +}); +describe("G5A prospective POST provider source, time and budget admission",()=>{ + it("makes deterministic, distinct UUID v4 slots from exact manifest",()=>{ + expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/); + expect(postReservationUuidV39(sha,0)).toBe(id); + expect(new Set([0,1,2].map(i=>postReservationUuidV39(sha,i))).size).toBe(3); + expect(()=>postReservationUuidV39("fake",0)).toThrow("P4_G5_G5A_POST_RESERVATION_DIGEST_HOLD"); + expect(()=>postReservationUuidV39(sha,3)).toThrow("P4_G5_G5A_POST_RESERVATION_DIGEST_HOLD"); + }); + it("retimed physical flight has same positive identity across PRE/POST query windows",async()=>{ + const before=await readOne(base(),"2026-11-05T02:20:00.000Z"); + const later=base(); + later.serviceWindowStartUtc=new Date("2026-11-05T01:00:00.000Z"); + later.serviceWindowEndUtc=new Date("2026-11-05T05:00:00.000Z"); + later.fromLocal="2026-11-05T09:00"; + later.toLocal="2026-11-05T13:00"; + later.fidsProtocolVersion="G5A-POST-20261103-v1"; + later.g5aPostReservationId=id; + later.batchId=id; + const post=await readOne(later,"2026-11-05T02:40:00.000Z"); + expect(before.row.analyticIdentityId).toBe(post.row.analyticIdentityId); + expect(before.row.canonicalFlightInstanceId).toBe(post.row.canonicalFlightInstanceId); + expect(before.row.analyticIdentityId).toMatch(/^leg:g5a:[a-f0-9]{64}$/); + expect(post.row.batchId).toBe(id); + const envelope=post.query.rawPayload as Record; + expect(envelope.g5aPostReservationId).toBe(id); + expect(envelope.g5aPostPurpose) + .toBe("PROVIDER_REPORTED_SCHEDULE_REVISION_NOT_ACTUAL_DELAY"); + expect(post.query.responseHash).toMatch(/^[a-f0-9]{64}$/); + expect(JSON.stringify(before.query.rawPayload)).not.toContain(id); + }); + it("does not permit treating an arbitrary source or PRE observation as POST",async()=>{ + const p=base();p.g5aPostReservationId=id; + await expect(readOne(p,"2026-11-05T02:20:00.000Z")) + .rejects.toThrow("G5A_POST_RESERVATION_SCOPE_INVALID_HOLD"); + p.fidsProtocolVersion="G5A-POST-20261103-v1"; + p.g5aObservedScheduleProtocol=false; + await expect(readOne(p,"2026-11-05T02:20:00.000Z")) + .rejects.toThrow("G5A_POST_RESERVATION_SCOPE_INVALID_HOLD"); + }); + it("refuses PR/push/fork/retry, unknown owner role, date replay",()=>{ + const at=Date.parse("2026-11-05T02:12:00.000Z"); + expect(()=>assertG5APostRunnerContextV39(env(),git,at)).not.toThrow(); + for(const edit of [ + {GITHUB_EVENT_NAME:"workflow_dispatch"}, + {GITHUB_EVENT_NAME:"push"}, + {GITHUB_REPOSITORY:"someone/fork"}, + {GITHUB_RUN_ATTEMPT:"2"}, + {GITHUB_SHA:"c".repeat(40)}, + {GITHUB_JOB:"offline-post"}, + {G5A_POST_SCHEDULE:"bad"}, + {G5A_POST_PAID_INTENT:"UNLIMITED"}, + {GITHUB_TOKEN:""}, + {AERODATABOX_API_KEY:""}, + {V39_DATABASE_RUNTIME_URL:"postgresql://admin:fake@localhost/test"}, + {DATABASE_URL:"postgresql://admin:fake@localhost/test"}, + {GATE05_AUTOMATION_DATABASE_URL:"audit-secret-should-not-be-here"} + ]){ + expect(()=>assertG5APostRunnerContextV39({...env(),...edit},git,at)) + .toThrow("P4_G5_G5A_POST_GITHUB_CONTEXT_OR_SECRETS_HOLD"); + } + expect(()=>assertG5APostRunnerContextV39(env(),git, + Date.parse("2027-11-05T02:12:00.000Z"))) + .toThrow("P4_G5_G5A_POST_GITHUB_CONTEXT_OR_SECRETS_HOLD"); + }); + it("calculates observed only with source before W=T+2h and true PRE baseline",()=>{ + const q=G5A_POST_AGGREGATE_AUDIT_SQL_V39; + for(const must of [ + "frame_hash=$1","p.population_query_id::text=pre.population_query_id", + "p.available_at<=pre.prediction_cutoff_utc", + "p.fids_protocol_version='G5A-POST-20261103-v1'", + "q.raw_payload->>'g5aPostReservationId'=p.batch_id", + "p.batch_id=ANY($2::text[])", + "p.available_at<=pre.selected_t_milestone_utc+INTERVAL '2 hours'", + "p.available_at>pre.prediction_cutoff_utc", + "GROUP BY pre.horizon",">=15","<15" + ])expect(q).toContain(must); + expect(q).not.toContain("actual_wheels_off"); + }); +}); From f0aef274d1542832d8ecd745c5ff6c4cd8c56afd Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:03:11 -0800 Subject: [PATCH 11/19] PG16 test of exact three POST ledger reservations after source spend, 24-unit cap, missed-slot accounting and least-privilege denial --- .../phase4_gate5_g5a_post_ledger_pg16.test.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/phase4_gate5_g5a_post_ledger_pg16.test.ts diff --git a/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts new file mode 100644 index 000000000..b89f4345a --- /dev/null +++ b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts @@ -0,0 +1,64 @@ +import {describe,it,expect} from "vitest"; +import pg from "pg"; +import {postReservationUuidV39,reserveG5APostSlotDbV39} + from "../scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation"; + +const suite=process.env.G5A_POST_TEST_PG16==="1"?describe:describe.skip; +const M="a".repeat(64), N="b".repeat(64), K="c".repeat(64); +const url=process.env.G5A_POST_TEST_WRITER_URL??""; +const qid="WSSS_G5A_20261105_0200Z"; +suite("actual isolated PG16 immutable G5A POST budget, rights, replay",()=>{ + it("reserves only 3 unique 2-unit POST attempts after 18 source units",async()=>{ + const db=new pg.Client({connectionString:url}); + await db.connect(); + try{ + for(const source of [M,N,K]){ + await db.query( + "INSERT INTO clean.phase4_g5_fids_cohort_budget_v39 "+ + "(manifest_sha256,cohort_id,project_unit_ceiling,reserved_units) "+ + "VALUES($1,$2,24,$3)", + [source,qid,source===K?24:18]); + } + const id0=await reserveG5APostSlotDbV39(db,M,qid,0); + const id1=await reserveG5APostSlotDbV39(db,M,qid,1); + const id2=await reserveG5APostSlotDbV39(db,M,qid,2); + expect([id0,id1,id2]).toEqual([0,1,2].map(i=>postReservationUuidV39(M,i))); + const result=await db.query( + "SELECT reserved_units FROM clean.phase4_g5_fids_cohort_budget_v39 "+ + "WHERE manifest_sha256=$1",[M]); + expect(Number(result.rows[0].reserved_units)).toBe(24); + const ledger=await db.query( + "SELECT count(*)::int n,sum(units)::int spent FROM clean.adb_rest_attempt_ledger "+ + "WHERE cycle_id=$1",["G5A_NOV05_"+M.slice(0,32)]); + expect(ledger.rows[0].n).toBe(3); + expect(ledger.rows[0].spent).toBe(6); + await expect(reserveG5APostSlotDbV39(db,M,qid,0)) + .rejects.toThrow("P4_G5_G5A_POST_PROJECT_24_UNIT_BUDGET_HOLD"); + // Missing earlier POST attempts are genuinely unknown, not a reason to + // prohibit the later independent source observation opportunity. + const last=await reserveG5APostSlotDbV39(db,N,qid,2); + expect(last).toBe(postReservationUuidV39(N,2)); + const gap=await db.query( + "SELECT reserved_units FROM clean.phase4_g5_fids_cohort_budget_v39 "+ + "WHERE manifest_sha256=$1",[N]); + expect(Number(gap.rows[0].reserved_units)).toBe(20); + await expect(reserveG5APostSlotDbV39(db,N,qid,1)) + .rejects.toThrow("P4_G5_G5A_POST_POST_RESERVATION_REPLAY_OR_FOREIGN_SLOT_HOLD"); + await expect(reserveG5APostSlotDbV39(db,K,qid,0)) + .rejects.toThrow("P4_G5_G5A_POST_PROJECT_24_UNIT_BUDGET_HOLD"); + const all=await db.query( + "SELECT count(*)::int n FROM clean.adb_rest_attempt_ledger "+ + "WHERE category='g5a_post_schedule_fids'"); + expect(all.rows[0].n).toBe(4); + const perms=await db.query(`SELECT + has_table_privilege(current_user,'clean.adb_rest_attempt_ledger','INSERT') i, + has_table_privilege(current_user,'clean.adb_rest_attempt_ledger','SELECT') s, + has_table_privilege(current_user,'clean.adb_rest_attempt_ledger','UPDATE') u, + has_table_privilege(current_user,'clean.adb_rest_attempt_ledger','DELETE') d, + has_schema_privilege(current_user,'clean','CREATE') c`); + expect(perms.rows[0]).toMatchObject({ + i:true,s:true,u:false,d:false,c:false + }); + }finally{await db.end().catch(()=>undefined)} + }); +}); From af96328cc5b9a94bcea5c45c433857105e90c3b6 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:03:36 -0800 Subject: [PATCH 12/19] Keep PG16 test cohort-budget inserts privileged, mirror restricted real source-role ACL --- tests/phase4_gate5_g5a_post_ledger_pg16.test.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts index b89f4345a..529a5e026 100644 --- a/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts +++ b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts @@ -12,13 +12,9 @@ suite("actual isolated PG16 immutable G5A POST budget, rights, replay",()=>{ const db=new pg.Client({connectionString:url}); await db.connect(); try{ - for(const source of [M,N,K]){ - await db.query( - "INSERT INTO clean.phase4_g5_fids_cohort_budget_v39 "+ - "(manifest_sha256,cohort_id,project_unit_ceiling,reserved_units) "+ - "VALUES($1,$2,24,$3)", - [source,qid,source===K?24:18]); - } + // The disposable test setup pre-inserts three cohort budgets as + // privileged schema bootstrap; this writer must NOT have INSERT on + // the budget table merely to run a postflight observation. const id0=await reserveG5APostSlotDbV39(db,M,qid,0); const id1=await reserveG5APostSlotDbV39(db,M,qid,1); const id2=await reserveG5APostSlotDbV39(db,M,qid,2); From 784eb8c91e94d2d99192ebe91c7778792d1618b9 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:04:07 -0800 Subject: [PATCH 13/19] Test real G5A POST exactly-scoped grant and 24-unit durable ledger on disposable PG16, no provider calls --- .../phase4-gate5-g5a-post-disposable-pg16.yml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml diff --git a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml new file mode 100644 index 000000000..140f867a4 --- /dev/null +++ b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml @@ -0,0 +1,90 @@ +name: Phase4 Gate5 G5A POST Ledger Disposable PG16 +on: + pull_request: + branches: [main] + paths: + - ".github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml" + - "scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts" + - "scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts" + - "tests/phase4_gate5_g5a_post_ledger_pg16.test.ts" + push: + branches: [main] + paths: + - ".github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml" + - "scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts" + - "scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts" + - "tests/phase4_gate5_g5a_post_ledger_pg16.test.ts" +permissions: + contents: read +env: + ADB_AUTO_COLLECT: "0" + V39_PHASE6_PROCESS_ARMED: "0" + AERODATABOX_API_KEY: "" + DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/v39_ci?sslmode=disable" + V39_DATABASE_RUNTIME_URL: "postgresql://g5_ci_runtime:g5_disposable_only@localhost:5432/v39_ci?sslmode=disable" + G5A_POST_TEST_WRITER_URL: "postgresql://p4_g5_g5a_owner_20261103:g5_post_disposable_only@localhost:5432/v39_ci?sslmode=disable" + G5A_POST_TEST_PG16: "1" +jobs: + post-ledger-pg16: + runs-on: ubuntu-latest + timeout-minutes: 12 + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: v39_ci + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres -d v39_ci" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + - run: npm ci --include=dev --no-audit --no-fund + - name: Boot only disposable PG16 baseline and staged V0076 + run: | + set -Eeuo pipefail + node --import tsx scripts/apply_boot_migrations_v39.ts + psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -f migrations/staged/V0076__phase4_gate5_logged_physical_fids_attempt_owner_v39.sql >/dev/null + - name: Prepare only disposable non-admin source writer and restricted app role + run: | + set -Eeuo pipefail + psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<'SQL' + CREATE ROLE g5_ci_runtime LOGIN PASSWORD 'g5_disposable_only'; + CREATE ROLE p4_g5_g5a_owner_20261103 LOGIN + NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS + PASSWORD 'g5_post_disposable_only'; + GRANT USAGE ON SCHEMA clean TO p4_g5_g5a_owner_20261103; + GRANT SELECT,UPDATE ON TABLE clean.phase4_g5_fids_cohort_budget_v39 + TO p4_g5_g5a_owner_20261103; + INSERT INTO clean.phase4_g5_fids_cohort_budget_v39 + (manifest_sha256,cohort_id,project_unit_ceiling,reserved_units) + VALUES + (repeat('a',64),'WSSS_G5A_20261105_0200Z',24,18), + (repeat('b',64),'WSSS_G5A_20261105_0200Z',24,18), + (repeat('c',64),'WSSS_G5A_20261105_0200Z',24,24); + SQL + - name: Test actual one-time baseline attempt ledger GRANT (no other tables) + run: | + set -Eeuo pipefail + set +x + GITHUB_ACTIONS=false \ + P4_G5_G5A_POST_GRANT_INTENT=EXACT_G5A_POST_IMMUTABLE_LEDGER_INSERT_SELECT_NO_OTHER_PRIVILEGES \ + node --import tsx scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts + - name: Verify real immutable POST reservation, 24-unit cap and replay failure + run: | + set -Eeuo pipefail + npx vitest run tests/phase4_gate5_g5a_post_ledger_pg16.test.ts + echo 'POST_PROVIDER_CALLS=0' + echo 'PRODUCTION_DATABASE_WRITES=0' + echo 'GITHUB_PROVIDER_SECRETS_AVAILABLE=FALSE' From 9e358c7afff424187af16b7273a4b5e217a52ab7 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:06:20 -0800 Subject: [PATCH 14/19] Use actual Plan f.8 pinned OpenAPI document SHA-256, not protocol markdown SHA or fabricated live provider version --- ...e4_gate5_g5a_prospective_post_schedule_observation.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts index def2d34d6..4d2146a18 100644 --- a/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts +++ b/scripts/v39_phase4_gate5_g5a_prospective_post_schedule_observation.ts @@ -36,7 +36,6 @@ const END="2026-11-05T06:00:00.000Z"; const SHA=/^[a-f0-9]{64}$/; const GH=/^[a-f0-9]{40}$/; const ms=(s:string)=>Date.parse(s); -const now=()=>new Date().toISOString(); const hash=(s:string)=>createHash("sha256").update(s).digest("hex"); const sleep=(m:number)=>new Promise(r=>setTimeout(r,m)); function hold(code:string):never{throw Error("P4_G5_G5A_POST_"+code+"_HOLD")} @@ -292,10 +291,12 @@ export async function main(){ serviceWindowEndUtc:new Date(TO),cutoffUtc:new Date(planned), withCancelled:true,withCodeshared:true,withCargo:false,withPrivate:false, direction:"Departure",restCategory:"outcome", - providerApiVersion:"RapidAPI-v1.15.4", + // The Plan f.8 §5.1 verified OpenAPI DOC BYTES pin is v1.15.3.0. + // A G5A protocol text SHA is NOT an AeroDataBox OpenAPI document SHA. + // Do not conflate frozen docs or live server schema version. + providerApiVersion:"RapidAPI-doc-pin-1.15.3.0-not-live-schema-certified", fidsProtocolVersion:"G5A-POST-20261103-v1", - openapiSha256:hash(readFileSync( - "data-collection/phase4/2026-11-03_G5A_PROSPECTIVE_PROVIDER_OBSERVABLE_PROTOCOL_AMENDMENT.md","utf8")), + openapiSha256:"735620f2d2132c5bf51768f50caa767b7f0b25be8b128679641402666696890a", g5aObservedScheduleProtocol:true,g5aPostReservationId:id,batchId:id }; try{ From 49a76e71dba0b2d7bcad456e34afa7c89c1ae6bb Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:08:40 -0800 Subject: [PATCH 15/19] Isolated PG16 POST test role: allow existing source readback tables as in merged source owner grant --- .github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml index 140f867a4..bd1c46c7a 100644 --- a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml +++ b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml @@ -67,6 +67,9 @@ jobs: GRANT USAGE ON SCHEMA clean TO p4_g5_g5a_owner_20261103; GRANT SELECT,UPDATE ON TABLE clean.phase4_g5_fids_cohort_budget_v39 TO p4_g5_g5a_owner_20261103; + GRANT SELECT ON TABLE clean.fids_query_response, + clean.flight_population,clean.flight_snapshots + TO p4_g5_g5a_owner_20261103; INSERT INTO clean.phase4_g5_fids_cohort_budget_v39 (manifest_sha256,cohort_id,project_unit_ceiling,reserved_units) VALUES From fe3597c081157103efc1d3dacb4da95db7efc1f8 Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:10:14 -0800 Subject: [PATCH 16/19] =?UTF-8?q?Checkpoint=20real=203=C3=972-unit=20WSSS?= =?UTF-8?q?=20G5A=20POST=20sources,=20immutable=20budget=20ledger,=20T+2h?= =?UTF-8?q?=20observed/unknown=20aggregate=20and=20no=20extra=20secret?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...OST_6_UNIT_SOURCE_AND_LABEL_MEASUREMENT.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 data-collection/phase4/2026-11-03_GATE5_G5A_PROSPECTIVE_POST_6_UNIT_SOURCE_AND_LABEL_MEASUREMENT.md diff --git a/data-collection/phase4/2026-11-03_GATE5_G5A_PROSPECTIVE_POST_6_UNIT_SOURCE_AND_LABEL_MEASUREMENT.md b/data-collection/phase4/2026-11-03_GATE5_G5A_PROSPECTIVE_POST_6_UNIT_SOURCE_AND_LABEL_MEASUREMENT.md new file mode 100644 index 000000000..4ad79ae0f --- /dev/null +++ b/data-collection/phase4/2026-11-03_GATE5_G5A_PROSPECTIVE_POST_6_UNIT_SOURCE_AND_LABEL_MEASUREMENT.md @@ -0,0 +1,19 @@ +# Gate 5 G5A — separate real POST observation and provider-schedule revision label + +**Engineering checkpoint, 2026-11-03.** The binding owner's direction is rapid scientific measurement, not repeated vendor email/runway investigations. See first two links in Phase4 README. **This checkpoint does NOT declare source collection, actual POST, scientific PASS, or Phase5/Phase6 authorized.** + +The 2026-11-05 WSSS source cohort (main-merged #372) has 9 prospective PRE source slots at T−24h/T−6h/T−90m, 2 REST units/slot, 18-unit ceiling under **one 24-unit project cap**. This POST extension uses the remaining **maximum 6 units**, 3 one-attempt prospective FIDS GETs. It reobserves provider schedule changes and never calls them actual departure or runway delay. It does not add new DDL, and does not reinstall any phase schema. + +**Frozen POST source plan**: UTC 2026-11-05 **03:40, 04:10, 04:40**, corresponding to Nov4 **7:40, 8:10, 8:40 PM Pacific**. Source range WSSS UTC Nov5 **01:00–05:00** (Singapore local Nov5 09:00–13:00) is **four hours**, wider than the first one-hour cohort because retimed flights can move out of the original hour. This increases bias/coverage uncertainty but avoids conflating an absent row with an on-time flight. Frozen label watermark per physical flight: `W=first_native_schedule_T+2h`. Post-source availability must be prospectively recorded `A_post<=W` to count; source absent or after W is **UNKNOWN**. The provider's native ID + confirmed operating flight and route, **not mutable schedule or query window**, join reobservations. + +**GitHub-only prearm**: `Phase4 Gate5 G5A WSSS Real Prospective POST` scheduled 2026-11-05 **02:10 UTC** (Nov4 6:10pm Pacific), then sources above; result audit after 2026-11-05 **05:01 UTC** (Nov4 9:01pm Pacific). If GH schedule starts late, it must hold or mark missed slots, not retroactively make a response available before W. No PR/push manual/Cloudflare/Replit paid path. + +**Immutable budget accounting**: before *each* physical GET, PostgreSQL SERIALIZABLE transaction locks the existing `phase4_g5_fids_cohort_budget_v39` row, verifies frozen manifest 24-unit ceiling and sufficient balance, atomically increases its reserved-unit count by 2 and writes deterministic unique UUID to existing B0062 `clean.adb_rest_attempt_ledger` category `g5a_post_schedule_fids`, then **commits before GET**. Failed/ambiguous HTTP is never refunded/replayed. A failed earlier slot is **UNOBSERVED**, later POST can still proceed; no backfill. POST original decoded source, normalized rows and source hash are persisted via the reviewed `fidsCensus_v3.ts` path, with this exact reservation UUID included in its canonical reconstructed source envelope and batch ID. No source Content/flight ID printed in Actions. + +**One extra exact-privilege owner action, no extra secret**: the existing restricted source role `p4_g5_g5a_owner_20261103` was created successfully using #371. To use the existing baseline POST attempt ledger, **only** `GRANT SELECT, INSERT ON clean.adb_rest_attempt_ledger` is required. Reviewed one-time no-provider installer `scripts/v39_phase4_gate5_g5a_post_ledger_exact_grant_once.ts` is tested with disposable PG16, preserves existing no-DELETE/no-DDL and checks PUBLIC has no INSERT. It does not change the GitHub environment secret or the runtime role. Run exactly once only after reviewed PR merge and hash pin, never run source schema migrations again. + +**Measured outcome**: at W+1min, aggregate PRE snapshot source baseline and the latest qualified persisted POST observation as of W, by `horizon`: number of persisted PRE candidates, valid baseline, valid post, observed schedule revisions of absolute ≥15min, observed changes below threshold, **unknown/no qualified post**. No negative label is inferred from a missing reobservation. The audit uses fixed cohort manifest hash, source query UUID, immutable post reservation UUID and server `available_at`, stable physical ID, and exact 2h clock watermark. This is a **reported schedule-revision classification** with coverage/selection bias, not original strict actual wheels-off target. + +**Independent post-measurement work**: source+PRE audit from independent reader, callback inside/outside/unresolved, AIRBORNE coverage, source Content 23h/24h retention cleanup and actual billed units still require verification. The original strict runway-defined Gate5 remains HOLD, and G5A PASS_WITH_MEASURED_LIMITATIONS requires real independent measured evidence and a separately reviewed scientific verdict; do not infer it from CI or schedule existence. + +**OpenAPI source provenance**: use the actual Plan f.8 §5.1 pinned v1.15.3.0 document SHA `735620f2d2132c5bf51768f50caa767b7f0b25be8b128679641402666696890a` explicitly as historical documentation, *not* G5A protocol Markdown SHA or an unverified live provider schema version; source owner equivalent repair in #373. From bd523a9b5f7f874af245fdfeed04fac02a71e2ba Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:14:58 -0800 Subject: [PATCH 17/19] Fix disposable POST PG16 fixture: each manifest has unique cohort ID as V0076 production schema requires --- .github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml index bd1c46c7a..9c2840f14 100644 --- a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml +++ b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml @@ -74,8 +74,8 @@ jobs: (manifest_sha256,cohort_id,project_unit_ceiling,reserved_units) VALUES (repeat('a',64),'WSSS_G5A_20261105_0200Z',24,18), - (repeat('b',64),'WSSS_G5A_20261105_0200Z',24,18), - (repeat('c',64),'WSSS_G5A_20261105_0200Z',24,24); + (repeat('b',64),'WSSS_G5A_20261105_0200Z_N',24,18), + (repeat('c',64),'WSSS_G5A_20261105_0200Z_K',24,24); SQL - name: Test actual one-time baseline attempt ledger GRANT (no other tables) run: | From 4e6b4445dee38801a2fb1055b6c7b7cec670a78f Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:15:17 -0800 Subject: [PATCH 18/19] Match unique manifest and cohort IDs in real disposable PG16 POST reservation test --- tests/phase4_gate5_g5a_post_ledger_pg16.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts index 529a5e026..5e31b927b 100644 --- a/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts +++ b/tests/phase4_gate5_g5a_post_ledger_pg16.test.ts @@ -32,15 +32,15 @@ suite("actual isolated PG16 immutable G5A POST budget, rights, replay",()=>{ .rejects.toThrow("P4_G5_G5A_POST_PROJECT_24_UNIT_BUDGET_HOLD"); // Missing earlier POST attempts are genuinely unknown, not a reason to // prohibit the later independent source observation opportunity. - const last=await reserveG5APostSlotDbV39(db,N,qid,2); + const last=await reserveG5APostSlotDbV39(db,N,qid+'_N',2); expect(last).toBe(postReservationUuidV39(N,2)); const gap=await db.query( "SELECT reserved_units FROM clean.phase4_g5_fids_cohort_budget_v39 "+ "WHERE manifest_sha256=$1",[N]); expect(Number(gap.rows[0].reserved_units)).toBe(20); - await expect(reserveG5APostSlotDbV39(db,N,qid,1)) + await expect(reserveG5APostSlotDbV39(db,N,qid+'_N',1)) .rejects.toThrow("P4_G5_G5A_POST_POST_RESERVATION_REPLAY_OR_FOREIGN_SLOT_HOLD"); - await expect(reserveG5APostSlotDbV39(db,K,qid,0)) + await expect(reserveG5APostSlotDbV39(db,K,qid+'_K',0)) .rejects.toThrow("P4_G5_G5A_POST_PROJECT_24_UNIT_BUDGET_HOLD"); const all=await db.query( "SELECT count(*)::int n FROM clean.adb_rest_attempt_ledger "+ From 5842155395d2194b47f82489636f0f88b28c2f7e Mon Sep 17 00:00:00 2001 From: Hamza Khan <139022998+HKcode22@users.noreply.github.com> Date: Tue, 3 Nov 2026 06:16:55 -0800 Subject: [PATCH 19/19] Fix isolated PG16 runtime role fixture: permit clean schema USAGE for read-only privilege diagnostics --- .github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml index 9c2840f14..ce4eac405 100644 --- a/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml +++ b/.github/workflows/phase4-gate5-g5a-post-disposable-pg16.yml @@ -61,6 +61,7 @@ jobs: set -Eeuo pipefail psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<'SQL' CREATE ROLE g5_ci_runtime LOGIN PASSWORD 'g5_disposable_only'; + GRANT USAGE ON SCHEMA clean TO g5_ci_runtime; CREATE ROLE p4_g5_g5a_owner_20261103 LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD 'g5_post_disposable_only';