← All Pull Requests

Detect sourcing access failures and require off-board component pages #32

Closed opened by John Lauer
Merges browser-recovery-component-coverage → main

Sourcing could stop at a Mouser denial even though the existing native-browser lane could reach the product, and component guidance contradicted itself about required wiki pages.

Adds browser-check --lane http|pup|nb --input observation.json: classifies saved visible page evidence and emits a structured next step. Transport success is not page success; denial, human verification, rate limit, missing page, empty/loading and content are distinct. No browser automation, profile mutation, credentials or implicit product verification. Native denial stops retries. Human challenges require human assistance and rate limits are respected.

Resolves the component-page optional/required contradiction in both main skill copies and explicitly includes off-board fans, heatsinks, sensors and connectors in the full assembly BOM. Requires symbol, mechanical footprint where appropriate, STEP/GLB, thumbnail, provenance and visual verification or an explicit incomplete/inapplicable record. It does not falsely call the existing PCB register an automatic assembly gate.

Base: 1d4f72b3df9db378e7b18b3b330003308c5535dc (0.1.66). Six focused files, no removed commands, no version or installed-runtime change. Review preflight passed, contextual branch diff reviewed. cargo check --workspace --bins, cargo build -p adom-aiflow, and workspace tests passed (including 10 browser-classifier cases; one preexisting ignored test). The CLI classified actual captured Pup denial and native product evidence correctly.

Native acceptance: AdomLapper, nb extension 0.2.15. Pup displayed Access Denied for MF40200V1-1000U-G99. Native Chrome showed a normal missing-page response to that URL; normal exact-MPN vendor search reached the actual product with stock, price and manufacturer CAD link. Downloaded fan ZIP and datasheet; short clips captured. This is evidence for a recovery recommendation, not a guarantee that native browsing always succeeds.

Maintainer follow-up: review/merge, publish a package, install and verify deployed availability. No release or live-package update claimed by this PR.

Diff Skip to comments (2)

