feat(capture): flag-gated #xray:capture navigation marker (27 K.4)#182
feat(capture): flag-gated #xray:capture navigation marker (27 K.4)#182bryanmatthewsimonson wants to merge 4 commits into
Conversation
Live preflight of the K.1 skill against the real connector found both driving paths dead: claude-in-chrome cannot navigate to chrome-extension:// URLs (it forces an https prefix) AND refuses to attach its debugger to extension pages — so extension-page JS (chrome.tabs.sendMessage, IDB reads, xray:llm:suggest) is unreachable, synthetic keys do not fire command shortcuts, and OS keystrokes into browsers are tier-blocked. Navigation of ordinary pages is the only verb an agent has. This is the K.4 slice the decision doc anticipated "where browser-driving is impractical". - content script: on init, if location.hash === "#xray:capture" AND the new captureAutomation flag is on, strip the marker (history.replaceState; the URL normalizer would drop it from stored URLs regardless), wait 1.5s for dynamic pages to settle, and trigger the SAME UI.openReader() the toolbar path uses. The outcome lands on document.documentElement.dataset.xrayCaptured (ok / error / flag-off) so the driver verifies from ordinary page context — no extension access needed. - captureAutomation joins FLAGS_DEFAULTS (default OFF, pin-tested) + an Options → Advanced toggle whose copy says exactly what it gates: the marker only; toolbar/shortcut/menu capture stay unconditional; captures pages, nothing more — no accepting, no publishing. - SKILL.md rewritten against verified reality: marker-driven capture with DOM-stamp verification, frontier enumeration from a user-provided workspace-backup export (extension IDB unreachable), PDFs handed to the user as a ?pdf= reader link, and suggest-pass scouting marked not-runnable by the connector (the reader Suggest button is the human path). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
17 confirmed findings from the 21-agent review of the S.1..K.2 diff (one — the skill IDB-creation hazard — was already erased by the K.4 rewrite). Applied on the stack tip so every PR in the chain carries them at merge: MAJORS - llm-review blockedReason now gates finding/baseline rows on subject- entity acceptance (the F.3 identity join was order-dependent: accepting a finding before its subject entity silently landed it label-keyed — fragmented and unpublishable). Label-only subjects stay acceptable. - EDIT_FIELDS.baseline gains the subject_ref field (correcting a baseline subject via the label was cosmetic while the stale ref kept the identity keyspace); both subject field labels now state the precedence rule (ref wins; blank ref = label). - Re-running synthesis carries record.triage forward — content-derived keys make re-proposed pairs keep their status; without this every corpus-v2 re-run resurrected every dismissed proposal. Plus the pure partitionProposals seam extracted from the DOM render + tests (partition, unknown-status-stays-open, flipped-endpoint stability). MINORS - case-view local-chip pass: endpoints canonicalized ONCE into a ref-indexed map (was rows x all-links x canon re-parses — ~40x on a loaded workspace) and capped at 6 chips/row matching the published cap, with a "+N more local" disclosure. - Reduce prompt: art keys are for READING the index; anywhere the tool asks for article_hash, supply the FULL hash from `articles` — never an art key. - Finding modal: switching between two baselined subjects no longer carries a stale cross-subject baselineRef; editing a finding whose subject is no longer tagged falls to the custom slot with its own label instead of silently re-pointing at the first choice. - Role validation message interpolates the live ROLES (was a hardcoded pre-F.6 list). - Baseline delete clears baseline_ref on referencing findings (with the blast radius stated in the confirm); the dangling-ref render branch is now tested (generic no-weight tooltip, nothing fabricated). - 30062 parser role tolerance pinned (journalist parses; a foreign "prosecutor" passes through on read — the NIP_DRAFT MUST, tested). - DIGEST_CLAIM_CAP x art-key interaction pinned (the articles map derives from the SAME capped slice). - Stale entitiesById comment corrected (S.2 made a builder read it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
A trailing commit on this branch applies all 17 confirmed findings from the Phase-27 adversarial review (3 majors: the order-dependent F.3 identity join, the cosmetic baseline subject edit, and triage lost on re-run). Findings span the whole S/F/K chain; they ride here at the tip so the stack carries them at merge — details in the commit message. |
Found in the first live agent-driven capture run, on archive.is — several COVID-corpus frontier links are archive.is snapshots of paywalled reporting, and the marker fired exactly once there, then never again: SecurityError: Failed to execute replaceState on History: A history state object with URL https://d8w6fmknl1l6hl.archive.is/7gYpy cannot be created in a document with origin https://archive.is archive.is serves snapshots under a randomized subdomain and sets a <base href> to it. replaceState resolves a RELATIVE argument against the document base URI — not its origin — so the rewrite targeted another host and threw. The strip sat before UI.openReader() inside the same try, so a cosmetic address-bar tidy killed the capture it preceded. It appeared to work on the first hit only because the base tag had not been applied yet — which is also why that capture archived the interstitial instead of the article. - Rebuild the replacement URL ABSOLUTE from location.origin (a relative one is another site\s <base href> to redirect), and give the strip its OWN try/catch: cosmetics can never abort load-bearing work behind them. A failed strip now logs and continues; the URL normalizer drops fragments from stored URLs regardless. - A hash-only navigation (<url> → <url>#xray:capture) is a same-document navigation, so init() never re-ran and the marker silently no-oped; a hashchange listener now covers it. The marker branch is extracted to maybeAutomationCapture() and stamps "error" on any throw instead of staying silent. - SKILL.md: SPA settle times (~10s), the stamp-is-not-content rule (check title/length — an "ok" stamp on an interstitial is the real failure mode), re-capture via full navigation, console-first debugging, and an honest per-URL reporting section. - JOURNAL entry: the generalizable lesson is that a try-block wrapping a nice-to-have around a must-have inherits the nice-to-have\s failure modes. No unit test would have caught either bug — both need a real page with a cross-origin base tag. Found by running the tool for real. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Field-tested and fixed. The first live capture run (12 COVID-corpus articles) surfaced a real bug in this slice on Fixed here: the replacement URL is built absolute from Run result: 11/12 captured cleanly with zero paywalls — Proximal Origin, the WHO study, Rootclaim, the DEFUSE reporting, both Huanan market papers. Tests 1,802 green. |
Model picker (llm-prompts.js LLM_MODELS) gains Claude Fable 5 and
Claude Sonnet 5. The table is the single source of truth — the Options
dropdown renders it verbatim and every pass resolves through
resolveModel() — so the additions are the table rows plus the pin test
asserting every offered id resolves to itself (a picker entry that
silently fell back to the default would be a lie).
Payload compatibility verified rather than assumed: the passes send
only model/max_tokens/system/tools/tool_choice/messages — no
temperature, top_p, top_k, or thinking/budget_tokens, which are exactly
the params Fable 5 and Sonnet 5 reject with a 400. Both work unchanged.
Fable 5 needs no thinking config (thinking is always on there; an
explicit disabled would 400).
Refusal handling (the real gap): Fable 5 runs safety classifiers that
can DECLINE a request — HTTP 200, stop_reason "refusal", empty or
partial content. Only the lens pass handled that; the other six fell
through to extractToolInput() → null → "the model did not return a
structured extract", blaming malformed output for a guardrail and
sending the user hunting a bug that does not exist. A shared
refusalResult() helper now generalizes the lens pass's own §6 rule
("a guardrail firing is its own state") across suggest, audit, audit
module, corpus map, corpus reduce, and hypothesis edges — carrying the
safety category when present, stating it is not a key/network/X-Ray
problem, and naming the workaround (switch model in Options).
stop_details is treated as optional, per the API contract.
This matters concretely for the COVID-origins corpus: it is
bio-adjacent, and Fable 5's classifiers target research biology — a
refusal there is plausible and must read as a guardrail, not a bug.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Also on this branch: the LLM model picker now offers Claude Fable 5 ( Adding them was a two-row change — The real find: only the lens pass handled Tests 1,804 green. |
|
Landed on main via the #183 consolidation merge (the stacked-merge cascade closed/retargeted intermediates; every commit from this PR is in main). |
Summary
Phase-27 slice K.4 — the capture-automation enabler, built after live preflight found the K.1 skill's driving paths dead against the real connector. Stacked on #181.
What the live run proved
claude-in-chrome cannot navigate to
chrome-extension://URLs (forceshttps://) and refuses to attach its debugger to extension pages — so every extension-page surface the skill leaned on (tabs.sendMessage, IDB reads,xray:llm:suggest) is unreachable. Synthetic keys don't fire command shortcuts; OS keystrokes into browsers are tier-blocked. Navigation of ordinary pages is the only verb an agent has.The enabler
Opening
<url>#xray:captureon any ordinary page triggers the sameUI.openReader()capture the toolbar button fires — iff the newcaptureAutomationflag is on (default OFF, pin-tested; Options → Advanced toggle whose copy says exactly what it gates). The marker is stripped before capture; the outcome is stamped on the page DOM (data-xray-captured: ok/error/flag-off) so the driver verifies without extension access. Toolbar/shortcut/menu capture paths are untouched and unconditional.Skill rewritten against reality
Marker-driven capture with DOM-stamp verification and per-URL pacing; frontier enumeration from a user-provided workspace-backup export; PDFs handed to the user as a
?pdf=reader link; suggest-pass scouting marked not-runnable (the reader's Suggest button is the human path). Connector limits documented at the top so future runs don't rediscover them.Tests: 1,797 green (1 new pin) · build clean · lint 0 errors.
🤖 Generated with Claude Code