#!/usr/bin/env node
import { access, readFile, readdir } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
const ROOT=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
const failures=[];const LIMIT=30000;const RESERVE=1000;
const ACTIONS=new Set(['block','allow','allowAllRequests','upgradeScheme','redirect']);
const TOP=new Set(['id','priority','action','condition']);
const COND=new Set(['urlFilter','regexFilter','isUrlFilterCaseSensitive','resourceTypes','excludedResourceTypes','requestMethods','excludedRequestMethods','initiatorDomains','excludedInitiatorDomains','requestDomains','excludedRequestDomains','domainType','tabIds','excludedTabIds','responseHeaders','excludedResponseHeaders']);
const TYPES=new Set(['main_frame','sub_frame','stylesheet','script','image','font','object','xmlhttprequest','ping','csp_report','media','websocket','webtransport','webbundle','other']);
const FILES=[['rules/runtime_grax.json',4000000,4999999],['rules/runtime_easylist.json',5000000,5999999],['rules/runtime_easyprivacy.json',6000000,6999999],['rules/runtime_popunder.json',7000000,7999999]];
await main();
async function main(){
 const m=await readJson('manifest.json');check(m?.manifest_version===3,'Manifest V3');check(m?.name==='Grax','Name is Grax');check(m?.version==='1.5.4','Version 1.5.4');check(!Object.hasOwn(m??{},'declarative_net_request'),'No static rule_resources block');check(m?.permissions?.includes('declarativeNetRequestWithHostAccess')||m?.permissions?.includes('declarativeNetRequest'),'DNR permission');
 check(!Array.isArray(m?.content_scripts)||m.content_scripts.length===0,'No always-on manifest content scripts');check(m?.permissions?.includes('scripting'),'Scripting permission for storage-driven runtime registration');
 const pkg=await readJson('package.json');check(pkg?.version==='1.5.4','Package version 1.5.4');
 const backgroundSource=await readFile(path.join(ROOT,'background.js'),'utf8');
 check(backgroundSource.includes('RUNTIME_CONTENT_SCRIPTS')&&backgroundSource.includes('registerRuntimeContentScripts')&&backgroundSource.includes('unregisterRuntimeContentScripts'),'Storage-driven content script registration present');
 check(backgroundSource.includes("world: 'MAIN'")&&backgroundSource.includes("world: 'ISOLATED'")&&backgroundSource.includes("runAt: 'document_start'"),'Dynamic MAIN/ISOLATED document-start scripts declared');
 check(backgroundSource.includes('TRUSTED_AND_UNTRUSTED_CONTEXTS'),'Content scripts can read master state directly');
 check(backgroundSource.includes('injectRuntimeIntoOpenTabs')&&backgroundSource.includes("files: ['compat-shim.js']")&&backgroundSource.includes("files: ['page-guard.js']")&&backgroundSource.includes("files: ['content.js']"),'Immediate reinjection path present');
 check(backgroundSource.includes('if (!state.settings.enabled) return [];'),'Paused mode removes all managed DNR policy rules');
 check(backgroundSource.includes('buildCompatibilityRules')&&backgroundSource.includes('/mocks/telemetry.js')&&backgroundSource.includes('/mocks/success.json')&&backgroundSource.includes('/mocks/pixel.gif'),'Compatibility-safe DNR mock response layer present');
 check(backgroundSource.includes('setTabBypass')&&backgroundSource.includes('updateSessionRules')&&backgroundSource.includes('clearTabBypassesForHostname'),'Immediate tab-scoped site bypass present');
 check(backgroundSource.includes('STRUCTURAL_ASSET_FILTERS')&&backgroundSource.includes('STRUCTURAL_MODULE_FILTERS')&&backgroundSource.includes('FIRST_PARTY_REALTIME_FILTERS')&&backgroundSource.includes('FIRST_PARTY_AUTH_PATH_FILTERS')&&backgroundSource.includes('STRUCTURAL_AUTH_DOMAINS'),'Framework, module, realtime, and authentication structural exclusions present');
 check(backgroundSource.includes('FIRST_PARTY_STRUCTURAL_RESOURCE_TYPES')&&backgroundSource.includes('VERIFIED_FIRST_PARTY_TELEMETRY_FILTERS')&&backgroundSource.includes('VERIFIED_FIRST_PARTY_AD_FILTERS')&&backgroundSource.includes('FIRST_PARTY_COMPATIBILITY_EXCLUSIONS'),'Universal first-party structural safeguard and verified mock routes present');
 check(backgroundSource.includes('GROK_TRUSTED_ENTITY_DOMAINS')&&backgroundSource.includes("'grok.com', 'x.com', 'twitter.com', 'twimg.com'")&&backgroundSource.includes('priority: 975_000'),'Grok/X/Twitter/Twimg entity-scoped infrastructure allowance present');
 check(backgroundSource.includes('CLAUDE_TRUSTED_ENTITY_DOMAINS')&&backgroundSource.includes("'claude.ai', 'anthropic.com'")&&backgroundSource.includes('priority: 976_000'),'Claude/Anthropic entity-scoped infrastructure allowance present');
 check(backgroundSource.includes('DEEPSEEK_TRUSTED_ENTITY_DOMAINS')&&backgroundSource.includes("'deepseek.com', 'chat.deepseek.com', 'api.deepseek.com'")&&backgroundSource.includes('priority: 977_000'),'DeepSeek entity-scoped infrastructure allowance present');
 check(backgroundSource.includes('COMPATIBILITY_SHIM_EXCLUDE_MATCHES')&&backgroundSource.includes("'*://claude.ai/*'")&&backgroundSource.includes("'*://*.deepseek.com/*'"),'Trusted native application registration exclusions present');
 check(backgroundSource.includes("'filma24.stream'")&&backgroundSource.includes('excludedInitiatorDomains: [...FIRST_PARTY_COMPATIBILITY_EXCLUSIONS]'),'Filma24 excluded from new first-party compatibility layer');
 check(!backgroundSource.includes("header: 'content-security-policy'")&&!backgroundSource.includes('content-security-policy-report-only'),'Host CSP remains intact; MAIN-world code uses browser registration rather than header weakening');
 check(backgroundSource.includes('PURE_TELEMETRY_DOMAINS')&&backgroundSource.includes('FUNCTIONAL_SDK_DOMAINS'),'Telemetry-only and functional SDK routing classes present');
 const compatSource=await readFile(path.join(ROOT,'compat-shim.js'),'utf8');
 check(compatSource.includes('__graxCompatibilityShimInstalledV4__')&&compatSource.includes('createRecursiveCallable'),'Lazy recursive MAIN-world compatibility shim present');
 check(compatSource.includes('return proxy;')&&compatSource.includes("property === 'then'")&&compatSource.includes('getOwnPropertyDescriptor'),'Recursive calls remain chain-safe, reflective, and non-thenable');
 check(compatSource.includes('NATIVE_STUB_SOURCE')&&compatSource.includes('installFunctionToStringMask')&&compatSource.includes('stealthFunctions'),'Native-signature masking for generated compatibility callables present');
 check(compatSource.includes('unhandledrejection')&&compatSource.includes('recoverTrackingRuntimeFailure'),'Silent tracking-related hydration recovery present');
 check(compatSource.includes('isNativeBypassContext')&&compatSource.includes("'claude.ai'")&&compatSource.includes('SECURITY_VERIFICATION_HOSTS'),'Claude and verification-frame native bypass present');
 check(!compatSource.includes('MutationObserver')&&!compatSource.includes('scanDocument'),'Compatibility shim does not eagerly race real SDK initialization');
 check(!compatSource.includes("Object.defineProperty(Element.prototype, 'setAttribute'"),'Compatibility shim avoids global Element prototype mutation');
 const mockSource=await readFile(path.join(ROOT,'mocks/telemetry.js'),'utf8');
 check(mockSource.includes('createRecursiveCallable')&&mockSource.includes('__grax_telemetry_mock_ready_v1__'),'Mock script installs recursive stubs and readiness signal');
 const guardSource=await readFile(path.join(ROOT,'page-guard.js'),'utf8');
 check(guardSource.includes('installWindowOpenGuard()'),'Window open guard installed');
 check(guardSource.includes('installDirectEventPropertyGuards()'),'Direct event-property guards installed');
 check(guardSource.includes('installSelectiveEventListenerDefuser()'),'Event listener defuser installed');
 check(guardSource.includes('installCapturePhaseSmothering()'),'Capture-phase smothering installed');
 check(guardSource.includes('installDynamicIframeGuard()'),'Dynamic iframe guard installed');
 check(guardSource.includes('installMarkupRealmGuards()'),'String-parsed realm guards installed');
 check(guardSource.includes('installShadowDomGuard()'),'Shadow DOM guard installed');
 check(guardSource.includes('installWorkerExecutionGuards()'),'Worker and Blob guards installed');
 check(guardSource.includes('installRealmMutationObserver()'),'Realm mutation observer installed');
 check(guardSource.includes("Object.defineProperty(HTMLAnchorElement.prototype, 'click'"),'Anchor click proxy installed');
 check(guardSource.includes("Object.defineProperty(Element.prototype, 'dispatchEvent'"),'Element dispatchEvent proxy installed');
 check(guardSource.includes('bindCaptureTarget(window)')&&guardSource.includes('bindCaptureTarget(document)')&&guardSource.includes('bindCaptureTarget(document.documentElement)')&&guardSource.includes('bindCaptureTarget(document.body)'),'Window, document, documentElement, and body capture guards installed');
 check(guardSource.includes('capture: true, passive: false'),'Non-passive capture smothering installed');
 check(guardSource.includes("onclick: 'click'")&&guardSource.includes("onmousedown: 'mousedown'")&&guardSource.includes("onpointerdown: 'pointerdown'")&&guardSource.includes("onmouseup: 'mouseup'")&&guardSource.includes("ontouchstart: 'touchstart'"),'All direct event-property hooks declared');
 check(guardSource.includes('configurable: !nativeDomRuntime')&&guardSource.includes('writable: !nativeDomRuntime'),'Native-runtime immutable descriptors configured');
 check(guardSource.includes('innerHTML')&&guardSource.includes('outerHTML')&&guardSource.includes('insertAdjacentHTML'),'HTML string parser surfaces guarded');
 check(guardSource.includes('createHTMLDocument')&&guardSource.includes('parseFromString'),'Detached document parsers guarded');
 check(guardSource.includes('attachShadow')&&guardSource.includes("mode: 'open'"),'Shadow-root interception present');
 check(guardSource.includes("installWorkerConstructorGuard('Worker'")&&guardSource.includes("installWorkerConstructorGuard('SharedWorker'")&&guardSource.includes('installServiceWorkerGuard()'),'Worker, SharedWorker, and service-worker guards present');
 check(guardSource.includes('guardedCreateObjectURL')&&guardSource.includes('GuardedBlob'),'Blob and object-URL guards present');
 check(guardSource.includes('MEDIA_FRAMEWORK_SOURCE')&&guardSource.includes('MEDIA_MIME_PATTERN')&&guardSource.includes('MEDIA_WORKER_URL_PATTERN'),'Media framework, MIME, and worker URL heuristics present');
 check(guardSource.includes('isMediaInteractionEvent')&&guardSource.includes('isMediaControlElement')&&guardSource.includes('documentHasMediaPlaybackSurface'),'Media-aware gesture calibration present');
 check(guardSource.includes("'grok.com'")&&guardSource.includes("'x.com'")&&guardSource.includes("'twitter.com'")&&guardSource.includes("'twimg.com'"),'Grok/X application family receives native MAIN-world treatment');
 check(guardSource.includes('externalDestinationLocked')&&guardSource.includes('destinationLocked'),'Target-based destination lock present');
 check(guardSource.includes('TRUSTED_APPLICATION_ORIGINS')&&guardSource.includes('TRUSTED_PLATFORM_DESTINATIONS'),'Trusted application and destination safelists present');
 check(guardSource.includes('youtube.com')&&guardSource.includes('googlevideo.com')&&guardSource.includes('ytimg.com')&&guardSource.includes('ggpht.com'),'YouTube application and delivery families safelisted');
 check(guardSource.includes('twitch.tv')&&guardSource.includes('twitchcdn.net')&&guardSource.includes('jtvnw.net')&&guardSource.includes('ttvnw.net'),'Twitch application and delivery families safelisted');
 check(guardSource.includes('netflix.com')&&guardSource.includes('nflxvideo.net')&&guardSource.includes('nflximg.net'),'Netflix application and delivery families safelisted');
 check(guardSource.includes('trustedApplicationContext')&&guardSource.includes('isTrustedApplicationHostname'),'Trusted first-party MAIN-world bypass present');
 check(guardSource.includes('OFFICIAL_APPLICATION_SHELL_ORIGINS')&&guardSource.includes('applicationShellContext')&&guardSource.includes('isApplicationShellDocumentContext'),'Dynamic application-shell structural allowance present');
 check(guardSource.includes('cloudflare.com')&&guardSource.includes('APPLICATION_SHELL_HOST_PATTERN')&&guardSource.includes('APPLICATION_SHELL_PATH_PATTERN'),'Cloudflare and generic dashboard shell contexts covered');
 check(guardSource.includes('setRuntimeEnabled')&&guardSource.includes('removeCompatibilityShims'),'MAIN-world runtime disable/restore lifecycle present');
 check(guardSource.includes('if (!enabled || !event?.isTrusted) return;'),'Capture-phase hooks short-circuit while paused');
 check(guardSource.includes('installSecurityContextHeuristic()')&&guardSource.includes('SECURITY_VERIFICATION_PROVIDERS'),'Context-aware WAF security heuristic installed');
 check(guardSource.includes('/cdn-cgi')&&guardSource.includes('__cf_chl_')&&guardSource.includes('captcha-delivery.com')&&guardSource.includes('datadome.co'),'Cloudflare, CAPTCHA, and DataDome verification signatures present');
 check(guardSource.includes('isSecurityStructuralPassActive')&&guardSource.includes('securityStructuralPassUntil'),'Temporary structural security pass present');
 check(guardSource.includes('isSecurityVerificationMarkup')&&guardSource.includes('markSecurityVerificationElement'),'Security frame and markup scoping present');
 const openStart=guardSource.indexOf('function inspectWindowOpen');const openEnd=guardSource.indexOf('function installDirectEventPropertyGuards',openStart);const openGuardBody=guardSource.slice(openStart,openEnd);check(openStart>=0&&openEnd>openStart&&!openGuardBody.includes('securityStructuralPass'),'WAF structural pass is isolated from window.open destination lock');
 const anchorStart=guardSource.indexOf('function shouldBlockProgrammaticAnchor');const anchorEnd=guardSource.indexOf('function isStealthProgrammaticAnchor',anchorStart);const anchorGuardBody=guardSource.slice(anchorStart,anchorEnd);check(anchorStart>=0&&anchorEnd>anchorStart&&!anchorGuardBody.includes('securityStructuralPass'),'WAF structural pass is isolated from scripted anchor destination lock');
 check(guardSource.includes('starveActivePopupGesture')&&guardSource.includes('rememberTrustedGesture'),'Active gesture starvation bridge present');
 check(guardSource.includes('createMediaCompatibleHighConfidenceListener'),'Media-compatible high-confidence listener isolation present');
 check(guardSource.includes('isPopupCapableScheme'),'Blob, data, and executable popup schemes remain locked');
 check(guardSource.includes('inspectBinaryMediaPart')&&guardSource.includes('bytes[0] === 0x47')&&guardSource.includes('0x1a')&&guardSource.includes('ftyp'),'Binary media-segment inspection present');
 check(guardSource.includes('mediaSafe')&&guardSource.includes('WORKER_POPUP_SOURCE'),'Media-safe worker bypass retains popup-payload rejection');
 check(guardSource.includes('stripe.com')&&guardSource.includes('paypal.com')&&guardSource.includes('accounts.google.com')&&guardSource.includes('appleid.apple.com')&&guardSource.includes('github.com')&&guardSource.includes('hcaptcha.com')&&guardSource.includes('recaptcha.net')&&guardSource.includes('challenges.cloudflare.com')&&guardSource.includes('arkoselabs.com'),'Protected auth, payment, and CAPTCHA destinations retained');
 check(guardSource.includes('rcdn-web'),'Observed rcdn-web destination classified');
 check(guardSource.includes('stopImmediatePropagation')&&guardSource.includes('stopPropagation')&&guardSource.includes('preventDefault'),'Full event cancellation path present');
 check(guardSource.includes('return false;'),'Blocked scripted navigation returns false');
 for(const f of ['background.js','compat-shim.js','page-guard.js','content.js','popup.html','popup.css','popup.js','options.html','options.css','options.js','mocks/telemetry.js','mocks/success.json','mocks/pixel.gif','mocks/empty.css','mocks/blank.html','tests/chromium-platform-runtime.py','tests/chromium-waf-runtime.py','tests/chromium-killswitch-appshell.py','tests/chromium-compatibility-shim.py','tests/background-killswitch.mjs','tests/compat-shim-smoke.mjs','rules/metadata.json','rules/BUILD_REPORT.json',...FILES.map(x=>x[0])])await exists(f);
 const ids=new Set();let total=0,regex=0;for(const [f,min,max] of FILES){const r=await validateRules(f,min,max,ids);total+=r.count;regex+=r.regex;}
 check(total+RESERVE<=LIMIT,`Dynamic quota ${total}+${RESERVE}<=${LIMIT}`);check(regex<=1000,`Regex quota ${regex}/1000`);
 const meta=await readJson('rules/metadata.json');check(meta?.architecture==='dynamic-only-compressed-native-dnr+main-world-page-guard','Architecture metadata');check(meta?.budgets?.packagedCompressedRules===total,'Metadata rule count');check(meta?.budgets?.logicalSourceRules===49182,'Logical source coverage');check(Array.isArray(meta?.ruleResources)&&meta.ruleResources.length===0,'Zero static resources');
 for(const resource of meta?.dynamicResources||[]){const bytes=await readFile(path.join(ROOT,resource.path));const digest=createHash('sha256').update(bytes).digest('hex');check(digest===resource.sha256,`${resource.path} SHA-256`);}
 const preservation=await readJson('tests/preservation-v153.json');for(const [relative,expected] of Object.entries(preservation?.files||{})){const bytes=await readFile(path.join(ROOT,relative));const digest=createHash('sha256').update(bytes).digest('hex');check(digest===expected,`Preserved v1.5.3 resource ${relative}`);}
 for(const f of ['background.js','compat-shim.js','page-guard.js','content.js','popup.js','options.js','mocks/telemetry.js','tools/validate-project.mjs','tests/page-guard-smoke.mjs','tests/background-killswitch.mjs','tests/compat-shim-smoke.mjs']){const r=spawnSync(process.execPath,['--check',path.join(ROOT,f)],{encoding:'utf8'});check(r.status===0,`${f} syntax`,r.stderr);}
 for(const f of ['tests/chromium-platform-runtime.py','tests/chromium-waf-runtime.py','tests/chromium-killswitch-appshell.py','tests/chromium-compatibility-shim.py']){const r=spawnSync('python3',['-m','py_compile',path.join(ROOT,f)],{encoding:'utf8'});check(r.status===0,`${f} syntax`,r.stderr);}
 const guardSmoke=spawnSync(process.execPath,[path.join(ROOT,'tests/page-guard-smoke.mjs')],{encoding:'utf8'});check(guardSmoke.status===0,'Page guard smoke tests',guardSmoke.stderr||guardSmoke.stdout);
 const lifecycleSmoke=spawnSync(process.execPath,[path.join(ROOT,'tests/background-killswitch.mjs')],{encoding:'utf8'});check(lifecycleSmoke.status===0,'Background absolute kill-switch tests',lifecycleSmoke.stderr||lifecycleSmoke.stdout);
 const compatSmoke=spawnSync(process.execPath,[path.join(ROOT,'tests/compat-shim-smoke.mjs')],{encoding:'utf8'});check(compatSmoke.status===0,'Recursive compatibility shim smoke tests',compatSmoke.stderr||compatSmoke.stdout);
 const cosmetics=(await readdir(path.join(ROOT,'rules/cosmetic'))).filter(x=>x.endsWith('.json'));check(cosmetics.length>=65,'Cosmetic shards');
 if(failures.length){console.error('FAIL');for(const x of failures)console.error(' - '+x);process.exitCode=1;}else console.log(`PASS · ${total} packaged filter rules · ${regex} regex rules · Claude/DeepSeek compatibility, v1.5.3 preservation, and instant bypass lifecycle`);
}
async function validateRules(f,min,max,ids){const rs=await readJson(f);check(Array.isArray(rs),`${f} array`);if(!Array.isArray(rs))return{count:0,regex:0};let regex=0,maxChunk=0;for(let i=0;i<rs.length;i++){const r=rs[i],label=`${f}[${i}]`;if(!obj(r)){failures.push(`${label} not object`);continue;}const bad=Object.keys(r).filter(k=>!TOP.has(k));if(bad.length)failures.push(`${label} top keys ${bad}`);if(!Number.isInteger(r.id)||r.id<min||r.id>max||ids.has(r.id))failures.push(`${label} id`);ids.add(r.id);if(!Number.isInteger(r.priority)||r.priority<1)failures.push(`${label} priority`);if(!obj(r.action)||!ACTIONS.has(r.action.type))failures.push(`${label} action`);if(!obj(r.condition)||!Object.keys(r.condition).length){failures.push(`${label} condition`);continue;}const bc=Object.keys(r.condition).filter(k=>!COND.has(k));if(bc.length)failures.push(`${label} condition keys ${bc}`);if('urlFilter'in r.condition&&'regexFilter'in r.condition)failures.push(`${label} both filters`);if('regexFilter'in r.condition){regex++;if(typeof r.condition.regexFilter!=='string'||!r.condition.regexFilter)failures.push(`${label} regex`);}for(const k of ['requestDomains','excludedRequestDomains','initiatorDomains','excludedInitiatorDomains'])if(k in r.condition){const v=r.condition[k];if(!Array.isArray(v)||!v.length||v.some(d=>typeof d!=='string'||!/^[a-z0-9.-]+$/i.test(d)||d.includes('..')))failures.push(`${label}.${k}`);if(k==='requestDomains')maxChunk=Math.max(maxChunk,v.length);}for(const k of ['resourceTypes','excludedResourceTypes'])if(k in r.condition){const v=r.condition[k];if(!Array.isArray(v)||!v.length||v.some(t=>!TYPES.has(t)))failures.push(`${label}.${k}`);}if(r.action?.type==='allowAllRequests'){const v=r.condition.resourceTypes;if(!Array.isArray(v)||v.some(t=>!['main_frame','sub_frame'].includes(t)))failures.push(`${label} allowAllRequests`);}}
 check(maxChunk<=100,`${f} domain chunks <=100`);return{count:rs.length,regex};}
async function readJson(f){try{return JSON.parse(await readFile(path.join(ROOT,f),'utf8'));}catch(e){failures.push(`${f}: ${e.message}`);return null;}}
async function exists(f){try{await access(path.join(ROOT,f));check(true,`${f} exists`);}catch{check(false,`${f} exists`);}}
function check(v,m,d=''){if(!v)failures.push(m+(d?' '+d.trim():''));}
function obj(v){return v!==null&&typeof v==='object'&&!Array.isArray(v);}
