← Commit history

Release 0.1.77: visible browser challenge evidence and sourcing guidance

John Lauer ·8ee8f9a355 · ·parent 98f492d
7 files changed +74−17
Cargo.lock+10−10
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-aiflow"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-analyze",  "aiflow-board",@@ -24,7 +24,7 @@ dependencies = [  [[package]] name = "aiflow-analyze"-version = "0.1.76"+version = "0.1.77" dependencies = [  "serde",  "serde_json",@@ -33,7 +33,7 @@ dependencies = [  [[package]] name = "aiflow-board"-version = "0.1.76"+version = "0.1.77" dependencies = [  "roxmltree",  "serde",@@ -42,7 +42,7 @@ dependencies = [  [[package]] name = "aiflow-bridge"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "serde",@@ -51,7 +51,7 @@ dependencies = [  [[package]] name = "aiflow-copper"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -61,7 +61,7 @@ dependencies = [  [[package]] name = "aiflow-grid"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "serde",@@ -70,7 +70,7 @@ dependencies = [  [[package]] name = "aiflow-place"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "serde",@@ -79,7 +79,7 @@ dependencies = [  [[package]] name = "aiflow-pours"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "aiflow-copper",@@ -89,7 +89,7 @@ dependencies = [  [[package]] name = "aiflow-router"-version = "0.1.76"+version = "0.1.77" dependencies = [  "aiflow-board",  "aiflow-grid",@@ -99,7 +99,7 @@ dependencies = [  [[package]] name = "aiflow-run"-version = "0.1.76"+version = "0.1.77" dependencies = [  "serde",  "serde_json",
Cargo.toml+1−1
@@ -14,7 +14,7 @@ members = [ ]  [workspace.package]-version = "0.1.76"+version = "0.1.77" edition = "2021" license = "MIT" repository = "https://wiki.adom.inc/adom/adom-aiflow"
bin/adom-aiflow
⋯ 1 unchanged line ⋯
crates/adom-aiflow/src/browser_access.rs+51−3
@@ -11,19 +11,37 @@ pub fn classify(v: &Value, lane: &str) -> Result<Value, String> {     let text = v["text"]         .as_str()         .ok_or("page evidence needs text string")?;+    let visual_text = match v.get("visualText") {+        None => "",+        Some(value) => value+            .as_str()+            .ok_or("visualText must be a string transcribed from the visible page")?,+    };     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 visual_text = visual_text.to_lowercase();+    // A challenge overlay can disagree with product text in the underlying DOM.+    // Accept a caller's screenshot transcription as additional evidence, never+    // as proof that an asset was downloaded or that access is authorized.+    let visible_challenge = |value: &str| {+        value.trim_start().starts_with("verify you are human")+            || value.contains("slide right to secure your access")+            || value.trim_start().starts_with("slide to verify")+    };     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")+        || visual_text.trim_start().starts_with("access denied")+        || visual_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");+        || visible_challenge(&text)+        || visible_challenge(&visual_text);     let missing = v["httpStatus"] == 404         || title.contains("page not found")         || text.contains("sorry, we can’t find the page")@@ -39,7 +57,7 @@ pub fn classify(v: &Value, lane: &str) -> Result<Value, String> {         "not_found"     } else if login {         "login_required"-    } else if text.trim().is_empty() {+    } else if text.trim().is_empty() && visual_text.trim().is_empty() {         "loading_or_empty"     } else {         "content_present"@@ -50,7 +68,7 @@ pub fn classify(v: &Value, lane: &str) -> Result<Value, String> {         ("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."),+        ("human_verification", _) => ("human_assistance", "A human verification challenge is visible. If this page is required, preserve the owned window and request human assistance; never solve or bypass it automatically. Otherwise continue through the official vendor API/BOM tool or manufacturer source. Underlying DOM product text is not proof that the visible challenge cleared."),         ("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."),@@ -146,4 +164,34 @@ mod tests {         p["httpStatus"] = json!(403);         assert_eq!(classify(&p, "http").unwrap()["next"], "pup");     }+    #[test]+    fn mouser_slider_is_a_challenge_not_generic_denial() {+        let p = page("Mouser Verification Required", "Verification Required. Slide right to secure your access. Access denied because you are using automation tools to browse.");+        let result = classify(&p, "nb").unwrap();+        assert_eq!(result["state"], "human_verification");+        assert_eq!(result["next"], "human_assistance");+        assert_eq!(result["productVerified"], false);+    }+    #[test]+    fn visible_challenge_takes_priority_over_underlying_product() {+        let mut p = page("680-125220 | Mouser", "Product specifications and price");+        p["visualText"] = json!("Verification Required. Slide right to secure your access.");+        assert_eq!(classify(&p, "nb").unwrap()["state"], "human_verification");+        p["visualText"] = json!("Access Denied");+        assert_eq!(classify(&p, "nb").unwrap()["state"], "access_denied");+    }+    #[test]+    fn generic_verification_word_does_not_imply_bot_challenge() {+        let p = page(+            "Product verification guide",+            "Verification required before assembly.",+        );+        assert_eq!(classify(&p, "nb").unwrap()["state"], "content_present");+    }+    #[test]+    fn invalid_visual_evidence_is_rejected() {+        let mut p = page("Product", "Specifications");+        p["visualText"] = json!(true);+        assert!(classify(&p, "nb").is_err());+    } }
docs/release-0.1.77.mdadded+9
@@ -0,0 +1,9 @@+# AIFlow 0.1.77++Browser evidence now recognizes Mouser's slider verification text and accepts optional `visualText` transcribed from a screenshot. A visible challenge takes precedence over underlying product text; neither a successful bridge call nor hidden DOM content verifies usable access. If the challenged page is unnecessary, use an official API or manufacturer source; human verification is never automated.++Observed on 9 October while sourcing the electronic-load heatsink: Pup denied the product page. Native Browser's screenshot and recording still showed verification while DOM evaluation returned product content. The official Parts Search API returned the listing. This release changes classification and guidance only; it does not drive browsers, clear challenges or guarantee CAD availability.++Four regression cases cover slider classification, visual/DOM disagreement, non-challenge verification wording and invalid visual evidence. Existing commands and engineering gates are preserved.++Validation: production `cargo check --workspace --bins`, optimized CLI build, workspace tests, component/widget/dashboard workflows, and mask/obstacle geometry checks passed. All 50 existing CLI commands remain present. The recorded native observation classifies as `human_verification`; browser navigation itself was not changed by this release. Base: `98f492d70a4777580b985e9390d3956364690684`.
package.json+1−1
@@ -1,7 +1,7 @@ {   "slug": "adom-aiflow",   "type": "app",-  "version": "0.1.76",+  "version": "0.1.77",   "title": "AI Flow",   "description": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. One Rust binary with a crate per step (placement helpers, a grid router with Kelvin taps, pours with keepouts, KiCad's DRC gate, live landing through the KiCad Bridge, copper measurement, current and thermal analysis) and a finish line that refuses an unfinished board. Every command answers with hints for the AI; every turn, its thinking time and every rework loop go into run.jsonl, so Claude, Codex and any other engine are compared on the same flow. KiCad today; Altium, Fusion and Adom's own web apps next.",   "summary": "Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board. The AI thinks its way from placement through routing, pours, current and thermal analysis to a delivered video; the binary does the fast, deterministic parts of every step, hands back hints, and keeps a ledger of every turn, every return to an earlier step, and the clock from the prompt to done.",
skills/aiflow-sourcing/SKILL.md+2−2
@@ -125,7 +125,7 @@ The shared step2glb service comes first, but it is often busy. When a STEP needs  ## 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:+Save a minimal page observation as JSON: `url`, `title`, `text` (visible body text), optionally `httpStatus`, `loginPage` and `visualText` (a concise transcription from a screenshot). Exclude account details, cookies and credentials. Run:  ```bash adom-aiflow browser-check --lane pup --input page-observation.json@@ -133,7 +133,7 @@ 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.+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 if access to that page is necessary; otherwise continue with the official API or manufacturer. Never automate the challenge. Compare the screenshot with DOM text: a visible verification overlay may coexist with underlying product content. Include the visible challenge in `visualText`; do not report successful access merely because evaluation returned product text.  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.