README.md+4
@@ -189,3 +189,7 @@ Latest maintenance: [0.1.28 lifecycle and native IPC readiness](docs/release-0.1 Adom Fields 0.4.0 adds conducting heatsink blocks, copper thickness per layer and a Signals view for plane-overlap capacitance, differential-pair skew, reference coverage and microstrip estimates. AI Flow hints direct the AI to use explicit assembly and stackup data and keep unsupported cases visible. [Fields inputs, sources, limits and fixture](https://wiki.adom.inc/adom/adom-fields/files/docs/heatsinks-and-signals.md).  Native pour integration: [owned-zone transactions and asynchronous refill/readback](docs/native-zone-landing.md).++## Browser access during sourcing++`adom-aiflow browser-check --lane pup --input page-observation.json` distinguishes access denial from missing or loading pages and recommends an owned native-browser session when appropriate. It checks saved page evidence and does not navigate or claim the product was verified. See [the sourcing skill](skills/aiflow-sourcing/SKILL.md#detect-browser-access-failures-before-abandoning-a-source).
SKILL.md+1−1
@@ -244,7 +244,7 @@ MPN marking is on by default for ICs: every IC's model carries its laser-etched  ## Provenance is required for shared component assets -Creating a new global component page is optional; truthful per-file provenance is required when publishing or improving one. Reuse and improve existing pages first. Keep board-only definitions in the board project. Add a readable provenance section and machine-readable asset record, linked prominently from the component README (including HTML READMEs without removing their existing content).+Every selected, identified, reusable manufacturer part needs an exact component wiki page: reuse and improve an existing page after duplicate checking, or create a missing one. This includes off-board fans, heatsinks, sensors and connectors, even when they are absent from the PCB footprint inventory. An unresolved identity or restricted asset must be recorded as an explicit incomplete item; never create a guessed component or claim complete coverage. Truthful per-file provenance is required when publishing or improving a page. Reuse and improve existing pages first. Keep board-only definitions in the board project. Add a readable provenance section and machine-readable asset record, linked prominently from the component README (including HTML READMEs without removing their existing content).  For every symbol, footprint, STEP, GLB and optional marked variant, record the source URL/file/revision and retrieval time, source and output SHA-256, authoring classification (manufacturer-supplied, source-derived, AI-created, or unknown), generator and version, parameters, units, transforms, modifications, redistribution evidence, checks performed and unresolved limits. A hash proves file identity, not quality or permission. Existing wiki availability alone does not establish upstream authorship or rights. 
crates/adom-aiflow/src/browser_access.rsadded+149
@@ -0,0 +1,149 @@+//! Classifies a caller-supplied page observation, not bridge transport success.+use serde_json::{json, Value};++pub fn classify(v: &Value, lane: &str) -> Result<Value, String> {+    let url = v["url"]+        .as_str()+        .ok_or("page evidence needs url, title and text strings")?;+    let title = v["title"]+        .as_str()+        .ok_or("page evidence needs title string")?;+    let text = v["text"]+        .as_str()+        .ok_or("page evidence needs text string")?;+    if !url.starts_with("https://") && !url.starts_with("http://") {+        return Err("url must be HTTP(S)".into());+    }+    let title = title.to_lowercase();+    let text = text.to_lowercase();+    let denied = title.contains("access denied")+        || title.contains("access to this page has been denied")+        || text.trim_start().starts_with("access denied")+        || text.contains("using automation tools to browse")+        || v["httpStatus"] == 403;+    let challenge = title.contains("just a moment")+        || title.contains("verify you are human")+        || text.trim_start().starts_with("verify you are human");+    let missing = v["httpStatus"] == 404+        || title.contains("page not found")+        || text.contains("sorry, we can’t find the page")+        || text.contains("sorry, we can't find the page");+    let login = v["loginPage"] == true;+    let state = if challenge {+        "human_verification"+    } else if v["httpStatus"] == 429 {+        "rate_limited"+    } else if denied {+        "access_denied"+    } else if missing {+        "not_found"+    } else if login {+        "login_required"+    } else if text.trim().is_empty() {+        "loading_or_empty"+    } else {+        "content_present"+    };+    let (next, hint) = match (state, lane) {+        ("login_required", "http") => ("nb", "Use the normal authorized login in an owned native-browser window with the appropriate existing profile."),+        ("access_denied", "http") => ("pup", "After one HTTP failure, inspect the public page in an owned background Pup window; for login use nb-bridge with the appropriate existing profile."),+        ("access_denied" | "login_required", "pup") => ("nb", "Use adom-native-browser: nbrowser_readiness, nbrowser_profiles, then nbrowser_open_window with a new sessionId, thread, purpose and appropriate work profile. Keep it in the background. Never reuse a user tab, import cookies, change network identity or disable protections. Offer installation only if needed. Verify the resulting page before claiming success."),+        ("access_denied", "nb") => ("official_alternative", "Native browser is also denied. Stop retries; use the vendor Search API/BOM tool or manufacturer source, or request human assistance. Do not evade the denial."),+        ("rate_limited", _) => ("wait_or_official_api", "Respect the rate limit and Retry-After. Do not rotate browsers or profiles to evade it; use the documented vendor API when authorized."),+        ("human_verification", _) => ("human_assistance", "A human verification challenge is present. Preserve the owned window and request human assistance; do not solve or bypass it automatically."),+        ("not_found", _) => ("vendor_search", "This is a missing page, not an access denial. Use the site's normal part search once and verify the exact MPN and canonical product URL; do not invent a product URL."),+        ("loading_or_empty", _) => ("inspect_after_load", "Empty content is not success or denial. Wait for navigation completion and inspect again with a bounded wait."),+        ("login_required", "nb") => ("authorized_login", "Use the site's normal authorized sign-in flow in the owned native-browser window; do not expose credentials."),+        _ => ("verify_product", "Content exists, but this does not prove the requested product or CAD asset. Verify exact MPN, stock/date and CAD links; validate downloaded file type, hash, dimensions and provenance."),+    };+    Ok(+        json!({"schema":"adom/browser-access@1","state":state,"lane":lane,"next":next,"hint":hint,"productVerified":false,"automaticNavigation":false}),+    )+}++#[cfg(test)]+mod tests {+    use super::*;+    fn page(title: &str, text: &str) -> Value {+        json!({"url":"https://www.mouser.com/example","title":title,"text":text})+    }+    #[test]+    fn denied_pup_recommends_nb() {+        let r = classify(+            &page("Access to this page has been denied.", "Access Denied"),+            "pup",+        )+        .unwrap();+        assert_eq!(r["next"], "nb");+    }+    #[test]+    fn denied_nb_stops() {+        assert_eq!(+            classify(&page("Access Denied", ""), "nb").unwrap()["next"],+            "official_alternative"+        );+    }+    #[test]+    fn missing_is_not_denied() {+        assert_eq!(+            classify(+                &page(+                    "Mouser",+                    "Sorry, we can’t find the page you’re looking for."+                ),+                "nb"+            )+            .unwrap()["next"],+            "vendor_search"+        );+    }+    #[test]+    fn bridge_success_is_not_page_success() {+        assert!(classify(&json!({"success":true}), "pup").is_err());+    }+    #[test]+    fn blank_is_not_success() {+        assert_eq!(+            classify(&page("", ""), "nb").unwrap()["state"],+            "loading_or_empty"+        );+    }+    #[test]+    fn challenge_requires_human() {+        assert_eq!(+            classify(&page("Just a moment...", ""), "pup").unwrap()["next"],+            "human_assistance"+        );+    }+    #[test]+    fn product_text_not_auto_verified() {+        let r = classify(+            &page(+                "Fan | Mouser",+                "Product description includes access denied in a troubleshooting guide.",+            ),+            "nb",+        )+        .unwrap();+        assert_eq!(r["state"], "content_present");+        assert_eq!(r["productVerified"], false);+    }+    #[test]+    fn rate_limit_does_not_switch_browser() {+        let mut p = page("", "");+        p["httpStatus"] = json!(429);+        assert_eq!(classify(&p, "pup").unwrap()["next"], "wait_or_official_api");+    }+    #[test]+    fn challenge_takes_priority_over_403() {+        let mut p = page("Verify you are human", "");+        p["httpStatus"] = json!(403);+        assert_eq!(classify(&p, "pup").unwrap()["next"], "human_assistance");+    }+    #[test]+    fn http_status_detected() {+        let mut p = page("", "");+        p["httpStatus"] = json!(403);+        assert_eq!(classify(&p, "http").unwrap()["next"], "pup");+    }+}
crates/adom-aiflow/src/main.rs+8
@@ -6,6 +6,7 @@ mod testplan; mod switchnode; mod thermalvias; mod outline;+mod browser_access;  /// The timeline dashboard (tools/timeline/index.html): it polls timeline.json beside it. const TIMELINE_PAGE: &str = include_str!("../../../tools/timeline/index.html");@@ -48,6 +49,8 @@ struct Cli {  #[derive(Subcommand)] enum Cmd {+    /// Inspect saved browser page evidence for access denial, login, missing page or usable content. Recommendations only; never drives a browser or changes profiles.+    BrowserCheck { #[arg(long)] input: PathBuf, #[arg(long, value_parser=["http", "pup", "nb"])] lane: String },     /// Measure external work as a real running command, including its output and exit status. Use: exec -- <program> <args>     Exec { #[arg(required = true, trailing_var_arg = true, allow_hyphen_values = true)] command: Vec<String> },     SilkscreenLayout { #[arg(long)] input: PathBuf, #[arg(long)] out: PathBuf, #[arg(long)] events: Option<PathBuf> },@@ -1970,6 +1973,11 @@ fn main() {         }     }     match &cli.cmd {+        Cmd::BrowserCheck { input, lane } => {+            let data: Value = serde_json::from_slice(&std::fs::read(input).unwrap_or_else(|e| err(&e.to_string(), &[]))).unwrap_or_else(|e| err(&e.to_string(), &[]));+            let result = browser_access::classify(&data, lane).unwrap_or_else(|e| err(&e, &[]));+            println!("{}", serde_json::to_string_pretty(&result).unwrap());+        }         Cmd::Exec { command } => {             thread(&cli); let mut r=load_run(&cli);             r.log("external-start",json!({"program":command[0],"args":&command[1..]}));
skills/adom-aiflow/SKILL.md+1−1
@@ -239,7 +239,7 @@ MPN marking is on by default for ICs: every IC's model carries its laser-etched  ## Provenance is required for shared component assets -Creating a new global component page is optional; truthful per-file provenance is required when publishing or improving one. Reuse and improve existing pages first. Keep board-only definitions in the board project. Add a readable provenance section and machine-readable asset record, linked prominently from the component README (including HTML READMEs without removing their existing content).+Every selected, identified, reusable manufacturer part needs an exact component wiki page: reuse and improve an existing page after duplicate checking, or create a missing one. This includes off-board fans, heatsinks, sensors and connectors, even when they are absent from the PCB footprint inventory. An unresolved identity or restricted asset must be recorded as an explicit incomplete item; never create a guessed component or claim complete coverage. Truthful per-file provenance is required when publishing or improving a page. Reuse and improve existing pages first. Keep board-only definitions in the board project. Add a readable provenance section and machine-readable asset record, linked prominently from the component README (including HTML READMEs without removing their existing content).  For every symbol, footprint, STEP, GLB and optional marked variant, record the source URL/file/revision and retrieval time, source and output SHA-256, authoring classification (manufacturer-supplied, source-derived, AI-created, or unknown), generator and version, parameters, units, transforms, modifications, redistribution evidence, checks performed and unresolved limits. A hash proves file identity, not quality or permission. Existing wiki availability alone does not establish upstream authorship or rights. 
skills/aiflow-sourcing/SKILL.md+22
@@ -122,3 +122,25 @@ Whenever a component page is created or its model changes (here, or through mole ## Processing STEP files locally  The shared step2glb service comes first, but it is often busy. When a STEP needs local work (turning, merging, reading colours and names through XCAF), suggest the lightweight `cadquery-ocp-novtk` build of OpenCascade's Python bindings in its own venv: about 0.4 GB, against about 1.3 GB for the full `cadquery-ocp`, whose VTK visualisation library STEP processing never uses. Ask the user before installing it, and say how to remove it (delete the venv). `adom-aiflow-etch` no longer needs it: the planner, the etch, the gate and the outline work on the shared service with plain python3 (only `--legacy` uses this venv).++## Detect browser access failures before abandoning a source++Save a minimal page observation as JSON: `url`, `title`, `text` (visible body text), optionally `httpStatus` and `loginPage`. Exclude account details, cookies and credentials. Run:++```bash+adom-aiflow browser-check --lane pup --input page-observation.json+```++The command classifies the observed page and recommends a next step. It never opens a browser or claims that the product was verified. A successful bridge call or HTTP 200 can still contain an Access Denied page. Empty/loading, missing-page, login and human-verification states are distinct.++After Pup denial, read adom-native-browser and check `nbrowser_readiness` then `nbrowser_profiles`; use a new owned background window in the appropriate existing work profile. Do not touch user tabs or switch disabled extensions on. If native browsing also fails, stop retries and use official vendor APIs/BOM tools or the manufacturer. Human-verification challenges require human assistance.++Verified example, 2026-10-08: Pup showed Mouser Access Denied for MF40200V1-1000U-G99. Native Chrome reached a normal missing-page response for the same incomplete URL. Mouser's normal exact-MPN search then opened the actual product page and its manufacturer-hosted 3D ZIP. The recovered model is a separate provenance/dimensional check, not proof of a qualified cooling design. Record short clips of the observed states when the user requested a process video; label a later walkthrough as such rather than presenting it as the original discovery.++## Required component pages include off-board hardware++Before calling sourcing complete, reconcile the full assembly BOM (not only PCB references) against exact component wiki pages. Fans, heatsinks, cables, sensors and connectors count. Duplicate-check first and improve the exact existing page; create a missing reusable part page. Link every selected MPN in the assembly register. Bare copper, unidentified placeholders and project-only assemblies remain in the project rather than becoming false catalog parts.++For each page track symbol, footprint or mechanical mounting pattern, plain STEP, GLB, 3D chip/component thumbnail, source drawing, provenance and verification evidence. An externally wired fan needs a power/ground/tach symbol and documented lead mapping, plus its mechanical mounting pattern; do not invent PCB electrical pads or pretend a separately chosen connector is supplied with the fan. Keep connector and fan identities distinct. Label inapplicable fields with a reason and missing assets as incomplete. CAD existence is not geometric verification; compare dimensions, mounting holes, wire exit, axes and visible geometry with the exact part drawing and photos. Simplified models are not full exact digital twins.++Publish permitted assets with portable paths, an asset manifest and source/derived hashes, and inspect the rendered component page and native EDA binding. Restricted vendor files stay private references with source links; independently authored geometry must disclose its assumptions and simplifications. Component publication and verification are required workflow work, not an automatic certification conferred by `components` or `browser-check`.

Comments

John Lauer ·

Added state-aware component asset guidance and probing handoff instructions. Fan prototype: https://wiki.adom.inc/adom/mf40200v1-1000u-g99 . Widget PR: https://wiki.adom.inc/adom/component-widgets/prs/1 . Wiki skill PR: https://wiki.adom.inc/adom/adom-wiki-skillpack/prs/8 . Probing handoff: https://wiki.adom.inc/adom/adom-aiflow-probing/issues/1 . No shared release claimed.

John Lauer ·

John explicitly requested direct check-in so Colby can use the changes today, while preserving the concurrent Fusion work. Applied contextually onto the latest 0.1.68 HEAD (99058efe1045f5a7052d0f837a6d6d57a767968b), checked in directly at 9de89747a8799fa6c0c203c4fc5385a8d130ffc1, and published as 0.1.69/latest (publication commit 5e69d34ddbff4f018adfbcba2aa69e9fb39332a1).

Combined production check/build pass; 102 workspace tests pass with one existing ignored. Existing CLI commands are preserved. Closing this review proposal because its changes are now on main; this was a direct application, not a PR merge. Board-level native acceptance and simulator integration remain separate from this software release.

Log in to comment.