diff --git a/docs/CLI.md b/docs/CLI.md index 5c9aee41..2e506b59 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -479,7 +479,7 @@ Flags: Notes: - Prefer explicit public source-family examples such as `--sources web,community`; use `--source-selection` only to explain selector semantics. - Use `--browser-mode extension` when X, Threads, Facebook, Reddit, or another signed-in social provider needs an existing relay-backed browser session; use `managed` for reproducible no-auth reruns. -- In the current contract, `auto` and `all` both stay inside the public topical families (`web`, `community`, `social`), but neither value guarantees reliability. +- `--source-selection auto` is public-first and resolves to `web,community`. Use `--source-selection all`, `--source-selection social`, or explicit `--sources ...social...` only when social evidence is intentional. - Add shopping only with `--source-selection shopping` or explicit `--sources ...shopping...` when the task is deliberately commercial. - Successful research artifact bundles include `report.md`, `summary.md`, `records.json`, `context.json`, `meta.json`, and `bundle-manifest.json`. - `report.md` is the primary deterministic evidence briefing. It renders evidence gate status, final answer, claim map, theme synthesis, source agreement or disagreement, confidence by claim, limitations, recommendations, and an evidence appendix from accepted records and metadata. @@ -515,9 +515,9 @@ Flags: Notes: - Use explicit providers plus `--browser-mode managed` for the most reproducible live reruns. - `deals.md` is the primary deterministic buying brief. It renders `# Shopping Buying Brief`, a Buying Readiness Gate, Recommendation, Best Candidate Offers, Market Baseline, Warnings and Constraints, Excluded or Constrained Offers, and an Evidence Appendix. -- Buying readiness is `pass`, `partial`, or `fail`. `pass` allows bounded buying guidance, `partial` means use the output as a constrained shortlist, and `fail` means no confident purchase recommendation is allowed from the current evidence. +- Buying readiness is `pass`, `partial`, or `fail`. `pass` allows bounded buying guidance, `partial` means use the output as a constrained shortlist, and `fail` means no confident purchase recommendation is allowed from the current evidence. Inspect `buyingReadiness.status` in JSON and completion text; command success only means the workflow completed transport and artifact work. - Raw audit surfaces stay separate: `offers.json` preserves structured offers, `comparison.csv` preserves the tabular comparison with appended currency and total-status audit fields, `meta.json` preserves workflow diagnostics, and `deals-context.json` preserves `query`, report-derived `highlights`, raw `offers`, and `meta` for agent handoff. -- `compact` and `context` modes summarize the same report guidance as `deals.md`; `json` mode still returns raw offers and meta. +- `compact` and `context` modes summarize the same report guidance as `deals.md`; `json` mode returns raw offers, `buyingReadiness`, and meta. - Expect warnings for stale, inferred, or missing price freshness; unknown or out-of-stock availability; weak query relevance; suspicious titles; duplicate same-title or same-product pressure; workflow alerts; and advisory region handling. - Treat `--region` as advisory unless `meta.selection.region_authoritative=true`. If `meta.alerts` includes `reasonCode=region_unenforced`, do not present the output as a trustworthy regional comparison. The alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning. - The Market Baseline section is computed only from deterministic same-currency evidence. If sample size or currency coverage is insufficient, the report says `market baseline unavailable` instead of inventing savings. @@ -554,7 +554,7 @@ Notes: - Successful product-video runs write `manifest.json`, `product.json`, `pricing.json`, `copy.md`, `features.md`, `presentation-readiness.json`, `raw/source-record.json`, and `bundle-manifest.json`. - `--output-dir` is a workflow root, not the final bundle directory. Omit it for routine runs and inspect the returned `artifact_path` first; when omitted, persisted artifacts land under `.opendevbrowser/product-video/`. If a wrapper must pass an explicit root, prefer `--output-dir .opendevbrowser`. - `presentation-readiness.json` is the audit surface for `presentationReadiness`, `productVideoReadiness`, selected and original record ids, bounded candidate summaries, promoted claims, rejected candidate summaries, evidence references, and compact counts. -- `manifest.readiness.presentation` and `manifest.readiness.productVideo` are the manifest production gates. `product.json.presentationReadiness`, `product.json.productVideoReadiness`, returned `product.presentationReadiness`, and returned `product.productVideoReadiness` mirror the product-facing gates, while JSON workflow output exposes `meta.presentationReadiness` and `meta.productVideoReadiness`. +- `manifest.readiness.presentation` and `manifest.readiness.productVideo` are the manifest production gates. `product.json.presentationReadiness`, `product.json.productVideoReadiness`, returned `product.presentationReadiness`, and returned `product.productVideoReadiness` mirror the product-facing gates, while JSON workflow output exposes `meta.presentationReadiness` and `meta.productVideoReadiness`. CLI completion text also includes both readiness statuses when available, so transport success is not confused with production authority. - Readiness status is `pass`, `partial`, or `fail`. `pass` can feed a normal production brief after human evidence review. `partial` is a constrained draft and must carry warnings plus reason codes. `fail` blocks production use. - Raw evidence stays preserved under `raw/source-record.json` for audit and debugging. Do not treat raw marketplace, site navigation, catalog, seller, shipping, condition, or returns text as verified copy unless it is promoted through `presentation-readiness.json.promotedClaims[]`. - `copy.md` and `features.md` are not automatically verified production input. They are production-safe only when readiness permits and the claims evidence map ties each claim to captured or structured evidence. @@ -600,7 +600,7 @@ Notes: - Repeat `--url` for multiple inspiration sources. There is no `--urls` alias. - `harvest` merges explicit URLs before discovered URLs, trims and de-duplicates references, and stores rejected reference diagnostics in generated metadata. - Query-based Pinterest harvests become product-ready only through query-discovered canonical `/pin/{id}/` references plus manifest-backed first-party pin-media bytes. `discovery-diagnostics.json` records accepted and rejected URL counts, blocker diagnostics, and recovery actions; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence. -- `social/pinterest` is a browser-native site recipe. For non-Google logged-in Pinterest search, prefer a dedicated managed headed profile with `--profile `, `--use-cookies`, and `--cookie-policy required`; use extension `/ops` when you need live active-tab reuse. Registry-backed explicit CDP profiles are available for browser/session primitives through `cdp-profile start` plus `connect --profile`, but provider workflows do not yet expose an explicit-CDP transport selector. Compatible Pinterest URL recovery can run as `--provider social/pinterest --url ` without `--query`; generic provider plus URL recovery without a query remains rejected. Use one canonical `/pin/{id}/` URL per harvest when validating design-ready pin media. The workflow must recover session evidence first when Pinterest returns login, challenge, empty-grid, or search-shell states. +- `social/pinterest` is a browser-native site recipe. Default Pinterest harvest applies extension auth defaults only when `extensionAuthReady` is current and the caller has not set incompatible auth transport or cookie settings. Explicit `browserMode`, `profile`, `useCookies`, `cookiePolicyOverride`, or `challengeAutomationMode` values other than `browser_with_helper` opt out; explicit `challengeAutomationMode=browser_with_helper` is compatible and may merge with the implicit extension, cookies, and required cookie policy defaults. For managed recovery, use a dedicated headed profile with `--profile `, `--use-cookies`, and `--cookie-policy required`. Registry-backed explicit CDP profiles are available for browser/session primitives through `cdp-profile start` plus `connect --profile`, but provider workflows do not yet expose an explicit-CDP transport selector. Compatible Pinterest URL recovery can run as `--provider social/pinterest --url ` without `--query`; generic provider plus URL recovery without a query remains rejected. Use one canonical `/pin/{id}/` URL per harvest when validating design-ready pin media. The workflow must recover session evidence first when Pinterest returns login, challenge, empty-grid, or search-shell states. - Canonical Pinterest pin-media harvest opens the exact canonical pin in the active managed or extension workflow session before extracting persisted first-party bytes. Extension `/ops` remains the best live-tab reuse path, but it is no longer the only intended logged-in Pinterest lane. - Browser-native site recipes do not silently widen scope to unrelated providers. If fallback to broad web sources is desired, ask the user or rerun with an explicit `--provider web/default`. - `--include-prototype-guidance` appends prototype structure guidance to the generated design contract output. @@ -619,7 +619,7 @@ Notes: - Workflow outputs can include typed `nextStepGuidance` with `readiness`, `reasonCode`, `primaryAction`, command examples, `paramsExamples`, `validationChecks`, `fallbackPolicy`, and `doNotProceedIf` blockers. - Harvest outputs separate operational completion, guidance readiness, and product readiness. `success:true` or `ok:true` means the command completed and wrote artifacts; top-level `ready` is product-ready only when `productSuccess=true`, `artifactAuthority=product_ready`, and `evidenceAuthority` is non-diagnostic. `readiness` and `guidanceReadiness` expose the next-step guidance state. - Treat product readiness as the gate between artifact completion and design readiness. Continue to Canvas only when top-level `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, ranked references are non-empty, manifest-backed authority evidence exists, and no `doNotProceedIf` condition applies. For canonical Pinterest pin-media harvests, Canvas continuation requires `evidenceAuthority=pin_media_ready` and manifest-backed `pin-media-index.json`; `snapshot_ready` and `motion_ready` are not substitutes for pin-media readiness. -- Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when their first-party pin-media artifact is captured, persisted, present in its manifest-backed index, and free of blocking warnings. Screenshot and screencast artifacts can inform diagnostics or motion design, but they do not satisfy required Pinterest pin-media readiness. The exact `login_or_challenge_state` and strict byte-backed `interface_chrome_shell` diagnostics are non-blocking only for trusted first-party manifest-backed pin-media bytes; broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers still demote readiness unless query-discovered canonical pins also produce complete pin-media authority. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure. `pin-media-index.json` remains Pinterest authority, `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority. Multimodal agents reason over those artifacts directly. +- Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when their first-party pin-media artifact is captured, persisted, present in its manifest-backed index, and free of blocking warnings. Screenshot and screencast artifacts can inform diagnostics or motion design, but they do not satisfy required Pinterest pin-media readiness. `login_or_challenge_state`, broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers demote readiness; strict byte-backed `interface_chrome_shell` diagnostics remain non-blocking only for trusted first-party manifest-backed pin-media bytes. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure. `pin-media-index.json` remains Pinterest authority, `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority. Multimodal agents reason over those artifacts directly. - CLI completion text includes `guidanceReadiness= productSuccess=false artifactAuthority= evidenceAuthority=` when guidance readiness exists but product readiness is false. Product-ready runs can still emit `readiness=`. - For `needs_recovery`, `blocked`, or `diagnostic_only`, follow the primary recovery action first. Common blockers are zero references, empty ranked references, failed required screenshot, screencast, or pin-media evidence, provider unavailability, login or challenge screens, and diagnostic-only captures. - The ready follow-through path is explicit: read `advanced-brief.md`, `meta-prompt.md`, `evidence.json`, `ranked-references.json`, `bundle-manifest.json`, `media-analysis.json` when present, and screenshot, screencast, or pin-media evidence first; load `opendevbrowser_skill_load opendevbrowser-best-practices "quick start"`, `opendevbrowser_skill_load opendevbrowser-design-agent "canvas-contract"`, and `opendevbrowser_skill_load opendevbrowser-motion-design "quick start"`; continue only if `canvas-plan.request.json` exists; open a Canvas session; fill the session ids in `canvas-plan.request.json`; run `opendevbrowser canvas --command canvas.plan.set --params-file ./canvas-plan.request.json`; confirm `planStatus=accepted`; then patch only the governance blocks called out by `design-agent-handoff.json`. @@ -921,6 +921,7 @@ Notes: - `--timeout-ms` sets client-side daemon transport timeout for slow `--execute` runs. - `--browser-mode` is accepted for `--execute` runs and maps provider recovery to the same `auto|extension|managed` modes as workflow commands. - `--challenge-automation-mode` is accepted for `--execute` runs and maps to `challengeAutomationMode` with the same `run > session > config` precedence as workflow commands. +- Inspect `execution.meta.ok`, `execution.meta.partial`, and `execution.failures` before treating an executed macro as complete. `success:true` can mean transport success while provider execution remains unblocked but incomplete. - `opendevbrowser --help` includes this timeout flag in the global flag inventory. ### Blocker contract (v2) diff --git a/docs/SURFACE_REFERENCE.md b/docs/SURFACE_REFERENCE.md index fe266e7e..2859502b 100644 --- a/docs/SURFACE_REFERENCE.md +++ b/docs/SURFACE_REFERENCE.md @@ -578,10 +578,13 @@ Auth and policy: - Workflow and macro execute browser options: `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, and `macro-resolve --execute` accept `--browser-mode auto|extension|managed`; `extension` reuses relay-backed browser state, while `managed` runs a deterministic managed browser. Workflow `--profile ` selects a named managed profile for provider browser fallback; registry-backed explicit CDP profiles use `cdp-profile start` plus `connect --profile` for browser/session primitives until workflows expose an explicit-CDP transport selector. - Workflow and macro execute cookie options: `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, and `macro-resolve --execute` accept `--use-cookies` and `--cookie-policy-override off|auto|required` (`--cookie-policy` alias) so provider macros can require observable cookie-backed browser sessions. - Workflow and macro execute override flags: `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, and `macro-resolve --execute` accept `--challenge-automation-mode off|browser|browser_with_helper`, which maps to `challengeAutomationMode` with `run > session > config` precedence. -- Shopping `--region` remains advisory unless workflow output reports `region_authoritative=true`. The `region_unenforced` alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning. +- Research `--source-selection auto` is public-first and resolves to `web,community`. Use `all`, `social`, or explicit social sources only when social evidence is intentional. +- Shopping `--region` remains advisory unless workflow output reports `region_authoritative=true`. The `region_unenforced` alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning. `buyingReadiness.status` is the buying authority gate and is separate from transport success. +- Product-video outputs expose `presentationReadiness` and `productVideoReadiness` in JSON, manifest files, and completion text. Those statuses are the production authority gates and are separate from transport success. +- Macro execute outputs must be reviewed through `execution.meta.ok`, `execution.meta.partial`, and `execution.failures`; transport success can still be unblocked but incomplete. - Inspiredesign harvest flags: `--query`, repeatable `--provider`, `--max-references 1..10`, and `--visual-evidence off|auto|required`. Harvest requires `--query` or at least one `--url`, keeps the daemon method as `inspiredesign.run`, defaults to `mode=path`, `visualEvidence=required`, and `maxReferences=5`, and keeps explicit `--url` references before discovered references. - Before trusting daemon-backed `inspiredesign harvest` results, run `npx opendevbrowser status --daemon --output-format json` and require `data.fingerprintCurrent === true`; restart or isolate the daemon when it is false or missing. -- Inspiredesign harvest supports browser-native site recipes for visually driven sites. `--provider social/pinterest` selects the Pinterest recipe and should use a dedicated managed headed profile with cookies and `--cookie-policy required` for non-Google logged-in search; use extension `/ops` when live active-tab reuse is required. Registry-backed explicit CDP profiles are available for browser/session primitives, but provider workflows do not yet expose an explicit-CDP transport selector. Query-based Pinterest harvests become product-ready only through query-discovered canonical `/pin/{id}/` references plus manifest-backed first-party pin-media bytes; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence. Compatible Pinterest URL recovery can run as `--provider social/pinterest --url ` without `--query`; use one canonical `/pin/{id}/` URL per harvest when validating design-ready pin media. Generic provider plus URL recovery without query remains rejected. Pinterest is not registered as a default full social provider. +- Inspiredesign harvest supports browser-native site recipes for visually driven sites. `--provider social/pinterest` selects the Pinterest recipe. Default Pinterest harvest applies extension auth defaults only when `status --daemon` reflects a current handshaken extension and the caller has not set incompatible auth transport or cookie settings. Explicit `browserMode`, `profile`, `useCookies`, `cookiePolicyOverride`, or `challengeAutomationMode` values other than `browser_with_helper` opt out; explicit `challengeAutomationMode=browser_with_helper` is compatible and may merge with the implicit extension, cookies, and required cookie policy defaults. Use a dedicated managed headed profile with cookies and `--cookie-policy required` for managed recovery. Registry-backed explicit CDP profiles are available for browser/session primitives, but provider workflows do not yet expose an explicit-CDP transport selector. Query-based Pinterest harvests become product-ready only through query-discovered canonical `/pin/{id}/` references plus manifest-backed first-party pin-media bytes; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence. Compatible Pinterest URL recovery can run as `--provider social/pinterest --url ` without `--query`; use one canonical `/pin/{id}/` URL per harvest when validating design-ready pin media. Generic provider plus URL recovery without query remains rejected. Pinterest is not registered as a default full social provider. - Canonical Pinterest pin-media harvest opens the exact canonical pin in the active managed or extension workflow session before extracting persisted first-party bytes. Extension `/ops` remains the best live-tab reuse path, but it is no longer the only intended logged-in Pinterest lane. - Inspiredesign harvest primary capture is pin-media-first for Pinterest: proven image, GIF, and video pins require manifest-backed pin-media evidence for product-ready canonical pin-media harvests. Screenshot evidence and screencast evidence remain useful capture or motion lanes, but they are not substitutes for `evidenceAuthority=pin_media_ready`. Video posters remain still-image fallback cues, and DOM/clone/deep capture is disabled for Pinterest harvest. Remote DOM media URLs are not product-ready unless persisted first-party bytes appear in `pin-media-index.json`. - Inspiredesign capture-mode resolution preserves the existing explicit-URL override: `inspiredesign run` forces `captureMode=deep` for any explicit `--url`, while `inspiredesign harvest` forces deep capture for non-Pinterest explicit `--url` references even when `--capture-mode off` is requested. Pinterest-only harvest discovery and compatible Pinterest URL recovery force `captureMode=off` even when `--capture-mode deep` is requested. @@ -596,7 +599,7 @@ Auth and policy: - Workflow response keys: artifact-bearing workflow success payloads use `artifact_path`; provider follow-up summaries use `meta.primaryConstraintSummary`; typed recovery and handoff payloads use `nextStepGuidance.readiness`, `reasonCode`, `primaryAction`, `paramsExamples`, `validationChecks`, `fallbackPolicy`, and `doNotProceedIf` when available. Inspiredesign harvest also reports product `ready`, `guidanceReady`, `guidanceReadiness`, `productSuccess`, `harvestReadiness`, `readiness`, `rankedReferenceCount`, `evidenceAuthority`, and `artifactAuthority` so wrapper success is not confused with design readiness. Product `ready` is true only when authority gates pass. - `design-contract.json.colorSystem.tokens` and `implementation-plan.json.tokenStrategy.colors` use explicit `{ light, dark }` semantic token maps. `design-agent-handoff.json.implementationContext.tokenStrategy` carries the same dual-mode token strategy for implementation agents. - Continue to Canvas only when top-level `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, ranked references are non-empty, no matching `doNotProceedIf` blockers remain active, and manifest-backed authority evidence exists. Strict proof reviews should inspect `evidence.json`, `ranked-references.json`, `pin-media-index.json`, `motion-evidence.json`, `media-analysis.json`, and `bundle-manifest.json` directly before accepting release or Canvas follow-through evidence. For canonical Pinterest pin-media harvests, Canvas continuation requires `evidenceAuthority=pin_media_ready` and manifest-backed `pin-media-index.json`; `snapshot_ready` and `motion_ready` are not substitutes for pin-media readiness. For `needs_recovery`, `blocked`, or `diagnostic_only`, follow recovery-first guidance and do not treat emitted artifacts as design-ready. -- Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when their first-party pin-media artifact is captured, persisted, present in its manifest-backed index, and free of blocking warnings. `pin-media-index.json` remains the only pin-media readiness and provenance authority for persisted first-party bytes. Screenshot and screencast artifacts can inform diagnostics or motion design, but they do not satisfy required Pinterest pin-media readiness. The exact `login_or_challenge_state` and strict byte-backed `interface_chrome_shell` diagnostics are non-blocking only for trusted first-party manifest-backed pin-media bytes; broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers still demote readiness unless query-discovered canonical pins also produce complete pin-media authority. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure. `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority. +- Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when their first-party pin-media artifact is captured, persisted, present in its manifest-backed index, and free of blocking warnings. `pin-media-index.json` remains the only pin-media readiness and provenance authority for persisted first-party bytes. Screenshot and screencast artifacts can inform diagnostics or motion design, but they do not satisfy required Pinterest pin-media readiness. `login_or_challenge_state`, broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers demote readiness; strict byte-backed `interface_chrome_shell` diagnostics remain non-blocking only for trusted first-party manifest-backed pin-media bytes. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure. `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority. - Routine workflow bundles should omit output roots and inspect the returned `artifact_path` first. When output roots are omitted, persisted bundles use `.opendevbrowser//` and include `bundle-manifest.json`. CLI invocations resolve omitted roots from cwd before daemon dispatch; direct daemon RPC uses `core.workspaceRoot/.opendevbrowser`; direct OpenCode workflow tools use `deps.workspaceRoot/.opendevbrowser`. If a wrapper must pass an explicit workflow root, prefer `.opendevbrowser`; explicit external output roots remain caller-controlled for intentional temp, release, debug, audit, screenshot, and screencast lanes. - `artifacts cleanup --expired-only` without `--output-dir` targets the current working directory's `.opendevbrowser` root. Use `--output-dir /tmp/opendevbrowser` only when intentionally cleaning an explicit temp artifact root. - Browser evidence omitted outputs use workspace-local artifact roots: screenshots write `.opendevbrowser/screenshot//capture.png` with `path` and `artifact_path`, and screencasts write `.opendevbrowser/screencast/` with replay files. Explicit `--path` and `--output-dir` remain caller-controlled. Browser screenshot, screencast, Canvas, annotation, desktop audit, and release proof outputs are intentional non-bundle lanes, do not promise `bundle-manifest.json`, and are not targets for bundle manifest cleanup. diff --git a/docs/investigations/pinterest-diagnostic-only-login-recovery-2026-07-07.md b/docs/investigations/pinterest-diagnostic-only-login-recovery-2026-07-07.md new file mode 100644 index 00000000..335e83b8 --- /dev/null +++ b/docs/investigations/pinterest-diagnostic-only-login-recovery-2026-07-07.md @@ -0,0 +1,308 @@ +# Investigation: Pinterest Diagnostic-Only Login And Recovery + +## Summary +Pinterest harvest transport is completing, but the product-readiness gate correctly keeps outputs diagnostic-only when the default run cannot produce manifest-backed first-party pin media. On this machine, current evidence does not support a stale installed binary, stale daemon, or stale skill-pack root cause. Live validation shows the explicit extension-auth Pinterest path works and produces product-ready `pin_media_ready` bundles, while the default harvest path does not automatically use or switch to that extension-auth path. + +In this report, **extension-auth first** means Pinterest harvest should default to extension mode with cookies required and browser-scoped helper assistance when the daemon reports a current connected extension. Concretely, `inspiredesign harvest --provider social/pinterest` should behave as though the caller supplied `--browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper`, unless the user explicitly requests another mode. + +Put directly: yes, for Pinterest harvest the recommended default should be extension mode when a current handshaken extension is available. "Auth first" does not mean accepting diagnostic-only auth claims or bypassing readiness gates. It means choosing the live extension-auth transport before managed headed or managed headless for Pinterest, because extension mode is the path that can reuse the user's logged-in Pinterest browser state. + +Scope note: sections 1-9 are the Pinterest diagnostic investigation. Sections 10-13 are the broader workflow-default audit added during the same investigation, covering the artifact-bearing `research`, `shopping`, `product-video`, `inspiredesign`, and `macro-resolve --execute` workflows that expose readiness or authority-style success gates. + +## Symptoms +- Pinterest harvest commands complete, but OpenDevBrowser marks the output diagnostic-only. +- The failing runs appear tied to Pinterest requiring login or manual recovery. +- Possible causes include stale cache, installed binary or skill drift, default settings, daemon transport setup, or a runtime readiness/capture regression. + +## Background / Prior Research +- Prior investigations established that CLI transport success is not product success. `productSuccess`, `artifactAuthority`, `evidenceAuthority`, `ranked-references.json`, and `pin-media-index.json` must be inspected directly. +- Prior July 2026 evidence found an empty `pin-media-index.json` as the decisive diagnostic-only surface and a stale installed `opendevbrowser-best-practices` copy as a contributing guidance issue. +- Prior code archaeology shows repeated Pinterest readiness fixes, including `d2c5861 fix: stabilize pinterest pin media setup`, `5724f22 Merge pull request #101 from freshtechbro/codex/pinterest-broad-query-readiness`, and earlier pin-media authority hardening commits. + +## Investigator Findings + + +### 2026-07-07 Read-Only Verification + +**Scope:** Verified target worktree `/Users/bishopdotun/.t3/worktrees/opendevbrowser/t3code-985b0845` at `184472c8945019569053bb9827f872a4eea913ef` on branch `t3code/pinterest-harvest-fix`. No source files were edited. + +**Verdict summary:** Transport completion is not product readiness. Current local daemon and installed CLI state do not prove a stale-state root cause. The default Pinterest harvest path is not extension-first and does not automatically rerun through extension auth after a blocked discovery. The explicit extension-auth path works on this machine and produced a product-ready `pin_media_ready` bundle. Managed profile recovery still has a separate final-capture profile-threading risk, and the warning exceptions around `login_or_challenge_state` and `interface_chrome_shell` should still be reviewed. + +1. **Stale installed binary, daemon, skill, or cache state: mostly disproved for this machine, still a required preflight.** + - Active `opendevbrowser` and `npx opendevbrowser` both report `0.0.40`, matching target `package.json`. The daemon status returned `fingerprintCurrent: true`, and the global plus `.agents` `opendevbrowser-best-practices` skill copies match repo version `2.6.0`. + - Current guidance correctly requires daemon freshness before trusting daemon-backed harvests: `src/public-surface/source.ts:881-889`, `docs/SURFACE_REFERENCE.md:583-585`, `skills/opendevbrowser-best-practices/SKILL.md:194-203`. + - Stale-state remains plausible in other environments because the active binary can differ from npm's default global prefix. The skill already warns to compare active binary paths and npm prefix: `skills/opendevbrowser-best-practices/SKILL.md:213-215`. + - Commit archaeology: `d2c5861` specifically added installed skill validation and real recovery evidence docs, but current local evidence does not show stale daemon, skill, or binary drift. + +2. **Default transport/profile setup does not provide verified Pinterest auth: proven.** + - The CLI forwards `browserMode`, `profile`, `useCookies`, and `cookiePolicyOverride` only when supplied by the caller. Defaults are harvest path output, required visual evidence, and a timeout, not Pinterest auth: `src/cli/commands/inspiredesign.ts:378-397`. + - The Pinterest recipe is `authenticated_preferred`, not authenticated-required, and its authenticated command is guidance rather than runtime default: `src/guidance/recipes/pinterest.ts:15-19`, `src/guidance/recipes/pinterest.ts:160-185`. + - Browser-native discovery treats `cookiePolicy === "required"` as requiring auth, but accepted proofs include extension plus cookies, managed trusted profile agreement, or explicit CDP trusted profile agreement. These are continuity proofs, not provider login proof by themselves: `src/providers/browser-native-discovery.ts:90-116`, `src/providers/browser-native-discovery.ts:138-153`. + - Runtime policy explicitly distinguishes `provider_verified`, `profile_declared`, and `cookies_observable`: `src/providers/runtime-policy.ts:225-239`. Current Pinterest flows do not appear to set a Pinterest-specific `provider_verified` proof. + - Managed profile trust is declared from a supplied profile name when mode is managed or omitted: `src/providers/workflows.ts:949-970`. + - Important gap: workflow pin-media options include `profile`, and the tool plus daemon adapters forward options into `captureInspiredesignPrimaryPinMediaEvidenceFromManager`, but the concrete capture option type drops `profile` and the primary launch omits it. `BrowserManager.launch` accepts `profile`, so managed-profile continuity can be lost specifically in the final pin-media authority lane: `src/providers/workflows.ts:358-368`, `src/tools/inspiredesign_run.ts:132-136`, `src/cli/daemon-commands.ts:959-963`, `src/inspiredesign/capture.ts:104-130`, `src/inspiredesign/capture.ts:1017-1024`, `src/browser/browser-manager.ts:137-146`. Canonical extension warmup also launches without profile: `src/inspiredesign/capture.ts:966-972`. + +3. **Runtime treatment of login, search-shell, and manual recovery pages: mixed. Search-shell is guarded, but login/chrome warning exceptions are risky.** + - Discovery intentionally accepts canonical pins from rendered search-shell or login-overlay pages only when rendered canonical pin links are present, and only for URL recovery. Exact rendered-link checks live at `src/providers/browser-native-discovery.ts:497-548`; extraction returns diagnostics with `sourcePageQuality` and accepted URLs at `src/providers/browser-native-discovery.ts:851-887`. + - Search-shell without rendered pin evidence stays diagnostic: `src/providers/browser-native-discovery.ts:774-848`, with regression tests at `tests/pinterest-guidance-recipe.test.ts:1254-1269`, `tests/pinterest-guidance-recipe.test.ts:2051-2229`, and `tests/pinterest-guidance-recipe.test.ts:2459-2486`. + - Final product readiness is strict: Pinterest-owned references require `pin_media_ready` authority backed by artifacts, and product success requires guidance ready, non-empty ranked references, coherent authority counts, no active blockers, and required Pinterest authority: `src/inspiredesign/product-readiness.ts:800-820`, `src/inspiredesign/product-readiness.ts:1105-1188`. + - `pin-media-index.json` authority requires canonical source/reference match, first-party media URL, valid artifact path, hash, bytes, dimensions, content type, and no blocking warnings: `src/inspiredesign/product-readiness.ts:443-491` and `src/inspiredesign/reference-pattern-board.ts:713-750`. + - Risk: `login_or_challenge_state` is explicitly non-blocking for pin-media warnings: `src/inspiredesign/pinterest-pin-media-evidence.ts:164-182`. `interface_chrome_shell` is also allowed when strict canonical byte-backed evidence passes: `src/inspiredesign/pinterest-pin-media-evidence.ts:776-783`. Tests codify this behavior by accepting indexed pin media with those warnings: `tests/inspiredesign-product-readiness.test.ts:1140-1167`. + - That exception should be reviewed because it can make a login/chrome-tainted canonical pin product-ready if all byte-backed checks pass. By contrast, `pinterestPageQuality: "search_shell"` is rejected from the index: `tests/inspiredesign-pinterest-pin-media-evidence.test.ts:1177-1179`. + +4. **Canonical pin-media load or timeout regression: old `load` hang mostly disproved at HEAD, with residual short-budget risk.** + - Canonical Pinterest pin URLs are normalized before capture: `src/inspiredesign/capture.ts:1136-1148`. + - Canonical pin setup uses `domcontentloaded` instead of `load`, and network-idle waits are capped and timeout-tolerant: `src/inspiredesign/capture.ts:984-999`, `src/inspiredesign/capture.ts:1036-1055`. + - Pin-media capture now runs before viewport probes can consume the budget: `src/inspiredesign/capture.ts:1177-1212`. + - Browser primitive capture is bounded by target-scope timeout, DOM inspection timeout, CDP fallback timeout, and per-candidate fetch timeout: `src/browser/browser-manager.ts:3290-3359`, `src/browser/browser-manager.ts:3362-3415`, `src/browser/browser-manager.ts:3418-3447`. + - Regression tests cover stale target queue timeout, exact canonical pin `domcontentloaded`, warmup timeout continuation, pin-media first budget, network-idle caps, and CDP fallback: `tests/browser-manager.test.ts:6427-6448`, `tests/providers-inspiredesign-capture.test.ts:889-1002`, `tests/providers-inspiredesign-capture.test.ts:1171-1453`, `tests/browser-manager.test.ts:10666-10831`. + - Residual risk: setup step budgets are capped proportionally under short caller deadlines, so live extension launch can still fail fast and return diagnostic setup failure. That is bounded, not an unbounded transport hang. + +5. **Requested commit archaeology.** + - `0435146` added broad Pinterest readiness closure: pin-media timeout caps, capture mode handling, daemon transport timeout increase, CDP extraction fallback, and readiness regressions. + - `0b65126` hardened pin-media capture budgets: stale target queue waits, per-candidate fetches, setup step caps, and login-overlay query-discovered pin-media readiness tests. + - `128df64` allowed rendered canonical pins through login/search shells while preserving strict challenge and noncanonical rejection. + - `017dd7e` expanded login-overlay search recovery when text search-result cues plus rendered canonical pin evidence are present. + - `d2c5861` stabilized canonical pin-media setup by switching exact canonical setup to `domcontentloaded`, preserving strict authority gates, and validating installed skill copies from the repo cwd. + - Current `184472c` is a release/final-review merge. The relevant Pinterest readiness and capture behavior is inherited from the earlier commits above. + +6. **Fresh local verification.** + - Target worktree status is read-only except this untracked investigation report. Branch is `t3code/pinterest-harvest-fix`, HEAD is `184472c8945019569053bb9827f872a4eea913ef`. + - Active CLI is `/opt/homebrew/bin/opendevbrowser`, `opendevbrowser --version` reports `0.0.40`, and target `package.json` is `0.0.40`. + - `npx opendevbrowser status --daemon --output-format json` reports `success: true`, `data.fingerprintCurrent: true`, extension connected, extension handshake complete, and ops connected. + - Repo and installed `.agents` `opendevbrowser-best-practices` copies both report version `2.6.0` and contain the current Pinterest authority guidance. + - The T3 worktree has no local `node_modules/.bin/vitest`, so targeted tests cannot run from that path without installing dependencies. The same-version main checkout passed `npm run test -- tests/pinterest-guidance-recipe.test.ts tests/providers-inspiredesign-capture.test.ts tests/inspiredesign-product-readiness.test.ts tests/providers-inspiredesign-workflow.test.ts`: 4 files passed, 338 tests passed. The coverage table from that targeted run is not global coverage evidence. + +7. **Pinterest parity, default mode, and fallback behavior: verified.** + - CLI parsing accepts `--browser-mode auto|extension|managed`, but the harvest command does not set a Pinterest-specific default browser mode. It forwards `browserMode`, `profile`, `useCookies`, and `cookiePolicyOverride` only when supplied: `src/cli/commands/inspiredesign.ts:256-270`, `src/cli/commands/inspiredesign.ts:378-397`. + - Provider runtime policy defaults social providers to `["managed_headed"]`; only shopping defaults to `["extension", "managed_headed"]`. Explicit `--browser-mode extension` narrows modes to `["extension"]`, and explicit `--browser-mode managed` narrows modes to `["managed_headed"]`: `src/providers/runtime-policy.ts:21-38`. + - Pinterest guidance and recipe recovery do recommend extension auth. The recipe command uses `--browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper`, and the recovery step says to use extension mode with a user-authorized signed-in Pinterest tab: `src/guidance/recipes/pinterest.ts:15-18`, `src/guidance/recipes/pinterest.ts:160-185`. + - There is no evidence of automatic default-to-extension or automatic mode escalation for Pinterest harvest after a diagnostic-only default run. The default run emitted blocked guidance recommending extension mode, but did not rerun itself through extension mode. + - Extension fallback can reuse the live extension tab and verify provider cookies when `cookiePolicy` is not off: `src/providers/runtime-factory.ts:1316-1366`, `src/providers/runtime-factory.ts:1507-1518`. However, extension `/ops` sessions do not support the final Pinterest pin-media byte capture helper directly: `src/browser/ops-browser-manager.ts:1023-1030`. + - The practical parity answer is therefore: default Pinterest harvest is not extension-first today. Here, extension-first means defaulting Pinterest harvest to the live extension session with cookies required and browser-scoped helper assistance when extension readiness is current. Explicit extension-auth mode is the working live-auth path; managed headed can be used with a managed profile but still has the final profile-threading risk described above; managed headless is only the recommended mode for public/no-auth cases and is not a reliable Pinterest login path. + +8. **Live full Pinterest harvest validation: default run.** + - Command shape, intentionally without `--browser-mode`, `--profile`, `--use-cookies`, or `--cookie-policy`: `npx opendevbrowser inspiredesign harvest --brief "Premium digital photography studio landing page" --query "Pinterest premium digital photography studio landing page cinematic parallax portfolio" --provider social/pinterest --max-references 3 --visual-evidence required --mode json --output-format json --timeout-ms 240000`. + - Result: exit `0`, `success=true`, `ready=false`, `productSuccess=false`, `artifactAuthority=diagnostic_only`, `evidenceAuthority=diagnostic_only`, `guidanceReadiness=blocked`. + - Artifact bundle: `.opendevbrowser/inspiredesign/cdff8771-264b-4f76-8e72-443a244f691e`. + - `discovery-diagnostics.json` recorded `acceptedUrlCount=0`, `failureCount=1`, `reason=env_limited`, `sourcePageQuality=chrome_only`, and `diagnosticBlockers=["search_shell_without_media_signals"]`. + - `ranked-references.json` had zero references, `pin-media-index.json` was empty, `pin-media-evidence.json` was empty, and `media-analysis.json` had zero references. + - The run's own guidance recommended the extension-auth parameters: `browserMode=extension`, `useCookies=true`, and `cookiePolicy=required`. + +9. **Live full Pinterest harvest validation: explicit extension-auth run.** + - Command shape: `npx opendevbrowser inspiredesign harvest --brief "Premium digital photography studio landing page" --query "Pinterest premium digital photography studio landing page cinematic parallax portfolio" --provider social/pinterest --max-references 3 --visual-evidence required --browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper --mode json --output-format json --timeout-ms 240000`. + - Result: exit `0`, `success=true`, `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, `evidenceAuthority=pin_media_ready`, `guidanceReadiness=ready`, `harvestReadiness=ready`. + - Artifact bundle: `.opendevbrowser/inspiredesign/ed3e54e0-fdc0-4200-8e82-94879ac4716f`. + - Discovery accepted three canonical pin URLs: `https://www.pinterest.com/pin/244812929739933748/`, `https://www.pinterest.com/pin/179581103892030641/`, and `https://www.pinterest.com/pin/2674081025172783/`. + - `ranked-references.json` reported `rankedReferenceCount=3`, `ready=true`, `artifactAuthority=product_ready`, `evidenceAuthority=pin_media_ready`, and every reference captured via `fetch`, `pin_media`, and `pin_media_ready`. + - `pin-media-index.json` contained three manifest-backed entries with persisted first-party bytes: two JPEGs and one WebP under `pin-media-evidence/...`, with byte counts `49127`, `65148`, and `59388`, valid dimensions, `authority=design_evidence`, `pinterestPageQuality=pin_media`, no warnings, `mediaUrlFirstParty=true`, and `sourceUrlMatchesReference=true`. + - `media-analysis.json` contained three analyzed references sourced from those saved pin-media files. `visual-evidence.json` recorded supplemental viewport screenshots as skipped because `pin_media_visual_authority_satisfied`; `motion-evidence.json` remained empty because these were still image pins. + - Post-run daemon status still showed `fingerprintCurrent=true`, extension connected, extension handshake complete, ops connected, and the extension tab on a Pinterest pin URL. This proves extension live-tab auth/navigation was usable on this machine for the successful run. + +10. **All other provider workflow defaults: source-traced.** + - The workflow surface under this audit is `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, and `macro-resolve --execute`. Generated help exposes these as the artifact-bearing provider workflow and browser-scoped helper lanes. + - The shared browser fallback default is source-family based, not workflow-success based: `web`, `community`, and `social` default to `["managed_headed"]`, while `shopping` defaults to `["extension", "managed_headed"]`: `src/providers/runtime-policy.ts:21-38`. + - CLI commands mostly pass through `browserMode`, `profile`, `useCookies`, `challengeAutomationMode`, and `cookiePolicyOverride` only when supplied. Research does this at `src/cli/commands/research.ts:292-309`, shopping at `src/cli/commands/shopping.ts:257-277`, product-video at `src/cli/commands/product-video.ts:220-243`, Inspiredesign at `src/cli/commands/inspiredesign.ts:378-397`, and macro execute at `src/cli/commands/macro-resolve.ts:244-263`. + - Research defaults to source selection `auto`, which expands to `web`, `community`, and `social`: `src/providers/research-compiler.ts:17-18`, `src/providers/research-compiler.ts:215-237`. + - Shopping defaults to tier-1 shopping providers when `--providers` is omitted and auto-excludes degraded default providers only when the selection was not explicit: `src/providers/shopping-workflow.ts:11-13`, `src/providers/shopping-workflow.ts:44-60`, `src/providers/shopping-workflow.ts:84-98`. + - Product-video defaults `include_all_images=true` and `include_copy=true` at compiler normalization even though the flags look optional on the CLI; pass readiness still depends on promoted clean claims, spec dimensions, visual assets, and text isolation: `src/providers/product-video-compiler.ts:220-222`, `src/providers/product-video-presentation/gate.ts:43-47`. + - Research pass readiness requires accepted records, independent domains, usable content, low rejection pressure, no blocking diagnostics, and no anti-bot failures: `src/providers/research-report/gate.ts:59-70`, `src/providers/research-report/gate.ts:98-105`. + - Shopping pass readiness requires usable and recommended offers, known availability, trusted/fresh same-currency prices, no duplicate pressure, computed baseline, no workflow diagnostics, and authoritative region when requested: `src/providers/shopping-report/gate.ts:88-114`. + - Inspiredesign user-facing product readiness requires `productSuccess=true`, `artifactAuthority=product_ready`, guidance readiness `ready`, and evidence authority in the product-ready set. The final authority precedence is `pin_media_ready`, then `motion_ready`, then `snapshot_ready`: `src/inspiredesign/product-readiness.ts:767-795`, `src/inspiredesign/product-readiness.ts:1105-1116`. + - Macro execute has no persisted artifact authority gate. Its success shape is execution records with no `execution.meta.blocker`; browser/cookie/challenge flags are valid only with `--execute`: `src/cli/commands/macro-resolve.ts:87-105`, `src/cli/commands/macro-resolve.ts:227-255`. + +11. **Live workflow validation: non-Pinterest workflows.** + - All commands in this pass were run from `/Users/bishopdotun/.t3/worktrees/opendevbrowser/t3code-985b0845` against a daemon reporting `fingerprintCurrent=true`. Command logs and raw stdout/stderr are in `/tmp/opendevbrowser-workflow-audit-20260707`. + - `research_default`: `research run --topic "OpenDevBrowser workflow reliability defaults" --days 14 --mode json --output-format json --timeout-ms 180000` exited `0` and wrote `.opendevbrowser/research/d0b3c7d7-8600-4b3d-9b0d-70214d453a65`, but `report.md` evidence gate was `fail`. The default `auto` source selection expanded to `web, community, social`; `meta.primaryConstraintSummary` was `Instagram requires login or an existing session`, with 5 provider failures and 7 alerts. + - `research_reliable`: adding `--sources web,community --browser-mode managed --challenge-automation-mode browser_with_helper` exited `0` and wrote `.opendevbrowser/research/bbdf729a-9bcb-4f25-b781-a10819108afa`, but the evidence gate was still `partial`, not `pass`, because rejection pressure and 3 workflow alerts remained. A narrower `--sources web --browser-mode managed` run wrote `.opendevbrowser/research/567093dd-314d-4dda-a0a4-fd4d8fee9f63` and stayed `partial` for the same global-alert pattern. + - Research default-success implication: public-first managed sources are more reliable than `auto`, but pass reliability also requires scoping degraded-provider alerts to selected sources or starting each run with an isolated/non-degraded runtime state. Otherwise stale social degradation can demote a web-only evidence gate. + - `shopping_default`: `shopping run --query "wireless ergonomic mouse" --budget 150 --mode json --output-format json --timeout-ms 180000` exited `0` and wrote `.opendevbrowser/shopping/f4aeb42a-8f72-44f4-a960-006808454fc5`. `deals.md` buying readiness was `partial`: 20 usable offers from 27, no provider failures, but 1 filter diagnostic kept workflow diagnostics from passing. + - `shopping_reliable`: explicit `--providers shopping/bestbuy,shopping/ebay --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper` exited `0` and wrote `.opendevbrowser/shopping/f9a1f728-f6f9-47e6-b433-0c9682e9aea6`. It was also `partial`: 4 usable offers from 10 and 1 filter diagnostic. A `shopping/bestbuy` single-provider candidate failed readiness because every offer had unknown availability and no market baseline. + - Shopping default-success implication: explicit providers and managed mode reduce transport variability, but strict `pass` also requires enough quality-eligible same-currency offers, known availability, no filter diagnostics, and a computed baseline. The default should surface "partial buying brief" as a valid constrained output, not imply pass-level buying confidence. + - `product_video_default`: `product-video run --product-url "https://www.apple.com/airpods-pro/" --output-format json --timeout-ms 180000` exited `0` and wrote `.opendevbrowser/product-video/0625342b-f0be-4140-b9d8-309b611a6148`. `presentation-readiness.json` was `partial`: 50 visual assets, clean title, copy requested, but clean benefit evidence spanned only 1 evidence dimension and reason codes included `insufficient_clean_feature_evidence`. + - `product_video_reliable`: adding `--browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-screenshots --include-all-images --include-copy` exited `0` and wrote `.opendevbrowser/product-video/99eef399-00ee-4c16-a031-8f660a23305e`, but readiness stayed `partial` for the same evidence-dimension reason. A Best Buy product URL candidate exited `2` with `Bestbuy is blocked by the Best Buy country-selection interstitial`. + - Product-video default-success implication: the successful path is not just a browser mode. It needs an official or provider page with enough clean product claims and spec dimensions, plus visual assets. Marketplace/country interstitials should trigger a provider-specific recovery path, and `partial` must remain a gated draft state until reason codes pass. + - `inspiredesign_run_default`: `inspiredesign run --brief "Extract a reusable dashboard design contract from live references" --url https://linear.app --mode json --output-format json --timeout-ms 180000` exited `0` and wrote `.opendevbrowser/inspiredesign/51470123-8715-4358-9338-bd805558bf19`, but it was `productSuccess=false`, `artifactAuthority=diagnostic_only`, `evidenceAuthority=diagnostic_only`. The captured Linear page was a login surface and off-brief. + - `inspiredesign_run_reliable`: adding `--browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-prototype-guidance` wrote `.opendevbrowser/inspiredesign/7eb15581-fce7-40e4-be01-8528e2ac23fd` and stayed diagnostic-only for the same off-brief/login-surface reason. + - `inspiredesign_harvest_web_default`: `inspiredesign harvest --brief "Synthesize a premium docs workspace" --query "best docs product landing pages" --provider web/default --max-references 3 --visual-evidence required --mode json --output-format json --timeout-ms 180000` exited `0` and wrote `.opendevbrowser/inspiredesign/5d37b34a-da46-46f7-b64a-a88e4f2f0376`, but readiness was `needs_recovery` and authority was diagnostic-only because the top reference was off-brief. + - `inspiredesign_harvest_web_reliable`: adding `--browser-mode managed --challenge-automation-mode browser_with_helper` wrote `.opendevbrowser/inspiredesign/4b962421-ae8a-4ac1-acb6-8be44bacfe47`, but readiness stayed `needs_recovery`; `meta.primaryConstraintSummary` was `Deep capture failed for 1 reference`. + - Apple exact-brief candidates surfaced a separate authority-promotion follow-up. `inspiredesign harvest --brief "Apple AirPods Pro product landing page" --url https://www.apple.com/airpods-pro/ --visual-evidence required --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper` wrote `.opendevbrowser/inspiredesign/4a9b5484-9857-442c-b475-74e99e24687b`; `visual-evidence.json` and `screenshot-index.json` contained a captured manifest-backed screenshot, and `ranked-references.json` had `topReferenceScore=94`, `topReferenceIntentMatched=true`, `missingScreenshotCount=0`. The top response still reported `authoritativeReferenceCount=0`, `snapshotReadyReferenceCount=0`, `productSuccess=false`, and `evidenceAuthority=diagnostic_only`. + - Inspiredesign non-Pinterest default-success implication: there are two fixes to separate. Direct `inspiredesign run --url` has no `--visual-evidence` flag and can skip visual authority, so direct URL runs should either default visual evidence to `auto|required` when a product-ready design handoff is expected or clearly label themselves as contract-only until harvest captures authority. Separately, screenshot-backed non-Pinterest harvests need an authority-counting/promotion fix so manifest-backed screenshots can become `snapshot_ready` when the existing readiness conditions are satisfied. + - `macro_execute_default`: `macro-resolve --expression '@web.search("OpenDevBrowser browser automation", 3)' --execute --output-format json --timeout-ms 180000` exited `0`, had 3 execution records, and no blocker. + - `macro_execute_reliable`: adding `--browser-mode managed --challenge-automation-mode browser_with_helper` also exited `0`, had 3 execution records, and no blocker. Macro execute is already default-successful for this public web expression; for gated macros, the reliable path should be preview first, then execute with the source-specific browser mode, cookies, and helper guidance. + +12. **Workflow-by-workflow default path recommendations.** + - Research: change default `auto` for ordinary research to public-first `web,community` in managed mode, with social sources opt-in. If `source-selection=social` or social sources are explicit and extension readiness is current, use extension-auth first with cookies and `browser_with_helper`; otherwise fail closed with a signed-in-session recovery command. Scope workflow alerts to selected sources/providers so unrelated degraded providers cannot demote public web-only research. + - Shopping: prefer managed mode with cookies and `browser_with_helper` for reproducible shopping runs, but keep extension available when provider diagnostics show auth/challenge pressure and a current extension session exists. Treat `partial` buying briefs as successful constrained artifacts but not strong-deal authority; next-step guidance should name the exact failed gate, usually unknown availability, baseline unavailable, filter diagnostics, region advisory, or provider failures. + - Product-video: default to managed mode with cookies, helper, `include_all_images=true`, `include_copy=true`, and at least one screenshot for URL-based runs. If the target is a marketplace or country interstitial, automatically recommend extension live-tab recovery or an official product page. Production pass should require `presentation-readiness.json.summary.status=pass` and matching `manifest.readiness.presentation.status=pass` plus `manifest.readiness.productVideo.status=pass`; otherwise keep copy/features as gated draft. + - Inspiredesign run: treat it as contract synthesis unless it has artifact-backed evidence. For product-ready design handoff, prefer `inspiredesign harvest` with explicit URLs or query discovery and visual evidence required. If direct `run --url` remains a product-ready path, add a default visual authority lane or a CLI flag equivalent to harvest's `--visual-evidence required`. + - Inspiredesign harvest web/default: default to managed mode with required visual evidence and helper. When query discovery returns off-brief or weak pages, the default successful path should be explicit high-quality URLs, not mode switching. Fix screenshot authority promotion so a manifest-backed non-Pinterest screenshot with matching source provenance can satisfy `snapshot_ready`. + - Inspiredesign harvest Pinterest: default to extension-auth first when the extension is current and handshaken. Managed headed with a named profile remains a recovery path only after profile threading into final pin-media capture is fixed. Managed headless should stay public/no-auth only. + - Macro resolve: keep resolve-only as the default. For `--execute`, choose transport by resolved provider/source. Public web macro execution can remain managed/public; gated social, shopping, or auth-sensitive macros should emit source-specific retry guidance with extension or managed profile based on provider diagnostics. + +13. **Where implementation guidance must be wired when fixes are made.** + - Next-step guidance: update `src/providers/workflow-handoff.ts` and guidance recipes so each workflow reports the gate that actually failed, the authority field expected, the current observed value, and the exact rerun command for the reliable path. + - Error reports: normalize workflow completion messages so `success=true` is always labeled as transport success, while workflow authority is reported separately as `evidence_gate`, `buying_readiness`, `presentation_readiness`, `productSuccess`, `artifactAuthority`, or `execution.blocker`. + - Public docs and help: update `src/public-surface/source.ts`, generated manifests, `docs/CLI.md`, `docs/SURFACE_REFERENCE.md`, and this `AGENTS.md` guidance when defaults change. Current docs already warn that output roots should usually be omitted and that artifact sidecars are authoritative. + - Skills: update `skills/opendevbrowser-best-practices/SKILL.md`, `opendevbrowser-research`, `opendevbrowser-shopping`, `opendevbrowser-product-presentation-asset`, and design-agent/motion-design skills so agents learn the reliable default path and the exact stop conditions before Canvas, buying, publishing, or production use. + - AGENTS files: update `src/providers/AGENTS.md`, `src/providers/research-report/AGENTS.md`, `src/providers/shopping-report/AGENTS.md`, `src/providers/product-video-presentation/AGENTS.md`, `src/inspiredesign/AGENTS.md`, `src/guidance/AGENTS.md`, `src/cli/commands/AGENTS.md`, `docs/AGENTS.md`, and root `AGENTS.md` only if behavior or workflow governance changes. + - Tests and live gates later: add fixture-level tests for each success authority and one live smoke per workflow family. For this investigation, no implementation or test changes were made. + +## Investigator Findings: Inspiredesign Snapshot Authority Follow-Up + +**Scope:** Read-only source and test trace for non-Pinterest Inspiredesign authority behavior in the T3 worktree. Source files were not edited. + +**Verdict:** The follow-up is mostly proven. Non-Pinterest harvests can persist manifest-backed screenshot artifacts yet fail `snapshot_ready` when the persisted screenshot lacks trusted `sourceUrl` provenance. Direct `inspiredesign run --url` is already fail-closed for product readiness when visual authority is off, but its user-facing wording should make the contract-only or diagnostic-only nature explicit. Renderer and product-readiness authority counts are strict and sidecar-backed, with media-analysis remaining advisory. + +1. **Manifest-backed screenshot harvest can fail to promote `snapshot_ready`: proven.** + - Workflow finalization persists screenshot files and sidecars from captured visual temp files at `src/providers/workflows.ts:3729-3799`, then counts only manifest-backed screenshots whose paths are in the artifact bundle at `src/providers/workflows.ts:7201-7207`. + - Product-readiness requires generic screenshot provenance to match the ranked reference URL at `src/inspiredesign/product-readiness.ts:267-273` and requires that provenance plus valid path/hash/bytes/no blocking warnings before counting generic visual authority at `src/inspiredesign/product-readiness.ts:285-311`. + - The existing non-Pinterest harvest fixture captures and writes `visual-evidence/.../viewport.png`, `visual-evidence.json`, and `screenshot-index.json`, but its captured visual metadata omits `sourceUrl`: `tests/providers-inspiredesign-workflow.test.ts:656-694` and `tests/providers-inspiredesign-workflow.test.ts:900-970`. That proves artifact persistence without proving promotion. + - Positive coverage already proves generic screenshots can promote when `sourceUrl` matches: `tests/inspiredesign-product-readiness.test.ts:970-1021`. The missing regression is the workflow-level case where a captured non-Pinterest screenshot without `sourceUrl` stays diagnostic despite being manifest-backed. + +2. **Direct `run --url` needs visual authority or clear contract-only labeling: authority requirement proven, labeling gap likely.** + - CLI direct `run` defaults `visualEvidence` to `off`, while `harvest` defaults it to `required`: `src/cli/commands/inspiredesign.ts:366-397`. Workflow normalization preserves that same default: `src/providers/workflows.ts:2035-2045`. + - Product readiness requires at least one artifact-backed authority count and all ranked references to be authoritative: `src/inspiredesign/product-readiness.ts:1138-1174`. User-facing success only permits `snapshot_ready`, `motion_ready`, or `pin_media_ready`: `src/inspiredesign/product-readiness.ts:767-795`. + - Renderer also blocks Canvas/product-ready output unless guidance is ready, counts are coherent, and every selected reference has authoritative visual, motion, or Pinterest pin-media evidence: `src/providers/renderer.ts:300-342`, `src/providers/renderer.ts:382-421`, and `src/providers/renderer.ts:1107-1158`. + - Existing direct-run test asserts diagnostic output by default, no `canvas-plan.request.json`, and diagnostic `design.md`: `tests/providers-inspiredesign-workflow.test.ts:621-637`. A required-visual direct URL path can produce Canvas/product artifacts when screenshot authority is present: `tests/providers-inspiredesign-workflow.test.ts:710-868`. + +3. **Renderer and product-readiness authority counts are sidecar-backed and strict: proven.** + - Product-readiness reads ranked references from direct, evidence, meta, or reference-pattern-board records, then derives snapshot, motion, pin-media, and authoritative totals from artifact indexes: `src/inspiredesign/product-readiness.ts:904-968`. + - Screenshot, motion, and pin-media sidecars are read only from known fields: `src/inspiredesign/product-readiness.ts:1001-1031`. Final precedence is pin media, motion, then snapshot: `src/inspiredesign/product-readiness.ts:1105-1116`. + - Renderer recomputes the same counts from `authorityScreenshotIndex`, `authorityMotionEvidence`, and `authorityPinMediaIndex`, and rejects explicit meta counts that do not match computed counts: `src/providers/renderer.ts:300-342` and `src/providers/workflows.ts:7249-7286`. + - Tests already cover generic snapshot and motion promotion, pin-media manifest authority, and media-analysis non-authority: `tests/inspiredesign-product-readiness.test.ts:970-1135` and `tests/inspiredesign-product-readiness.test.ts:1309-1340`. + +4. **Recommended smallest fixes and tests.** + - Smallest source fix: in `finalizeInspiredesignReferenceVisual`, inject trusted fallback `sourceUrl: visual.sourceUrl ?? reference.url` before `persistInspiredesignVisualEvidence(...)` only for captured non-Pinterest workflow visuals with a trusted temp path. Keep Pinterest provenance strict. Target seam: `src/providers/workflows.ts:3729-3799`. + - Add a workflow regression beside `tests/providers-inspiredesign-workflow.test.ts:648-970` that asserts a non-Pinterest harvest with manifest-backed screenshot returns `productSuccess=true`, `artifactAuthority="product_ready"`, `evidenceAuthority="snapshot_ready"`, `snapshotReadyReferenceCount=1`, and `authoritativeReferenceCount=1` after the provenance fix. + - Add a negative workflow or product-readiness regression where screenshot `sourceUrl` is missing or mismatched and the run remains `diagnostic_only`. + - Strengthen the direct `run --url` default test at `tests/providers-inspiredesign-workflow.test.ts:621-637` to assert top-level and meta `productSuccess=false`, `artifactAuthority="diagnostic_only"`, and `evidenceAuthority="diagnostic_only"`. If wording changes, adjust renderer strings around `src/providers/renderer.ts:518-572` to say contract-only or diagnostic-only explicitly. + - Add a focused renderer test that non-authority `screenshotIndex`, `motionEvidence`, or `pinMediaIndex` alone cannot flip product readiness when authority indexes are empty or invalid, covering `src/providers/renderer.ts:300-342` and `src/providers/renderer.ts:1107-1158`. + +## Investigator Findings: Workflow Defaults Follow-Up + +**Date:** 2026-07-07. **Scope:** read-only source follow-up for research, shopping, product-video, and macro authority gates. No source files were edited. + +1. **Research auto/social contamination and alert scoping: proven.** + - Default research `auto` resolves to `web`, `community`, and `social`: `src/providers/research-compiler.ts:17`, `src/providers/research-compiler.ts:225-237`. That means ordinary omitted-source research can enter gated social providers. + - Research builds workflow alerts without selected provider ids: `src/providers/workflows.ts:6714-6717`. Shopping already passes scoped provider ids, which shows the intended shape: `src/providers/workflows.ts:6863-6865`. + - `buildWorkflowAlerts` falls back to global `buildAlerts()` when runtime snapshots are absent: `src/providers/workflows.ts:588-612`, while `buildAlerts()` iterates the global provider signal map: `src/providers/workflows.ts:493-571`. + - Research gate treats workflow alerts as blocking diagnostics for pass: `src/providers/research-report/gate.ts:78-91`, `src/providers/research-report/gate.ts:98-105`, `src/providers/research-report/gate.ts:139-160`. + - Smallest fixes: change `RESEARCH_AUTO_SOURCES` to `web,community` while preserving explicit `all` for social, pass effective research provider ids into `buildWorkflowAlerts`, and filter the global fallback by provider id. + - Tests/docs to add or change: update `tests/providers-research-executor.test.ts` auto-source expectations; add web-only research alert-scoping tests for runtime snapshots and global fallback; update `docs/CLI.md`, `src/public-surface/source.ts`, generated manifests, `skills/opendevbrowser-best-practices/SKILL.md`, and `skills/opendevbrowser-research/SKILL.md` to say social is explicit opt-in. + +2. **Shopping partial/pass messaging: partially proven, mostly a response-shape gap.** + - The gate already has `pass`, `partial`, and `fail`, with strict pass checks for availability, relevance, freshness, price trust, baseline, diagnostics, provider failures, filter diagnostics, primary constraints, and advisory region: `src/providers/shopping-report/gate.ts:88-114`. + - `partial` is intentionally constrained, not a confident buy recommendation: `src/providers/shopping-report/gate.ts:116-123`. Docs already describe this: `docs/CLI.md:517-523`, `skills/opendevbrowser-shopping/SKILL.md:57-66`. + - Mismatch: gate counters include all assessments for unknown availability, weak relevance, suspicious titles, freshness, and price trust: `src/providers/shopping-report/gate.ts:49-86`, but criteria text says some checks apply to the "confident set": `src/providers/shopping-report/gate.ts:132-161`. + - JSON shopping responses return raw `offers` and `meta` only, not a first-class `buyingReadiness` field: `src/providers/renderer.ts:1004-1013`. Compact mode includes readiness in summary lines: `src/providers/renderer.ts:944-953`, so mode parity is uneven. + - Smallest fixes: expose `buyingReadiness` from `briefing.gate` in shopping responses and meta for all render modes, make CLI completion print `Buying readiness: pass|partial|fail`, and either count only non-excluded candidate/confident assessments or change criteria wording to "current evidence set". + - Tests/docs to add or change: add a gate-scope test with clean candidates plus an excluded weak or out-of-stock offer; add JSON/compact/context/md readiness parity tests; add workflow-message tests for shopping partial. Update `docs/CLI.md` and shopping skill docs only if gate scope or JSON semantics change. + +3. **Product-video interstitial/default readiness guidance: proven as guidance visibility, not gate failure.** + - Compiler defaults are readiness-producing: `include_screenshots`, `include_all_images`, and `include_copy` default to `true`: `src/providers/product-video-compiler.ts:219-222`. + - Readiness still becomes `partial` when pass evidence is thin, visuals are absent, copy is omitted, or title fallback is used: `src/providers/product-video-presentation/gate.ts:43-47`. + - Workflow writes readiness surfaces into `presentation-readiness.json`, `product.json`, and `manifest.readiness`: `src/providers/workflows.ts:7817-7866`. + - Product-video handoff emits generic followthrough and suggested steps, but no typed `nextStepGuidance` equivalent to research or Inspiredesign: `src/providers/workflow-handoff.ts:448-510`, `src/providers/workflow-handoff.ts:552-590`. + - Best Buy interstitial guidance exists in provider constraints: `src/providers/constraint.ts:318-327`, `src/providers/constraint.ts:392-397`. Product-video meta records `primaryConstraint` and `primaryConstraintSummary`: `src/providers/workflows.ts:7906-7947`, but that provider guidance is not threaded into the product-video handoff input at `src/providers/workflows.ts:7908-7919`. + - Smallest fixes: add typed product-video readiness guidance in `src/providers/workflow-handoff.ts`, include status, reason codes, validation checks, and do-not-proceed conditions, and thread `primaryIssue?.guidance` into product-video suggested steps. + - Tests/docs to add or change: add product-video workflow tests asserting partial/fail outputs include typed guidance; add a Best Buy `bestbuy_international_gate` test asserting the country-selection recovery text is surfaced in handoff guidance; update `docs/CLI.md`, `docs/SURFACE_REFERENCE.md`, `src/public-surface/source.ts`, generated manifests, and `skills/opendevbrowser-product-presentation-asset/SKILL.md`. + +4. **Macro execute blocked/unblocked authority: proven blocker-only, with documentation gap.** + - CLI returns top-level `success: true` when the daemon call completes: `src/cli/commands/macro-resolve.ts:254-267`. + - Execution status lives under `execution.meta.ok`, `partial`, `failures`, and optional `execution.meta.blocker`: `src/macros/execute.ts:416-433`. + - CLI fallback messaging checks only `execution.meta.blocker`: `src/cli/commands/macro-resolve.ts:85-105`. + - Daemon and tool handoff both pass `blocked: Boolean(execution.meta.blocker)`: `src/cli/daemon-commands.ts:2335-2343`, `src/tools/macro_resolve.ts:158-168`. Handoff branches only on that blocked flag: `src/providers/workflow-handoff.ts:514-543`. + - This means `execution.meta.ok=false` with no blocker is unblocked authority but not provider success. That can be valid, but docs currently only say to inspect blockers: `docs/CLI.md:919-938`, `src/public-surface/source.ts:903-907`. + - Smallest fixes: keep blocker-only authority if intended, but clarify wording to require checking `execution.meta.blocker`, `execution.meta.ok`, `execution.partial`, and `execution.failures` before treating output as complete. Optionally rename the handoff input to `authorityBlocked`. + - Tests/docs to add or change: add CLI and handoff tests for unblocked failed execution with `ok=false` and no blocker; update `docs/CLI.md`, `src/public-surface/source.ts`, generated manifests, and macro docs to separate transport success, blocker authority, and provider success. + +### Pinterest Authority Follow-Up + +**Scope:** Read-only follow-up against `/Users/bishopdotun/.t3/worktrees/opendevbrowser/t3code-985b0845` at `184472c`. Three focused explore probes were used and load-bearing claims were spot-checked in source. No source code was edited. + +**Verdict summary:** Refute full-workflow extension-auth-first as the current default. Prove canonical final pin-media capture defaults to extension only after a canonical pin exists. Prove managed profile authority is dropped in the final pin-media capture launch. Support that `login_or_challenge_state` is too permissive for product authority, and that `interface_chrome_shell` is intentionally permissive only for strict canonical byte-backed evidence. + +1. **Extension-auth first default: refuted for Pinterest harvest discovery, partially true for canonical final capture.** + - CLI parsing records `--browser-mode`, `--profile`, `--use-cookies`, `--challenge-automation-mode`, and `--cookie-policy` only when supplied: `src/cli/commands/inspiredesign.ts:256-326`. The daemon payload forwards those parsed values as-is, so default harvest sends `browserMode`, `profile`, `useCookies`, `challengeAutomationMode`, and `cookiePolicyOverride` as undefined: `src/cli/commands/inspiredesign.ts:378-397`. + - Runtime fallback defaults still choose managed headed for `social`; only `shopping` is extension-first by source family: `src/providers/runtime-policy.ts:21-25`. Explicit `--browser-mode extension` and `--browser-mode managed` narrow fallback modes, but undefined mode falls back through source defaults: `src/providers/runtime-policy.ts:28-37`, `src/providers/runtime-policy.ts:54-64`. + - Pinterest guidance recommends extension-auth, but it is guidance rather than an applied runtime default. The recipe command contains `--browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper`: `src/guidance/recipes/pinterest.ts:15-28`. The recipe itself is only `authenticated_preferred`: `src/guidance/recipes/pinterest.ts:160-185`. + - Site-recipe discovery fetches through the generic site-recipe fetch options with `source: "web"`: `src/providers/workflows.ts:2195-2247`, while the separate auth metadata is resolved from the Pinterest recipe id and caller-provided runtime policy only: `src/providers/workflows.ts:2478-2502`. Neither seam injects extension, cookies, or required cookie policy by default. + - The partial true case is the final canonical pin-media helper. Exact canonical Pinterest pin capture resolves undefined or `auto` mode to `extension`, while preserving explicit `managed`: `src/inspiredesign/capture.ts:943-949`, then warmup and primary capture use that resolved mode: `src/inspiredesign/capture.ts:1133-1163`. The primary launch uses `headless: options.browserMode !== "extension"` and `noExtension: shouldForceManagedPrimaryCapture(options.browserMode)`: `src/inspiredesign/capture.ts:1017-1024`. + - Smallest fix: centralize a Pinterest harvest default resolver in `normalizeInspiredesignInput` or the daemon command path so `social/pinterest` harvest with no explicit `browserMode`, no explicit `profile`, and no explicit cookie policy defaults to extension, cookies enabled, required cookie policy, and browser helper when the daemon has current extension readiness. Do not apply this to explicit managed/profile runs or non-Pinterest providers. + - Tests to add/change: add a CLI/workflow test that default `inspiredesign harvest --provider social/pinterest --query ...` receives the intended extension-auth defaults once implemented, while explicit `--browser-mode managed` and `--profile` remain managed. Add a capture unit test around `tests/providers-inspiredesign-capture.test.ts:1006-1079` locking the existing canonical pin behavior: undefined or `auto` uses extension, explicit managed uses managed. + +2. **Managed profile dropped in final pin-media capture: proven.** + - Workflow-level inputs include and preserve `profile`: `src/providers/workflows.ts:268-272`, `src/providers/workflows.ts:358-368`. `withProfileOverride` also treats a named profile as managed and forces managed mode when no browser mode is supplied: `src/providers/workflows.ts:949-957`. Trusted managed profile provenance is only accepted when mode is absent or managed: `src/providers/workflows.ts:960-970`. + - Pin-media workflow capture forwards `profile: workflowInput.profile` into the capture callback: `src/providers/workflows.ts:3140-3163`. The tool and daemon adapters then spread those options into `captureInspiredesignPrimaryPinMediaEvidenceFromManager`: `src/tools/inspiredesign_run.ts:132-136`, `src/cli/daemon-commands.ts:959-963`. + - The concrete `InspiredesignPrimaryPinMediaCaptureOptions` type drops `profile`: `src/inspiredesign/capture.ts:124-131`. The actual primary launch also omits `profile` and hardcodes `persistProfile: false`: `src/inspiredesign/capture.ts:1017-1024`. The final byte capture uses the session returned by that launch: `src/inspiredesign/capture.ts:1155-1184`. + - `BrowserManager.launch` supports `profile`, but for headless managed launches `persistProfile` defaults false, which sends the session to a temp profile rather than the named managed profile: `src/browser/browser-manager.ts:137-146`, `src/browser/browser-manager.ts:1769-1788`. Therefore passing `profile` alone would not fix final managed capture unless persistent profile use is also requested. + - Smallest fix: add `profile?: string` to `InspiredesignPrimaryPinMediaCaptureOptions` and the shared primary capture option shape used by `launchPrimaryCaptureSession`; for `browserMode === "managed" && profile`, launch with `profile`, `persistProfile: true`, and `noExtension: true`. Leave default and extension capture behavior unchanged. + - Tests to add/change: add a focused regression in `tests/providers-inspiredesign-capture.test.ts` near `tests/providers-inspiredesign-capture.test.ts:1624-1703` that calls `captureInspiredesignPrimaryPinMediaEvidenceFromManager` with canonical Pinterest URL, `browserMode: "managed"`, and `profile: "pinterest-design"`; assert one launch, no extension warmup, and launch options include `headless: true`, `profile: "pinterest-design"`, `persistProfile: true`, and `noExtension: true`. Optionally add a workflow-level test proving `runInspiredesignWorkflow` forwards `profile` to `capturePinMediaEvidence`, although source already does at `src/providers/workflows.ts:3140-3163`. + +3. **`login_or_challenge_state` and `interface_chrome_shell` warning exceptions: too permissive for login, deliberately narrow but still risky for interface chrome.** + - `login_or_challenge_state` is globally exempted from pin-media blocking warning markers: `src/inspiredesign/pinterest-pin-media-evidence.ts:180-182`. Both warning gates return non-blocking for that marker: `src/inspiredesign/pinterest-pin-media-evidence.ts:765-783`. + - Reference authority also allows pin-media readiness when the only diagnostic reason is `login_or_challenge_state`: `src/inspiredesign/reference-pattern-board.ts:713-741`. The later diagnostic blocker helper repeats that login-only exception: `src/inspiredesign/reference-pattern-board.ts:952-980`. + - `interface_chrome_shell` is more constrained, but it is still allowed when the evidence passes strict canonical byte-backed checks: `src/inspiredesign/pinterest-pin-media-evidence.ts:740-783`. Product readiness then accepts pin-media entries when `hasPinterestPinMediaAuthorityBlockingWarning` returns false: `src/inspiredesign/product-readiness.ts:439-466`, `src/inspiredesign/product-readiness.ts:780-827`. + - Current tests explicitly codify the permissive behavior. `login_or_challenge_state` and strict `interface_chrome_shell` index entries are accepted as authoritative in `tests/inspiredesign-product-readiness.test.ts:1139-1173`. Pin-media evidence tests keep strict interface chrome authoritative in `tests/inspiredesign-pinterest-pin-media-evidence.test.ts:469-480` and malformed interface chrome blocking in `tests/inspiredesign-pinterest-pin-media-evidence.test.ts:494-529`. Workflow tests produce product-ready outputs despite login or interface warnings at `tests/providers-inspiredesign-workflow.test.ts:1829-2023`. + - Smallest fix: make `login_or_challenge_state` authority-blocking. Remove it from `NON_BLOCKING_PIN_MEDIA_WARNING_MARKERS`, or at minimum stop exempting it in `hasAuthorityBlockingWarning`; tighten `hasPinMediaReadyPinterestEvidence` so any diagnostic reason blocks pin-media readiness unless a new explicit authenticated-provider proof is introduced. + - Recommended stance on `interface_chrome_shell`: keep the strict byte-backed exception only if the product accepts chrome text as a viewport artifact warning. If tightened, do it separately from login so valid first-party bytes are not regressed unnecessarily. + - Tests to add/change: change `tests/inspiredesign-product-readiness.test.ts:1151-1154` to expect login-warning pin media not authoritative. Add a reference-board or workflow fixture near `tests/providers-inspiredesign-workflow.test.ts:1829-2023` proving a canonical Pinterest reference with valid bytes plus `warnings: ["login_or_challenge_state"]` remains diagnostic-only and is rejected with that reason. Keep capture-layer diagnostic warning tests such as `tests/providers-inspiredesign-capture.test.ts:2043-2145`, because capture should report observed login/search/chrome state without itself deciding product authority. + +## Investigation Log + +### Bootstrap - Workspace And Scope +**Hypothesis:** The current investigation must use the T3 worktree, not a stale main checkout. +**Findings:** The T3 worktree is `/Users/bishopdotun/.t3/worktrees/opendevbrowser/t3code-985b0845` on branch `t3code/pinterest-harvest-fix` at `184472c8945019569053bb9827f872a4eea913ef`, matching `origin/main`. RepoPrompt CLI has a window for `/Users/bishopdotun/Documents/DevProjects/opendevbrowser`, not this T3 worktree, so all file and git evidence must be verified against the T3 path. +**Evidence:** `git rev-parse --show-toplevel`; `git branch --show-current`; `git rev-parse HEAD`; `rpce-cli -e 'windows'`; `rpce-cli -w 2 -e 'tree --type roots'`. +**Conclusion:** Continue read-only investigation against the T3 worktree and use RepoPrompt only where it cannot confuse checkout state. + +## Root Cause +The root cause is not one thing named "Pinterest harvest failed." It is a cross-workflow pattern where transport completion, browser continuity, provider auth/source quality, and product authority are being conflated: + +1. **Transport success:** the CLI and daemon can complete the request, write artifacts, and return a structured response. +2. **Browser continuity:** OpenDevBrowser can have a current daemon, extension handshake, cookies, or a declared managed profile. +3. **Provider auth and source quality:** Pinterest can still show a login wall, research `auto` can pull in gated social providers, shopping can produce usable but partial offers, product-video can hit marketplace interstitials, and Inspiredesign can capture pages that are off-brief or lack promoted authority. +4. **Product authority:** each workflow has its own authority gate. Inspiredesign needs `product_ready` plus `snapshot_ready`, `motion_ready`, or `pin_media_ready`; Pinterest specifically needs `pin-media-index.json` entries backed by first-party persisted bytes, valid hashes, dimensions, content type, source/reference matching, and no active blockers. Research needs an evidence gate, shopping needs a buying readiness gate, product-video needs presentation/product-video pass readiness, and macro execute needs no blocker. + +Current HEAD intentionally fails closed at layer 4 when Pinterest login or manual recovery prevents first-party pin media authority. That is the right default. The likely code defect is that the final pin-media capture session can discard the requested managed profile even though the workflow accepted it, which weakens the documented `--browser-mode managed --profile ... --use-cookies --cookie-policy required` recovery path. + +The live runs add a sharper default/fallback diagnosis: Pinterest's explicit extension-auth path works on this machine, but default `inspiredesign harvest --provider social/pinterest` does not enter that path automatically. The default social fallback policy is managed-headed, not extension-first. Extension-first means defaulting Pinterest harvest to `--browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper` when the daemon reports a current connected extension. The blocked default run produced guidance for a manual extension rerun instead of switching modes itself. + +The broader workflow audit adds three more implementation targets. Research defaults should avoid pulling gated social providers into ordinary public research unless the user asks for them, and workflow alerts should be scoped to selected sources. Product-video needs readiness-aware source fallback and interstitial recovery, not merely a different browser mode. Non-Pinterest Inspiredesign harvests can capture manifest-backed screenshots yet still report zero snapshot-ready authority, so the screenshot authority-promotion seam needs a focused fix before default product-ready design handoff can be reliable. + +## Recommendations +1. Make Pinterest harvest extension-auth first by default, meaning default `inspiredesign harvest --provider social/pinterest` to `--browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper` when the daemon reports extension connected and handshaken, unless the user explicitly requests another mode. +2. If automatic switching is implemented, preserve fail-closed semantics: only switch to extension for `social/pinterest`, only when extension readiness is current, and only report product readiness when `pin-media-index.json` has manifest-backed first-party bytes. +3. Thread `profile` through `InspiredesignPrimaryPinMediaCaptureOptions` and into the primary pin-media `manager.launch(...)` call for managed sessions. Add a regression test that a workflow `profile` reaches the final pin-media capture launch. +4. Add Pinterest provider auth proof clarity. Distinguish `profile_declared`, `cookies_observable`, `live_extension`, and actual Pinterest logged-in state in diagnostics without leaking private account identifiers. +5. Tighten or re-review the current warning exceptions. `login_or_challenge_state` and strict-byte-backed `interface_chrome_shell` are currently allowed to remain non-blocking under some conditions. Require stronger auth provenance if those warnings are present. +6. Improve user-facing diagnostics for this exact failure. When transport succeeds but `pin-media-index.json` is empty or absent, report the missing authority gate directly and include the exact extension-auth rerun command when extension is available. +7. Keep the stale-state preflight in the runbook, but do not treat it as the active cause on this machine unless `fingerprintCurrent`, active binary version, or installed skill version drift again. +8. Add a workflow default resolver that can choose success-oriented defaults by workflow and provider: public managed for ordinary research, extension-auth for gated social/Pinterest when available, managed plus helper for shopping/product-video, and visual-evidence authority for Inspiredesign harvest. +9. Make every workflow response separate transport success from authority success. Recommended field names: research `evidenceGate.status`, shopping `buyingReadiness.status`, product-video `presentationReadiness.status` and `productVideoReadiness.status`, Inspiredesign `productSuccess/artifactAuthority/evidenceAuthority`, and macro `execution.meta.blocker`. +10. Fix non-Pinterest Inspiredesign screenshot authority promotion. A manifest-backed screenshot with matching reference id/source URL, valid hash, sufficient bytes, no blocking warnings, and ready guidance should count toward `snapshotReadyReferenceCount` and allow `product_ready` when all other checks pass. +11. Scope provider degradation and workflow alerts to selected sources/providers. A web-only research run should not stay partial because stale social provider degradation exists elsewhere in the runtime. +12. For product-video, detect marketplace interstitials and country selectors as source blockers with explicit recovery: use a current extension tab, choose an official product URL, or use a provider profile that has already cleared the country selector. Do not label the pack production-ready until both readiness surfaces are `pass`. + +## Preventive Measures +1. Add a workflow-level regression proving the Pinterest default mode decision. The test should fail if default `social/pinterest` silently drifts away from the intended policy, whether that policy becomes extension-first or explicit manual recovery. +2. Add a live-shape or integration-style regression for extension-auth Pinterest harvest that asserts accepted canonical pins can become `pin_media_ready` while supplemental visual screenshots remain non-authoritative. +3. Add a workflow-level regression that exercises a managed Pinterest profile through discovery, final pin-media capture setup, and product-readiness evaluation. +4. Add a diagnostic-only regression fixture where Pinterest returns login/manual recovery with no `pin-media-index.json`, and assert the top-level reason names the missing pin-media authority. +5. Add an auth-provenance assertion to Pinterest harvest tests so `provider_verified` cannot be implied by profile declaration or cookie observability alone. +6. Preserve the existing strict artifact contract: `media-analysis.json` and transport success must never substitute for `pin-media-index.json`. +7. Keep install-state checks explicit in troubleshooting: active binary path, repo package version, daemon `fingerprintCurrent`, extension handshake status, and loaded best-practices skill version. +8. Add one fixture and one live-smoke shape per provider workflow: research pass/partial/fail, shopping pass/partial/fail, product-video pass/partial/fail, Inspiredesign snapshot-ready/pin-media-ready/diagnostic-only, and macro execute blocked/unblocked. +9. Add docs drift tests that require reliable-path examples in `src/public-surface/source.ts`, `docs/CLI.md`, `docs/SURFACE_REFERENCE.md`, `skills/opendevbrowser-best-practices/SKILL.md`, and the relevant workflow skill packs to stay aligned. +10. Add workflow-message tests that assert authority failures are named directly in completion text, not hidden behind `success=true`. + +## Appendix: Review Scope And Evidence Limits +This report combines two scopes: + +1. Pinterest-specific diagnostic-only login and recovery analysis. +2. A broader workflow-default audit for `research`, `shopping`, `product-video`, `inspiredesign`, and `macro-resolve --execute`. + +Source review supports the default-mode, CLI pass-through, Pinterest authority, and workflow gate claims cited above. Live command outputs, artifact bundle contents, daemon status, installed binary paths, installed skill versions, and commit archaeology are external investigation evidence from this machine and this run. + +Where the report names implementation targets outside a directly inspected source seam, such as full workflow profile forwarding or non-Pinterest screenshot authority promotion, treat those as follow-up hypotheses to verify during implementation. No implementation or source-code changes were made as part of this investigation. diff --git a/docs/plans/workflow-authority-defaults-and-reporting-2026-07-07.md b/docs/plans/workflow-authority-defaults-and-reporting-2026-07-07.md new file mode 100644 index 00000000..5dbdd005 --- /dev/null +++ b/docs/plans/workflow-authority-defaults-and-reporting-2026-07-07.md @@ -0,0 +1,298 @@ +--- +title: Workflow Authority Defaults and Reporting +artifact_contract: ce-unified-plan/v1 +artifact_readiness: implementation-ready +execution: code +created: 2026-07-07 +product_contract_source: ce-plan-bootstrap +origin_document: docs/investigations/pinterest-diagnostic-only-login-recovery-2026-07-07.md +--- + +# Workflow Authority Defaults and Reporting + +## Product Contract + +### Problem Frame + +OpenDevBrowser workflows can return transport success while their real authority gates fail or produce diagnostic-only output. The current investigation proves this for Pinterest harvest and identifies related default and reporting gaps in research, shopping, product-video, Inspiredesign, and macro execution. + +### Requirements + +- R1: Pinterest harvest must prefer the useful authenticated path by default only when a current handshaken extension is available and the caller did not explicitly choose another transport, profile, cookie policy, or challenge mode. +- R2: Managed Pinterest profile recovery must preserve the named profile through final pin-media byte capture. +- R3: Pinterest login or challenge diagnostics must block product authority. Capture can report the diagnostic, but product readiness must fail closed. +- R4: Non-Pinterest manifest-backed screenshot evidence must be able to promote to `snapshot_ready` only when source provenance is trusted and matches the ranked reference. +- R5: Research defaults must be public-first and scoped so unrelated degraded providers cannot demote selected-source runs. +- R6: Shopping, product-video, and macro execution must report readiness or execution authority separately from top-level transport success. +- R7: Public docs, generated public surface files, skills, and tests must describe the real authority gates and reliable default paths. +- R8: Fixes must preserve existing authority boundaries: `pin-media-index.json` is Pinterest authority, `media-analysis.json` is advisory, and transport success is not product success. + +### Non-Goals + +- Do not make `media-analysis.json` authoritative. +- Do not broaden social provider defaults outside the Pinterest harvest resolver. +- Do not weaken readiness gates, suppress tests, or accept fallback semantics that hide failed authority gates. +- Do not edit root `AGENTS.md` unless behavior governance changes and maintainer approval is explicit. +- Do not decide the separate `interface_chrome_shell` product policy here. Preserve the current strict byte-backed exception unless review finds a narrow bug. + +### Acceptance Examples + +- AE1: Default `inspiredesign harvest --provider social/pinterest` uses extension, required cookies, and browser helper only when extension readiness is current and no explicit caller preference exists. +- AE2: Explicit `--browser-mode managed --profile pinterest-design --use-cookies --cookie-policy required` reaches final pin-media capture with `profile: "pinterest-design"` and `persistProfile: true`. +- AE3: A Pinterest pin-media artifact with `warnings: ["login_or_challenge_state"]` remains diagnostic-only even if bytes, hash, dimensions, and source URL are otherwise valid. +- AE4: A non-Pinterest harvest with a trusted temp screenshot and matching reference URL reports `artifactAuthority="product_ready"` and `evidenceAuthority="snapshot_ready"`. +- AE5: Research `auto` uses `web,community`; explicit `all` or `social` still includes social. +- AE6: Shopping JSON includes `buyingReadiness.status`; product-video includes presentation/product-video readiness; macro execution reports `ok`, `partial`, `failures`, and `blocker` semantics. + +Product Contract preservation: Product Contract created from the July 7 investigation report and user brief. No prior requirements-only unified plan was rewritten. + +## Implementation Units + +## Task 1 - Preserve Managed Profile In Final Pin-Media Capture +Reasoning: Workflow inputs already accept and forward `profile`, but the concrete final Pinterest pin-media capture launch drops it and uses a temporary managed profile. +What to do: Thread `profile` through primary pin-media capture options and launch explicit managed profile captures with persistent profile state. +How: +1. Add `profile?: string` to `InspiredesignPrimaryPinMediaCaptureOptions` and the internal primary capture launch option shape in `src/inspiredesign/capture.ts`. +2. When `browserMode === "managed"` and `profile` is present, call `manager.launch` with `profile`, `persistProfile: true`, `noExtension: true`, and managed headless behavior. +3. Preserve extension warmup and default canonical pin behavior for undefined or `auto` browser mode. +4. Add a failing-first regression near existing managed canonical pin capture tests, then implement the smallest production change. +Files impacted: `src/inspiredesign/capture.ts`, `tests/providers-inspiredesign-capture.test.ts`. +End goal: Managed profile recovery is a real authority path instead of only a declared workflow option. +Acceptance criteria: +- [ ] Managed canonical pin capture with `profile: "pinterest-design"` launches with that profile and `persistProfile: true`. +- [ ] Explicit managed mode still avoids extension warmup. +- [ ] Undefined and `auto` canonical pin capture still default to extension. +- [ ] `npm run test -- tests/providers-inspiredesign-capture.test.ts` passes. + +## Task 2 - Tighten Pinterest Login Warning Authority +Reasoning: `login_or_challenge_state` currently remains non-blocking in pin-media authority paths, which can let a login-tainted Pinterest artifact become product-ready. +What to do: Make `login_or_challenge_state` authority-blocking while leaving capture diagnostics intact. +How: +1. Remove the login warning from non-blocking authority behavior in `src/inspiredesign/pinterest-pin-media-evidence.ts`. +2. Update `src/inspiredesign/reference-pattern-board.ts` so login or challenge diagnostic reasons block `pin_media_ready`. +3. Prefer no change in `src/inspiredesign/product-readiness.ts`; use delegated helper behavior unless tests reveal a remaining bypass. +4. Update existing tests that currently codify login-warning authority, and add workflow-level diagnostic-only coverage. +Files impacted: `src/inspiredesign/pinterest-pin-media-evidence.ts`, `src/inspiredesign/reference-pattern-board.ts`, `tests/inspiredesign-product-readiness.test.ts`, `tests/inspiredesign-pinterest-pin-media-evidence.test.ts`, `tests/providers-inspiredesign-workflow.test.ts`. +End goal: Pinterest login/challenge state cannot satisfy product authority even when transport and byte capture succeed. +Acceptance criteria: +- [ ] Pin media with `login_or_challenge_state` is rejected as authoritative. +- [ ] Workflow fixture with valid bytes plus login warning remains `diagnostic_only`. +- [ ] Capture-layer warning emission tests still pass. +- [ ] Strict `interface_chrome_shell` behavior is unchanged. +- [ ] `npm run test -- tests/inspiredesign-product-readiness.test.ts tests/inspiredesign-pinterest-pin-media-evidence.test.ts tests/providers-inspiredesign-workflow.test.ts` passes. + +## Task 3 - Promote Trusted Non-Pinterest Screenshot Authority +Reasoning: Non-Pinterest harvests can persist manifest-backed screenshots yet fail `snapshot_ready` because captured visual metadata omits trusted source URL provenance. +What to do: Inject `sourceUrl: reference.url` only for trusted non-Pinterest finalization of captured temp screenshots. +How: +1. In `finalizeInspiredesignReferenceVisual` in `src/providers/workflows.ts`, add a narrow provenance fill before `persistInspiredesignVisualEvidence`. +2. Apply it only when the reference is non-Pinterest, the visual came from a trusted temp file, and `visual.sourceUrl` is absent. +3. Do not overwrite mismatched explicit `sourceUrl`. +4. Keep product-readiness checks strict and add workflow regression coverage. +Files impacted: `src/providers/workflows.ts`, `tests/providers-inspiredesign-workflow.test.ts`, optionally `tests/inspiredesign-product-readiness.test.ts`. +End goal: Valid non-Pinterest visual evidence can become `snapshot_ready` without weakening generic visual authority rules. +Acceptance criteria: +- [ ] Non-Pinterest harvest fixture reports `productSuccess=true`, `artifactAuthority="product_ready"`, and `evidenceAuthority="snapshot_ready"`. +- [ ] Counts include `snapshotReadyReferenceCount=1` and `authoritativeReferenceCount=1`. +- [ ] Missing or mismatched source provenance outside the trusted seam remains diagnostic. +- [ ] Pinterest provenance remains strict. +- [ ] `npm run test -- tests/providers-inspiredesign-workflow.test.ts tests/inspiredesign-product-readiness.test.ts` passes. + +## Task 4 - Add Pinterest Extension-Auth Default Resolver +Reasoning: Live validation shows explicit extension-auth Pinterest harvest can produce product-ready pin media, while default Pinterest harvest does not enter that path. +What to do: Apply extension-auth defaults for `social/pinterest` harvest only when runtime extension readiness is current and the caller did not specify browser/profile/cookie/challenge options. +How: +1. Add a pure resolver in `src/providers/workflows.ts` that identifies Pinterest harvest and explicit caller choices. +2. Expose or reuse a narrow runtime readiness method that reports extension current, connected, and handshaken status without account identity. +3. Before Inspiredesign discovery, apply `browserMode: "extension"`, `useCookies: true`, `cookiePolicyOverride: "required"`, and `challengeAutomationMode: "browser_with_helper"` when the resolver allows it. +4. Do not change `DEFAULT_PROVIDER_FALLBACK_MODES.social`. +5. Record default metadata so output can distinguish implicit defaults from explicit user settings. +Files impacted: `src/providers/workflows.ts`, `src/providers/runtime-factory.ts` or the daemon runtime construction seam, `src/cli/daemon-commands.ts` if readiness injection belongs there, `src/tools/inspiredesign_run.ts` if tool runtime exposes readiness, `tests/providers-inspiredesign-workflow.test.ts`, `tests/cli-workflows.test.ts` if payload expectations change. +End goal: Pinterest harvest defaults are useful for logged-in extension users while preserving explicit recovery choices and fail-closed authority. +Acceptance criteria: +- [ ] Default Pinterest harvest with current extension readiness applies extension, required cookies, and helper. +- [ ] Explicit managed mode, explicit profile, explicit cookie policy, or explicit challenge mode is preserved. +- [ ] Non-Pinterest harvest behavior is unchanged. +- [ ] Missing extension readiness produces recovery guidance rather than fake authority. +- [ ] Focused Inspiredesign workflow and CLI tests pass. + +## Task 5 - Scope Research Defaults And Alerts +Reasoning: Ordinary research defaults include social sources and unscoped workflow alerts can let stale social degradation demote public web-only results. +What to do: Make `auto` public-first and scope workflow alerts to effective selected providers. +How: +1. Change `RESEARCH_AUTO_SOURCES` in `src/providers/research-compiler.ts` to `["web", "community"]`. +2. Preserve explicit `all`, explicit `social`, and explicit `--sources social`. +3. In `src/providers/workflows.ts`, pass effective provider IDs into `buildWorkflowAlerts` for research. +4. Ensure fallback global alerts are filtered by provider ID when runtime snapshots are absent. +5. Keep `research-report/gate.ts` strict. +Files impacted: `src/providers/research-compiler.ts`, `src/providers/workflows.ts`, `tests/providers-research-executor.test.ts`, any focused research workflow tests discovered during implementation. +End goal: Default research can produce public-first useful output without hidden social contamination, while explicit social research remains available. +Acceptance criteria: +- [ ] `sourceSelection: "auto"` resolves to `web,community`. +- [ ] `sourceSelection: "all"` still includes social. +- [ ] Explicit social remains supported. +- [ ] Web-only or web/community runs do not inherit unrelated social alerts. +- [ ] `npm run test -- tests/providers-research-executor.test.ts` and related research workflow tests pass. + +## Task 6 - Surface Shopping Buying Readiness +Reasoning: Shopping already computes `pass`, `partial`, and `fail`, but JSON and completion surfaces do not expose the buying authority clearly enough. +What to do: Expose `buyingReadiness` consistently without changing transport success semantics. +How: +1. In `src/providers/renderer.ts`, include `buyingReadiness` in shopping JSON, context, and meta outputs. +2. In `src/providers/workflows.ts`, preserve readiness in final response metadata after manifest assembly. +3. In `src/cli/commands/shopping.ts`, add a concise completion suffix naming `buyingReadiness.status`. +4. If criteria wording is misleading, change wording to "current evidence set" rather than changing gate logic. +Files impacted: `src/providers/renderer.ts`, `src/providers/workflows.ts`, `src/cli/commands/shopping.ts`, shopping renderer/workflow tests, `tests/cli-workflows.test.ts`. +End goal: Users see whether a shopping output is a confident buying brief, constrained partial shortlist, or failed authority gate. +Acceptance criteria: +- [ ] JSON includes first-class `buyingReadiness.status`. +- [ ] Other render modes expose equivalent readiness. +- [ ] CLI output separates transport success from buying readiness. +- [ ] Existing shopping gate strictness is preserved. + +## Task 7 - Surface Product-Video Readiness And Provider Recovery +Reasoning: Product-video readiness artifacts exist, but provider interstitial and country-selector guidance is not threaded into handoff steps. +What to do: Thread primary provider issue guidance into product-video handoff and completion output. +How: +1. Extend product-video handoff input in `src/providers/workflow-handoff.ts` with primary constraint summary and provider guidance. +2. Pass `primaryIssue.summary` and `primaryIssue.guidance` from `src/providers/workflows.ts`. +3. In `src/cli/commands/product-video.ts`, include presentation and product-video readiness statuses in completion text. +4. Keep gate behavior unchanged and production-ready only when readiness surfaces pass. +Files impacted: `src/providers/workflow-handoff.ts`, `src/providers/workflows.ts`, `src/cli/commands/product-video.ts`, product-video workflow tests, `tests/cli-workflows.test.ts`. +End goal: Product-video users get precise next steps for partial/fail outputs instead of treating transport success as production-ready video authority. +Acceptance criteria: +- [ ] Partial or fail output names readiness status and reason codes. +- [ ] Best Buy or country-selection interstitial guidance appears in suggested steps when detected. +- [ ] Pass/fail readiness semantics remain unchanged. +- [ ] Focused product-video workflow and CLI tests pass. + +## Task 8 - Report Macro Execution Completeness +Reasoning: Macro blocker authority is separate from provider execution success, but current messaging does not consistently tell users to inspect `ok`, `partial`, and `failures`. +What to do: Keep blocker-only authority and report execution completeness explicitly. +How: +1. Extend macro handoff input in `src/providers/workflow-handoff.ts` with `ok`, `partial`, and failure count. +2. Pass execution status from `src/cli/daemon-commands.ts` and `src/tools/macro_resolve.ts`. +3. Update `src/cli/commands/macro-resolve.ts` fallback messaging for `ok=false` with no blocker. +4. Add tests for blocked, unblocked incomplete, and successful execution messages. +Files impacted: `src/providers/workflow-handoff.ts`, `src/cli/daemon-commands.ts`, `src/tools/macro_resolve.ts`, `src/cli/commands/macro-resolve.ts`, `tests/cli-macro-resolve.test.ts`. +End goal: Macro output distinguishes transport success, blocker authority, and provider execution success. +Acceptance criteria: +- [ ] `ok=false` with no blocker reports unblocked but incomplete execution. +- [ ] Blocker messages still prioritize blocker recovery. +- [ ] `ok=true` success remains concise and compatible. +- [ ] `npm run test -- tests/cli-macro-resolve.test.ts` passes. + +## Task 9 - Sync Docs, Skills, Generated Surface, And Guidance +Reasoning: Defaults and authority semantics are public behavior. Docs, skills, generated manifests, and agent guidance must not drift. +What to do: Update public guidance after behavior tests pass. +How: +1. Update `src/public-surface/source.ts` for Pinterest defaults, research source defaults, shopping readiness, product-video readiness, macro execution fields, and transport-vs-authority wording. +2. Regenerate public manifests with `node scripts/generate-public-surface-manifest.mjs`. +3. Update `docs/CLI.md` and `docs/SURFACE_REFERENCE.md`. +4. Update relevant skills: `skills/opendevbrowser-best-practices/SKILL.md`, `skills/opendevbrowser-research/SKILL.md`, `skills/opendevbrowser-shopping/SKILL.md`, and `skills/opendevbrowser-product-presentation-asset/SKILL.md`. +5. Update relevant nested `AGENTS.md` files only if behavior guidance changes and the edit is not root governance. +Files impacted: `src/public-surface/source.ts`, `src/public-surface/generated-manifest.ts`, `src/public-surface/generated-manifest.json`, `docs/CLI.md`, `docs/SURFACE_REFERENCE.md`, relevant `skills/*/SKILL.md`, possibly nested `AGENTS.md` files. +End goal: Public and agent-facing guidance teaches working defaults and authority stop conditions. +Acceptance criteria: +- [ ] Docs and skills separate transport success from authority success. +- [ ] Generated manifests match `src/public-surface/source.ts`. +- [ ] No root `AGENTS.md` edit is made without explicit approval. +- [ ] Docs drift checks or relevant generated-surface tests pass. + +## Task 10 - Verify Authority Gates And Land Safely +Reasoning: Tests prove branches, but the minimum closeout criterion is useful authority-gate output from real workflow surfaces. +What to do: Run focused tests, full quality gates, live authority smoke workflows, review loops, atomic commits, PR checks, and merge only when gates are real. +How: +1. Run focused suites for each changed area. +2. Run full gates: `npm run lint`, `npm run typecheck`, `npm run test`, `npm run build`, `npm run extension:build`, and `npm run version:check` if available. +3. Run live smokes with isolated output dirs and save evidence paths. +4. Use RepoPrompt and code review loops for adversarial review. Apply findings, rerun affected tests and smokes, then repeat until clear. +5. Commit atomically with Conventional Commit style and required `Co-authored-by: Codex ` footer. +6. Push, open PR, monitor checks, fix failures, rerun workflows, and merge only after checks and authority smokes pass. +Files impacted: Git metadata, PR metadata, and evidence artifacts under ignored output roots. No tracked source files beyond prior tasks unless review fixes require them. +End goal: The branch is landable and merged with real authority proof, not just passing transport or unit tests. +Acceptance criteria: +- [ ] Focused and full quality commands pass with zero errors and zero warnings. +- [ ] Live Pinterest extension default smoke records useful `pin_media_ready` only when authority artifacts exist. +- [ ] Research smoke shows public-first source selection and no unrelated social alert demotion. +- [ ] Shopping smoke records visible buying readiness. +- [ ] Product-video smoke records readiness status and provider recovery guidance. +- [ ] Inspiredesign non-Pinterest smoke records `snapshot_ready` when artifact authority is valid. +- [ ] Macro smoke records execution completeness fields. +- [ ] Review loop reports no blocking findings. +- [ ] PR checks pass and merge completes. + +## File-By-File Implementation Sequence + +1. `src/inspiredesign/capture.ts` and `tests/providers-inspiredesign-capture.test.ts` for managed profile capture. +2. `src/inspiredesign/pinterest-pin-media-evidence.ts`, `src/inspiredesign/reference-pattern-board.ts`, and Pinterest authority tests. +3. `src/providers/workflows.ts` and Inspiredesign workflow tests for non-Pinterest screenshot provenance. +4. `src/providers/workflows.ts` plus runtime readiness seam for Pinterest extension-auth defaults and workflow tests. +5. `src/providers/research-compiler.ts`, `src/providers/workflows.ts`, and research tests. +6. `src/providers/renderer.ts`, `src/providers/workflows.ts`, `src/cli/commands/shopping.ts`, and shopping tests. +7. `src/providers/workflow-handoff.ts`, `src/providers/workflows.ts`, `src/cli/commands/product-video.ts`, and product-video tests. +8. `src/providers/workflow-handoff.ts`, `src/cli/daemon-commands.ts`, `src/tools/macro_resolve.ts`, `src/cli/commands/macro-resolve.ts`, and macro tests. +9. Public docs, skills, generated manifests, and nested guidance docs. +10. Review fixes, quality gates, live smokes, commits, PR, checks, and merge. + +## Dependency Mapping + +| Task | Depends on | Blocks | Can parallelize with | +| --- | --- | --- | --- | +| Task 1 | none | Pinterest managed-profile live smoke | Tasks 2, 3, 5 | +| Task 2 | none | Pinterest docs and authority claims | Tasks 1, 3, 5 | +| Task 3 | none | Inspiredesign snapshot-ready live smoke | Tasks 1, 2, 5 | +| Task 4 | runtime readiness seam | Pinterest docs and default live smoke | Tasks 6, 7, 8 | +| Task 5 | none | Research docs and live smoke | Tasks 1, 2, 3 | +| Task 6 | none | Shopping docs and live smoke | Tasks 7, 8 | +| Task 7 | existing provider guidance | Product-video docs and live smoke | Tasks 6, 8 | +| Task 8 | none | Macro docs and live smoke | Tasks 6, 7 | +| Task 9 | Tasks 1-8 behavior settled | Final gates and PR | none | +| Task 10 | Tasks 1-9 | Merge | none | + +Critical path: Task 4 runtime readiness, Task 9 docs/generated sync, Task 10 live smokes and PR checks. + +## Test Command Inventory + +Focused commands: + +```bash +npm run test -- tests/providers-inspiredesign-capture.test.ts +npm run test -- tests/inspiredesign-pinterest-pin-media-evidence.test.ts +npm run test -- tests/inspiredesign-product-readiness.test.ts +npm run test -- tests/providers-inspiredesign-workflow.test.ts +npm run test -- tests/providers-research-executor.test.ts +npm run test -- tests/cli-workflows.test.ts +npm run test -- tests/cli-macro-resolve.test.ts +``` + +Full gates: + +```bash +npm run lint +npm run typecheck +npm run build +npm run extension:build +npm run version:check +npm run test +``` + +Generated sync: + +```bash +node scripts/generate-public-surface-manifest.mjs +``` + +## Live Authority Smoke Gates + +- Pinterest default extension-auth: run default `inspiredesign harvest --provider social/pinterest` with daemon extension current and inspect `productSuccess`, `artifactAuthority`, `evidenceAuthority`, `ranked-references.json`, and `pin-media-index.json`. +- Pinterest managed profile recovery: run managed profile harvest and verify final capture uses the named persistent profile; output remains diagnostic-only if login/challenge is observed. +- Research public-first: run omitted-source research and verify sources are `web,community` and no unrelated social alert demotion occurs. +- Shopping readiness: run representative shopping JSON workflow and verify visible `buyingReadiness.status` and constrained partial wording when partial. +- Product-video readiness: run official product URL and interstitial-prone marketplace URL, verifying readiness surfaces and provider recovery guidance. +- Inspiredesign non-Pinterest snapshot: run harvest with required visual evidence and verify `snapshot_ready` only when manifest-backed source provenance is valid. +- Macro execution completeness: run or fixture an unblocked incomplete macro and verify `ok=false`, `partial`, and `failures` are visible. + +## Version History + +- 2026-07-07 v1: Created implementation-ready plan from the Pinterest diagnostic and broader workflow-default investigation. diff --git a/skills/opendevbrowser-best-practices/SKILL.md b/skills/opendevbrowser-best-practices/SKILL.md index 3c684ccc..bcb0a936 100644 --- a/skills/opendevbrowser-best-practices/SKILL.md +++ b/skills/opendevbrowser-best-practices/SKILL.md @@ -126,7 +126,7 @@ Rules: - treat `research run` as provider-constrained and low-level; inspect `summary.md`, `report.md`, `records.json`, `context.json`, `meta.json`, and `bundle-manifest.json` before publishing claims - use `--source-selection` only to explain selector semantics; use explicit `--sources web,community` for public topical examples - add shopping only with `--source-selection shopping` or explicit `--sources ...shopping...` when the task is deliberately commercial -- in the current contract, `auto` and `all` both resolve to `web`, `community`, and `social`, but neither value guarantees reliability +- `--source-selection auto` resolves to public-first `web,community`; use `all`, `social`, or explicit social sources only when social evidence is intentional 3. Deterministic shopping reruns with explicit providers. @@ -138,6 +138,7 @@ npx opendevbrowser shopping run --query "wireless earbuds" --providers shopping/ Rules: - use explicit providers plus `--browser-mode managed` for the most reproducible reruns +- inspect `buyingReadiness.status`; transport success and emitted offers are not enough for buying guidance when readiness is `partial` or `fail` - treat `--region` as advisory unless `meta.selection.region_authoritative=true` - inspect `meta.primaryConstraintSummary` first on no-offer runs - if `meta.primaryConstraint.guidance` is present, follow `meta.primaryConstraint.guidance.reason` and `meta.primaryConstraint.guidance.recommendedNextCommands[]` @@ -157,11 +158,11 @@ Rules: - use repeated `--url` flags instead of packed URL strings - provide `--query` or at least one `--url` for `inspiredesign harvest`; use `--query` when provider discovery is part of the task - use `inspiredesign harvest` when visual reference discovery, screenshot PNG artifacts, ranked references, metadata-only visual JSON, deterministic `media-analysis.json`, `meta-prompt.md`, or motion-design follow-through is required -- treat `social/pinterest` as a browser-native site recipe, not a default full social provider; for non-Google logged-in Pinterest search, prefer a dedicated managed headed profile with `--profile `, cookies, and `--cookie-policy required`; reserve extension `/ops` for live active-tab reuse and user-owned Google OAuth continuity; use registry-backed explicit CDP profiles through `cdp-profile start` plus `connect --profile` for browser/session primitives until provider workflows expose an explicit-CDP transport selector +- treat `social/pinterest` as a browser-native site recipe, not a default full social provider; default Pinterest harvest applies extension auth only when `extensionAuthReady` is current and the caller has not set incompatible auth transport or cookie settings; explicit `browserMode`, `profile`, `useCookies`, `cookiePolicyOverride`, or `challengeAutomationMode` values other than `browser_with_helper` opt out, while explicit `challengeAutomationMode=browser_with_helper` is compatible and may merge with the implicit extension, cookies, and required cookie policy defaults; for managed recovery, use a dedicated headed profile with `--profile `, cookies, and `--cookie-policy required`; use registry-backed explicit CDP profiles through `cdp-profile start` plus `connect --profile` for browser/session primitives until provider workflows expose an explicit-CDP transport selector - for multi-pin Pinterest design harvests, run query discovery with `--query ... --provider social/pinterest` and trust the broad-query harvest only when query-discovered canonical `https://www.pinterest.com/pin//` URLs become ranked references with manifest-backed first-party pin-media bytes in the same bundle; omitted `--output-dir` runs return `artifact_path`, so inspect that path before assuming `.opendevbrowser/inspiredesign/`; if the query bundle remains diagnostic-only, read `meta.discovery.acceptedUrls` and `discovery-diagnostics.json`, keep only canonical pin URLs, then use one canonical recovery harvest per selected pin with omitted `--output-dir` -- `discovery-diagnostics.json` records accepted and rejected URL counts, blocker diagnostics, and recovery actions; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence +- `discovery-diagnostics.json` records accepted and rejected URL counts, blocker diagnostics, and recovery actions; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence, and `login_or_challenge_state` blocks Pinterest product authority - canonical Pinterest pin harvests and recovery paths use `captureMode=off` and open the exact canonical pin in the active managed or extension workflow session before byte-backed pin-media extraction, which is required for reliable GIF and video pin capture in live sessions -- trust multi-pin Pinterest harvest outputs only when top-level `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, `evidenceAuthority=pin_media_ready`, `ranked-references.json` is non-empty, and `pin-media-index.json` proves pin-media-first manifest-backed authority for the selected pin; `snapshot_ready` and `motion_ready` are not substitutes for canonical pin-media readiness, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media when authority is complete, `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority +- trust multi-pin Pinterest harvest outputs only when top-level `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, `evidenceAuthority=pin_media_ready`, `ranked-references.json` is non-empty, and `pin-media-index.json` proves pin-media-first manifest-backed authority for the selected pin; `snapshot_ready` and `motion_ready` are not substitutes for canonical pin-media readiness, `login_or_challenge_state` blocks authority, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media when authority is complete, `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority - treat `media-analysis.json` as a deterministic design-fact surface from trusted saved pin media, not as readiness authority; `pin-media-index.json` remains the only pin-media readiness and provenance authority - use `media-analysis.json` for palette, tone, layout, OCR-free typography structure, text-region layout, sampled saved-media motion facts, `motionSignature`, limitations, and non-goals; do not claim readable text extraction, exact copy, font families, OCR, model vision, Tesseract, OpenCV, Sharp, browser canvas analysis, browser replay evidence, interaction choreography, new dependencies, or raw `mediaAnalysis` in `canvas-plan.request.json` - for richer `media-analysis.json`, prefer hosts with FFmpeg and FFprobe available; they are recommended optional host tools, are not bundled static binaries, and are not downloaded by default diff --git a/skills/opendevbrowser-product-presentation-asset/SKILL.md b/skills/opendevbrowser-product-presentation-asset/SKILL.md index af7c4f32..a30a0a75 100644 --- a/skills/opendevbrowser-product-presentation-asset/SKILL.md +++ b/skills/opendevbrowser-product-presentation-asset/SKILL.md @@ -54,7 +54,7 @@ Expected output pack always includes: - `copy.md` and `features.md`, which are production input only when readiness allows it - `raw/source-record.json` for auditability and raw evidence preservation -Workflow JSON output also exposes `product.presentationReadiness`, `product.productVideoReadiness`, `meta.presentationReadiness`, and `meta.productVideoReadiness` so callers can gate automation without opening files first. +Workflow JSON output also exposes `product.presentationReadiness`, `product.productVideoReadiness`, `meta.presentationReadiness`, and `meta.productVideoReadiness` so callers can gate automation without opening files first. CLI completion text includes both readiness statuses when available, so transport success is not production authority. When visual capture succeeds, the pack may also include: - `images/` for product stills @@ -78,7 +78,7 @@ Helper behavior: 1. Pick product URL or product name. 2. Before daemon-backed `product-video run` workflows, run `opendevbrowser status --daemon --output-format json` and continue only when `data.fingerprintCurrent === true`. 3. Run collection workflow and confirm output pack path. -4. Review `presentation-readiness.json`, `manifest.readiness.presentation`, `manifest.readiness.productVideo`, `product.json.presentationReadiness`, `product.json.productVideoReadiness`, and returned `meta.presentationReadiness` when available. +4. Review `presentation-readiness.json`, `manifest.readiness.presentation`, `manifest.readiness.productVideo`, `product.json.presentationReadiness`, `product.json.productVideoReadiness`, returned `meta.presentationReadiness`, and returned `meta.productVideoReadiness` when available. 5. Confirm raw evidence remains preserved in `raw/source-record.json`, but do not treat raw marketplace text as verified production copy. 6. Run `./skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh` only after readiness review. Canonical helper path: `./skills/opendevbrowser-product-presentation-asset/scripts/render-video-brief.sh`. diff --git a/skills/opendevbrowser-research/SKILL.md b/skills/opendevbrowser-research/SKILL.md index 6cdc6970..ac3b5226 100644 --- a/skills/opendevbrowser-research/SKILL.md +++ b/skills/opendevbrowser-research/SKILL.md @@ -41,7 +41,7 @@ Load this skill before research tasks. Use it to plan source families, gather pr - Before daemon-backed `research run` workflows, run `opendevbrowser status --daemon --output-format json` and continue only when `data.fingerprintCurrent === true`. - Define timebox first (`--days` or `--from/--to`). - Choose explicit source families before invoking the CLI primitive: `web`, `community`, `social`, `shopping`, or a deliberate combination such as `web,community`. -- Treat `auto` as a source-family selector, not a reliability guarantee. +- Treat `auto` as public-first `web,community`, not a reliability guarantee. Use `all`, `social`, or explicit social sources only when social evidence is intentional. - Persist artifacts and return reproducible paths. - Mark unsupported claims as tentative or exclude them from the final answer. - Honor bounded retries and backoff windows under 429 pressure. @@ -130,7 +130,7 @@ opendevbrowser research run --topic "" --sources web,shopping --mode md ## Notes -- `auto` and `all` are selector values in the current source-family contract, not promises of reliable coverage. +- `auto` is the public-first selector for `web,community`; `all` is an explicit broader selector, not a reliability guarantee. - Use `--source-selection shopping` or explicit `--sources ...shopping...` to include shopping only when commercial intent is explicit. - Use `--mode path` with `scripts/write-artifacts.sh` when you need replayable handoff bundles. - For browser-backed release proof and mode sweeps, follow the canonical direct-run evidence policy in `../opendevbrowser-best-practices/SKILL.md`. diff --git a/skills/opendevbrowser-shopping/SKILL.md b/skills/opendevbrowser-shopping/SKILL.md index d3607254..f52eba33 100644 --- a/skills/opendevbrowser-shopping/SKILL.md +++ b/skills/opendevbrowser-shopping/SKILL.md @@ -48,18 +48,19 @@ Reliable defaults: Successful shopping bundles preserve separate user and audit surfaces: - `deals.md`: primary deterministic buying brief with a Buying Readiness Gate, Recommendation, Best Candidate Offers, Market Baseline, Warnings and Constraints, Excluded or Constrained Offers, and Evidence Appendix. +- `buyingReadiness` in JSON output: first-class buying authority gate with `status` values `pass`, `partial`, or `fail`. - `offers.json`: raw structured offer evidence. - `comparison.csv`: tabular provider, title, price, shipping, deal score, availability, and URL comparison, with appended currency and total-status audit fields. - `meta.json`: workflow diagnostics, selected providers, alerts, failures, and filter diagnostics. - `deals-context.json`: agent handoff context with `query`, report-derived `highlights`, raw `offers`, and `meta`. -`compact` and `context` modes summarize the report guidance. `json` mode still returns raw offers plus meta. +`compact` and `context` modes summarize the report guidance. `json` mode returns raw offers, `buyingReadiness`, and meta. ## Decision Model Use the runtime buying brief as the source of truth for purchase guidance: -1. Read the Buying Readiness Gate. +1. Read the Buying Readiness Gate in `deals.md` or `buyingReadiness.status` in JSON output. Transport success and emitted offers are not enough for purchase guidance. - `pass`: evidence supports bounded buying guidance for the current shortlist. - `partial`: offers are usable as a constrained shortlist, but evidence gaps limit confidence. - `fail`: no confident purchase recommendation is allowed. diff --git a/src/cli/commands/macro-resolve.ts b/src/cli/commands/macro-resolve.ts index 46441e06..95e4f910 100644 --- a/src/cli/commands/macro-resolve.ts +++ b/src/cli/commands/macro-resolve.ts @@ -90,6 +90,16 @@ const hasExecutionBlocker = (result: unknown): boolean => { return asRecord(meta?.blocker) !== null; }; +const hasIncompleteExecution = (result: unknown): boolean => { + const execution = asRecord(asRecord(result)?.execution); + const meta = asRecord(execution?.meta); + if (!execution || !meta || asRecord(meta.blocker)) { + return false; + } + const failures = Array.isArray(execution.failures) ? execution.failures : []; + return meta.ok === false || meta.partial === true || failures.length > 0; +}; + const buildMacroResolveMessage = (execute: boolean, result: unknown): string => { const summary = readFollowthroughSummary(result); const nextStep = readWorkflowGuidanceNextStep(result); @@ -102,6 +112,9 @@ const buildMacroResolveMessage = (execute: boolean, result: unknown): string => if (hasExecutionBlocker(result)) { return "Macro resolved, but execution is blocked and needs follow-up."; } + if (hasIncompleteExecution(result)) { + return "Macro transport succeeded, but execution is incomplete and unblocked. Inspect execution.meta.ok, execution.meta.partial, and execution.failures before treating results as complete."; + } return "Macro resolved and executed."; }; diff --git a/src/cli/commands/product-video.ts b/src/cli/commands/product-video.ts index 80f756b0..a4f2162d 100644 --- a/src/cli/commands/product-video.ts +++ b/src/cli/commands/product-video.ts @@ -41,6 +41,33 @@ const parseBoolean = (value: string, flag: string): boolean => { const COOKIE_POLICY_VALUES = new Set(["off", "auto", "required"]); const BROWSER_MODE_VALUES = new Set(["auto", "extension", "managed"]); + +type ProductVideoReadinessKey = "presentationReadiness" | "productVideoReadiness"; + +const asRecord = (value: unknown): Record | null => ( + value && typeof value === "object" && !Array.isArray(value) + ? value as Record + : null +); + +const readReadinessStatus = (data: unknown, key: ProductVideoReadinessKey): string | null => { + const record = asRecord(data); + const topLevel = asRecord(record?.[key]); + const metaLevel = asRecord(asRecord(record?.meta)?.[key]); + const status = topLevel?.status ?? metaLevel?.status; + return typeof status === "string" && status.trim().length > 0 ? status.trim() : null; +}; + +const buildProductVideoCompletionMessage = (data: unknown): string => { + const message = buildWorkflowCompletionMessage("Product video asset workflow", data); + const suffixes = [ + ["Presentation readiness", readReadinessStatus(data, "presentationReadiness")], + ["Product-video readiness", readReadinessStatus(data, "productVideoReadiness")] + ] + .flatMap(([label, status]) => (status ? [`${label}: ${status}.`] : [])); + return suffixes.length > 0 ? `${message} ${suffixes.join(" ")}` : message; +}; + const parseProductVideoArgs = (rawArgs: string[]): ProductVideoCommandArgs => { const parsed: ProductVideoCommandArgs = {}; @@ -242,7 +269,7 @@ export async function runProductVideoCommand(args: ParsedArgs) { return { success: true, - message: buildWorkflowCompletionMessage("Product video asset workflow", data), + message: buildProductVideoCompletionMessage(data), data }; } diff --git a/src/cli/commands/shopping.ts b/src/cli/commands/shopping.ts index 42d9cbe3..a1c450ab 100644 --- a/src/cli/commands/shopping.ts +++ b/src/cli/commands/shopping.ts @@ -38,6 +38,25 @@ const deriveShoppingTransportTimeoutMs = (timeoutMs: number): number => { ); }; +const asRecord = (value: unknown): Record | null => ( + value && typeof value === "object" && !Array.isArray(value) + ? value as Record + : null +); + +const readBuyingReadinessStatus = (data: unknown): string | null => { + const record = asRecord(data); + const readiness = asRecord(record?.buyingReadiness) ?? asRecord(asRecord(record?.meta)?.buyingReadiness); + const status = readiness?.status; + return typeof status === "string" && status.trim().length > 0 ? status.trim() : null; +}; + +const buildShoppingCompletionMessage = (data: unknown): string => { + const baseMessage = buildWorkflowCompletionMessage("Shopping workflow", data); + const status = readBuyingReadinessStatus(data); + return status ? `${baseMessage} Buying readiness: ${status}.` : baseMessage; +}; + const requireValue = (rawArgs: string[], index: number, flag: string): string => { const value = rawArgs[index + 1]; if (!value) { @@ -286,7 +305,7 @@ export async function runShoppingCommand(args: ParsedArgs) { return { success: true, - message: buildWorkflowCompletionMessage("Shopping workflow", data), + message: buildShoppingCompletionMessage(data), data }; } @@ -294,5 +313,6 @@ export async function runShoppingCommand(args: ParsedArgs) { export const __test__ = { parseShoppingRunArgs, parseProviders, - deriveShoppingTransportTimeoutMs + deriveShoppingTransportTimeoutMs, + buildShoppingCompletionMessage }; diff --git a/src/cli/daemon-commands.ts b/src/cli/daemon-commands.ts index 47f7dd3d..1832c0e2 100644 --- a/src/cli/daemon-commands.ts +++ b/src/cli/daemon-commands.ts @@ -25,7 +25,10 @@ import { runShoppingWorkflow } from "../providers/workflows"; import { resolveWorkflowArtifactRoot } from "../providers/workflow-output-root"; -import { buildMacroResolveSuccessHandoff } from "../providers/workflow-handoff"; +import { + buildMacroResolveSuccessHandoff, + macroExecutionNeedsCompletionReview +} from "../providers/workflow-handoff"; import { isChallengeAutomationMode, type ChallengeAutomationMode } from "../challenges"; import { type MacroExecutionPayload, @@ -917,6 +920,7 @@ export async function handleDaemonCommand(core: OpenDevBrowserCore, request: Dae ); case "inspiredesign.run": { const inspiredesignTimeoutMs = optionalNumber(params.timeoutMs, "timeoutMs"); + const relayStatus = core.relay.status(); return runInspiredesignWorkflow( createDaemonWorkflowRuntime(core), { @@ -937,7 +941,8 @@ export async function handleDaemonCommand(core: OpenDevBrowserCore, request: Dae profile: optionalString(params.profile), useCookies: optionalBoolean(params.useCookies), challengeAutomationMode: optionalChallengeAutomationMode(params.challengeAutomationMode), - cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride) + cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride), + extensionAuthReady: relayStatus.extensionHandshakeComplete }, { mediaAnalysisConfig: core.config.inspiredesign?.mediaAnalysis ?? {}, @@ -2337,7 +2342,8 @@ async function resolveMacroExpression( expression: options.expression, defaultProvider: options.defaultProvider, execute: true, - blocked: Boolean(execution.meta.blocker) + blocked: Boolean(execution.meta.blocker), + executionNeedsCompletionReview: macroExecutionNeedsCompletionReview(execution) }); return { runtime: resolvedRuntime, diff --git a/src/inspiredesign/capture.ts b/src/inspiredesign/capture.ts index 4a8168ea..5f47a3e1 100644 --- a/src/inspiredesign/capture.ts +++ b/src/inspiredesign/capture.ts @@ -124,6 +124,7 @@ export type InspiredesignPrimaryMotionCaptureOptions = InspiredesignPrimaryCaptu export type InspiredesignPrimaryPinMediaCaptureOptions = InspiredesignPrimaryCaptureCookieOptions & { timeoutMs?: number; browserMode?: WorkflowBrowserMode; + profile?: string; challengeAutomationMode?: ChallengeAutomationMode; referenceId: string; pinMediaEvidencePath: string; @@ -1008,6 +1009,7 @@ const launchPrimaryCaptureSession = async ( remainingTimeoutMs: () => number, options: InspiredesignPrimaryCaptureCookieOptions & { browserMode?: WorkflowBrowserMode; + profile?: string; challengeAutomationMode?: ChallengeAutomationMode; maxNetworkIdleWaitMs?: number; maxSessionSetupStepMs?: number; @@ -1015,12 +1017,14 @@ const launchPrimaryCaptureSession = async ( } ): Promise<{ sessionId: string }> => { const launchTimeoutMs = captureSessionSetupStepTimeout(remainingTimeoutMs, options.maxSessionSetupStepMs); + const managedProfile = options.browserMode !== "extension" ? options.profile?.trim() : undefined; const session = await withCaptureDeadline( manager.launch({ headless: options.browserMode !== "extension", startUrl: "about:blank", - persistProfile: false, - noExtension: shouldForceManagedPrimaryCapture(options.browserMode) + persistProfile: Boolean(managedProfile), + noExtension: Boolean(managedProfile) || shouldForceManagedPrimaryCapture(options.browserMode), + ...(managedProfile ? { profile: managedProfile } : {}) }, launchTimeoutMs), launchTimeoutMs, "primary media capture session launch" diff --git a/src/inspiredesign/pinterest-pin-media-evidence.ts b/src/inspiredesign/pinterest-pin-media-evidence.ts index cf28e2c5..706bcee8 100644 --- a/src/inspiredesign/pinterest-pin-media-evidence.ts +++ b/src/inspiredesign/pinterest-pin-media-evidence.ts @@ -778,7 +778,7 @@ const hasAuthorityBlockingWarning = ( requireDesignAuthority: boolean ): boolean => readWarningEntries(evidence.warnings).some((warning) => { const marker = normalizeWarningMarker(warning); - if (NON_BLOCKING_PIN_MEDIA_WARNING_MARKERS.has(marker)) return false; + if (!requireDesignAuthority && NON_BLOCKING_PIN_MEDIA_WARNING_MARKERS.has(marker)) return false; if (marker === "interface_chrome_shell" && isStrictCanonicalByteBackedPinMediaEvidence(evidence, requireDesignAuthority)) return false; return BLOCKING_WARNING_MARKERS.some((blockingMarker) => marker.includes(blockingMarker)); }); diff --git a/src/inspiredesign/product-readiness.ts b/src/inspiredesign/product-readiness.ts index fb3e787f..c79b72ca 100644 --- a/src/inspiredesign/product-readiness.ts +++ b/src/inspiredesign/product-readiness.ts @@ -153,6 +153,9 @@ const normalizeUrlForAuthority = (value: unknown): string | undefined => { try { const url = new URL(value); if (url.protocol !== "http:" && url.protocol !== "https:") return undefined; + if (url.pathname.length > 1 && url.pathname.endsWith("/")) { + url.pathname = url.pathname.replace(/\/+$/u, ""); + } return url.href; } catch { return undefined; diff --git a/src/inspiredesign/reference-pattern-board.ts b/src/inspiredesign/reference-pattern-board.ts index 977846bd..8dbafb45 100644 --- a/src/inspiredesign/reference-pattern-board.ts +++ b/src/inspiredesign/reference-pattern-board.ts @@ -716,7 +716,7 @@ const hasPinMediaReadyPinterestEvidence = ( pinMediaIndex?: readonly InspiredesignPinterestPinMediaIndexEntry[] ): boolean => { if (!isPinterestProductCandidateReferenceUrl(reference.url)) return false; - if (diagnosticReasons.some((reason) => reason !== "login_or_challenge_state")) return false; + if (diagnosticReasons.length > 0) return false; if (reference.captureStatus !== "captured") return false; const pinMedia = reference.capture?.pinMedia; if (pinMedia?.status !== "captured") return false; diff --git a/src/providers/renderer.ts b/src/providers/renderer.ts index 3a51641b..d4efdcd2 100644 --- a/src/providers/renderer.ts +++ b/src/providers/renderer.ts @@ -972,8 +972,10 @@ export const renderShopping = (args: { const markdown = renderShoppingBriefingMarkdown(briefing); const guidanceLines = shoppingBriefingGuidanceLines(briefing); const comparisonCsv = toComparisonCsv(args.offers); + const buyingReadiness = briefing.gate; const contextPayload = { query: args.query, + buyingReadiness, highlights: guidanceLines, offers: args.offers, meta: args.meta @@ -991,6 +993,7 @@ export const renderShopping = (args: { return { response: { mode: args.mode, + buyingReadiness, summary: guidanceLines.join("\n"), meta: args.meta }, @@ -1001,6 +1004,7 @@ export const renderShopping = (args: { return { response: { mode: args.mode, + buyingReadiness, offers: args.offers, meta: args.meta }, @@ -1011,6 +1015,7 @@ export const renderShopping = (args: { return { response: { mode: args.mode, + buyingReadiness, markdown, meta: args.meta }, @@ -1021,6 +1026,7 @@ export const renderShopping = (args: { return { response: { mode: args.mode, + buyingReadiness, context: contextPayload, meta: args.meta }, @@ -1031,6 +1037,7 @@ export const renderShopping = (args: { return { response: { mode: "path", + buyingReadiness, meta: args.meta }, files diff --git a/src/providers/research-compiler.ts b/src/providers/research-compiler.ts index fb7b72d2..4c02b0f2 100644 --- a/src/providers/research-compiler.ts +++ b/src/providers/research-compiler.ts @@ -14,8 +14,8 @@ import type { import type { ResearchRunInput } from "./workflows"; import type { WorkflowCheckpoint, WorkflowPlan, WorkflowPlanStep, WorkflowResumeEnvelope } from "./workflow-contracts"; -const RESEARCH_AUTO_SOURCES: ProviderSource[] = ["web", "community", "social"]; -const RESEARCH_ALL_SOURCES: ProviderSource[] = [...RESEARCH_AUTO_SOURCES]; +const RESEARCH_AUTO_SOURCES: ProviderSource[] = ["web", "community"]; +const RESEARCH_ALL_SOURCES: ProviderSource[] = ["web", "community", "social"]; const DEFAULT_RESEARCH_SEARCH_LIMIT = 10; export const RESEARCH_WEB_SEARCH_FETCH_LIMIT = 5; const RESEARCH_COMMUNITY_EXPANSION_PER_RECORD = 2; diff --git a/src/providers/shopping-report/gate.ts b/src/providers/shopping-report/gate.ts index 45c7366b..00fa3a7f 100644 --- a/src/providers/shopping-report/gate.ts +++ b/src/providers/shopping-report/gate.ts @@ -133,7 +133,7 @@ const gateCriteria = (facts: ShoppingGateFacts): ShoppingBriefingCriterion[] => criterion({ label: "Availability", observed: `${facts.unknownAvailability} unknown, ${facts.outOfStock} out of stock`, - threshold: "No unknown or out-of-stock offer in the confident set", + threshold: "No unknown or out-of-stock offer in the current evidence set", passed: facts.unknownAvailability === 0 && facts.outOfStock === 0 }), criterion({ @@ -148,7 +148,7 @@ const gateCriteria = (facts: ShoppingGateFacts): ShoppingBriefingCriterion[] => criterion({ label: "Query and title quality", observed: `${facts.weakRelevance} weak relevance, ${facts.suspiciousTitles} suspicious title`, - threshold: "No weak relevance or suspicious title in the confident set", + threshold: "No weak relevance or suspicious title in the current evidence set", passed: facts.weakRelevance === 0 && facts.suspiciousTitles === 0 }), criterion({ diff --git a/src/providers/workflow-handoff.ts b/src/providers/workflow-handoff.ts index aa432b50..61ddecd4 100644 --- a/src/providers/workflow-handoff.ts +++ b/src/providers/workflow-handoff.ts @@ -231,6 +231,11 @@ const buildShoppingRerunCommand = (input: ShoppingHandoffInput): string => { type ProductVideoReadinessHandoffInput = Pick; +type ProductVideoProviderGuidanceInput = { + reason: string; + recommendedNextCommands: readonly string[]; +}; + type ProductVideoHandoffInput = { productUrl?: string; productName?: string; @@ -241,6 +246,8 @@ type ProductVideoHandoffInput = { includeCopy?: boolean; presentationReadiness?: ProductVideoReadinessHandoffInput; productVideoReadiness?: ProductVideoReadinessHandoffInput; + primaryConstraintSummary?: string; + providerGuidance?: ProductVideoProviderGuidanceInput; }; const buildProductVideoRerunCommand = (input: ProductVideoHandoffInput = {}): string => { @@ -263,6 +270,15 @@ type MacroResolveHandoffInput = { defaultProvider?: string; execute: boolean; blocked: boolean; + executionNeedsCompletionReview?: boolean; +}; + +type MacroExecutionCompletenessInput = { + failures: readonly ProviderFailureEntry[]; + meta: { + ok: boolean; + partial: boolean; + }; }; type InspiredesignSuccessHandoffInput = { @@ -489,13 +505,24 @@ const productVideoBriefHelperReason = (input: ProductVideoHandoffInput): string return "Run the product-presentation-asset brief helper on manifest.json to generate readiness-aware production brief files."; }; +const productVideoProviderRecoveryReason = (input: ProductVideoHandoffInput): string | null => { + const guidance = input.providerGuidance; + if (!guidance) return null; + const commands = guidance.recommendedNextCommands.filter((command) => command.trim().length > 0); + const commandSuffix = commands.length > 0 ? ` Next steps: ${commands.join(" ")}` : ""; + const summaryPrefix = input.primaryConstraintSummary ? `${input.primaryConstraintSummary} ` : ""; + return `${summaryPrefix}${guidance.reason}${commandSuffix}`; +}; + export const buildProductVideoSuccessHandoff = (input: ProductVideoHandoffInput = {}): WorkflowSuccessHandoff => { const rerunCommand = buildProductVideoRerunCommand(input); + const providerRecoveryReason = productVideoProviderRecoveryReason(input); return createSuccessHandoff( productVideoHandoffFollowthroughSummary(input), productVideoHandoffNextAction(input), [ { reason: productVideoReadinessInspectionReason(input) }, + ...(providerRecoveryReason ? [{ reason: providerRecoveryReason }] : []), { reason: productVideoBriefHelperReason(input), command: PRODUCT_VIDEO_BRIEF_HELPER_COMMAND @@ -535,6 +562,17 @@ export const buildMacroResolveSuccessHandoff = (input: MacroResolveHandoffInput) ] ); } + if (input.executionNeedsCompletionReview) { + return createSuccessHandoff( + "Macro transport succeeded, but execution is incomplete and unblocked. Inspect execution.meta.ok, execution.meta.partial, and execution.failures before treating results as complete.", + `Inspect execution.meta.ok, execution.meta.partial, and execution.failures, then rerun ${executeCommand} after resolving unblocked provider failures.`, + [ + { reason: "Inspect execution.meta.ok, execution.meta.partial, and execution.failures to separate transport success from execution completeness." }, + { reason: "Rerun the macro after resolving provider failures or accepting partial results intentionally.", command: executeCommand }, + { reason: "Use browser-scoped challenge automation only if the incomplete execution points to a live browser recovery path.", command: browserRetryCommand } + ] + ); + } return createSuccessHandoff( "Review execution.records and trace metadata before widening the macro or changing providers.", `Inspect execution.records and execution.meta, then rerun ${previewCommand} if you need a narrower plan.`, @@ -546,6 +584,10 @@ export const buildMacroResolveSuccessHandoff = (input: MacroResolveHandoffInput) ); }; +export const macroExecutionNeedsCompletionReview = (execution: MacroExecutionCompletenessInput): boolean => ( + !execution.meta.ok || execution.meta.partial || execution.failures.length > 0 +); + export const buildInspiredesignSuccessHandoff = ( input: InspiredesignSuccessHandoffInput ): WorkflowSuccessHandoff => { diff --git a/src/providers/workflows.ts b/src/providers/workflows.ts index 3d07754e..2c12e1a5 100644 --- a/src/providers/workflows.ts +++ b/src/providers/workflows.ts @@ -310,6 +310,7 @@ export interface InspiredesignRunInput { cookiePolicyOverride?: ProviderCookiePolicy; profile?: string; cookieSource?: ProviderCookieSourceConfig; + extensionAuthReady?: boolean; } export interface ProductVideoRunInput { @@ -653,19 +654,47 @@ const buildTranscriptAlertsFromFailures = ( return alerts; }; +const filterWorkflowAlertsByProviderIds = ( + alerts: Array>, + providerIds?: string[] +): Array> => { + if (providerIds === undefined) return alerts; + const selectedProviderIds = new Set(providerIds); + return alerts.filter((alert) => { + const provider = alert.provider; + return typeof provider !== "string" || selectedProviderIds.has(provider); + }); +}; + const buildWorkflowAlerts = ( runtime: ReferenceRetrievalPort, failures: ProviderFailureEntry[], providerIds?: string[] ): Array> => { const snapshots = getRuntimeAntiBotSnapshots(runtime, providerIds); - if (snapshots.length === 0) { - return buildAlerts(); + const alerts = snapshots.length === 0 + ? buildAlerts() + : [ + ...buildRuntimePressureAlerts(snapshots), + ...buildTranscriptAlertsFromFailures(failures) + ]; + return filterWorkflowAlertsByProviderIds(alerts, providerIds); +}; + +const collectWorkflowProviderIds = (results: ProviderAggregateResult[]): string[] => { + const providerIds = new Set(); + for (const result of results) { + for (const providerId of result.providerOrder) { + providerIds.add(providerId); + } + for (const record of result.records) { + providerIds.add(record.provider); + } + for (const failure of result.failures) { + providerIds.add(failure.provider); + } } - return [ - ...buildRuntimePressureAlerts(snapshots), - ...buildTranscriptAlertsFromFailures(failures) - ]; + return [...providerIds]; }; const getDegradedProviders = (): Set => { @@ -1770,6 +1799,65 @@ const INSPIREDESIGN_COOKIE_POLICIES = new Set(["off", "aut const WORKFLOW_BROWSER_MODES = new Set(["auto", "extension", "managed"]); const INSPIREDESIGN_DEFAULT_MAX_REFERENCES = 5; const INSPIREDESIGN_MAX_REFERENCES_LIMIT = 10; +const INSPIREDESIGN_PINTEREST_PROVIDER_ID = "social/pinterest"; +const INSPIREDESIGN_PINTEREST_PROVIDER_ALIAS = "pinterest"; +const PINTEREST_EXTENSION_CHALLENGE_MODE: ChallengeAutomationMode = "browser_with_helper"; + +const isPinterestWorkflowReferenceUrl = (value: string): boolean => { + try { + const hostname = new URL(value).hostname.toLowerCase(); + return hostname === "pinterest.com" || hostname.endsWith(".pinterest.com"); + } catch { + return false; + } +}; + +const isPinterestWorkflowProvider = (providerId: string): boolean => ( + providerId === INSPIREDESIGN_PINTEREST_PROVIDER_ID + || providerId === INSPIREDESIGN_PINTEREST_PROVIDER_ALIAS + || resolveSiteRecipeForProvider(providerId)?.id === INSPIREDESIGN_PINTEREST_PROVIDER_ID +); + +const hasExplicitPinterestAuthSetting = (input: InspiredesignRunInput): boolean => ( + input.browserMode !== undefined + || input.profile !== undefined + || input.useCookies !== undefined + || input.cookiePolicyOverride !== undefined + || ( + input.challengeAutomationMode !== undefined + && input.challengeAutomationMode !== PINTEREST_EXTENSION_CHALLENGE_MODE + ) +); + +const shouldApplyPinterestExtensionAuthDefaults = ( + input: InspiredesignRunInput, + providers: readonly string[], + urls: readonly string[] +): boolean => ( + input.harvest === true + && input.extensionAuthReady === true + && ( + providers.some(isPinterestWorkflowProvider) + || urls.some(isPinterestWorkflowReferenceUrl) + ) + && !hasExplicitPinterestAuthSetting(input) +); + +const pinterestExtensionAuthDefaults = ( + input: InspiredesignRunInput, + providers: readonly string[], + urls: readonly string[] +): Partial => { + if (!shouldApplyPinterestExtensionAuthDefaults(input, providers, urls)) { + return {}; + } + return { + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: PINTEREST_EXTENSION_CHALLENGE_MODE + }; +}; const isJsonRecord = (value: JsonValue | undefined): value is Record => ( typeof value === "object" && value !== null && !Array.isArray(value) @@ -2076,6 +2164,7 @@ const normalizeInspiredesignInput = (input: InspiredesignRunInput): Inspiredesig const query = typeof input.query === "string" && input.query.trim().length > 0 ? input.query.trim() : undefined; const providers = normalizeInspiredesignProviders(input.providers); const hasExplicitMaxReferences = typeof input.maxReferences !== "undefined"; + const extensionAuthDefaults = pinterestExtensionAuthDefaults(input, providers, urls); if (query && input.harvest !== true) { throw new Error("Inspiredesign workflow query is only supported when harvest is true."); } @@ -2109,6 +2198,7 @@ const normalizeInspiredesignInput = (input: InspiredesignRunInput): Inspiredesig const briefExpansion = expandInspiredesignBrief(brief, preferredFormatId); return { ...input, + ...extensionAuthDefaults, brief, briefExpansion, ...(query ? { query } : {}), @@ -3784,7 +3874,11 @@ const finalizeInspiredesignReferenceVisual = async ( if (buffer.byteLength === 0) { throw new Error("Visual evidence screenshot file was empty."); } - const persisted = persistInspiredesignVisualEvidence(visual, { + const visualForPersistence: InspiredesignVisualEvidenceRuntimeMetadata = !isPinterestWorkflowReferenceUrl(reference.url) + && runtimeVisual.sourceUrl === undefined + ? { ...runtimeVisual, sourceUrl: reference.url } + : runtimeVisual; + const persisted = persistInspiredesignVisualEvidence(visualForPersistence, { artifactPath, sha256: hashVisualEvidenceBuffer(buffer), bytes: buffer.byteLength @@ -5410,21 +5504,6 @@ const finalizeInspiredesignResponseGuidance = (args: { }; }; -const isPinterestWorkflowReferenceUrl = (value: string): boolean => { - try { - const hostname = new URL(value).hostname.toLowerCase(); - return hostname === "pinterest.com" || hostname.endsWith(".pinterest.com"); - } catch { - return false; - } -}; - -const isPinterestWorkflowProvider = (providerId: string): boolean => ( - providerId === "social/pinterest" - || providerId === "pinterest" - || resolveSiteRecipeForProvider(providerId)?.id === "social/pinterest" -); - const isPinterestEvidenceRequiredForWorkflow = ( workflowInput: InspiredesignResolvedInput, discovery: InspiredesignDiscoveryDiagnostics @@ -6624,6 +6703,12 @@ export const runResearchWorkflow = async ( }); const excludedProviderSet = new Set(plan.compiled.autoExcludedProviders); + const allResearchResults = [ + ...execution.searchRuns.map((run) => run.result), + ...execution.followUpRuns.map((run) => run.result) + ]; + const selectedResearchProviderIds = collectWorkflowProviderIds(allResearchResults) + .filter((providerId) => !excludedProviderSet.has(providerId)); const rawRecords = [ ...execution.searchRuns.flatMap((run) => run.result.records), ...execution.followUpRuns.flatMap((run) => run.result.records) @@ -6714,7 +6799,7 @@ export const runResearchWorkflow = async ( failures: mergedFailures, rejected_candidates: rejectedCandidates, rejectedCandidates, - alerts: buildWorkflowAlerts(runtime, mergedFailures) + alerts: buildWorkflowAlerts(runtime, mergedFailures, selectedResearchProviderIds) } as Record, primaryConstraintFailures); const handoff = buildResearchSuccessHandoff({ topic: plan.compiled.topic, @@ -7916,7 +8001,13 @@ export const runProductVideoWorkflow = async ( includeAllImages: workflowInput.include_all_images, includeCopy: workflowInput.include_copy, presentationReadiness: presentation.presentationReadiness, - productVideoReadiness: presentation.productVideoReadiness + productVideoReadiness: presentation.productVideoReadiness, + ...(primaryIssue + ? { + primaryConstraintSummary: primaryIssue.summary, + ...(primaryIssue.guidance ? { providerGuidance: primaryIssue.guidance } : {}) + } + : {}) }); const meta = withFollowthroughMeta({ ...(workflowInput.browserMode diff --git a/src/public-surface/generated-manifest.json b/src/public-surface/generated-manifest.json index 41ab054c..c3966f72 100644 --- a/src/public-surface/generated-manifest.json +++ b/src/public-surface/generated-manifest.json @@ -537,7 +537,7 @@ ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Load opendevbrowser-research first, use explicit source families such as --sources web,community, and inspect artifacts before final claims." + "Load opendevbrowser-research first, use explicit source families such as --sources web,community, and inspect artifacts before final claims. sourceSelection=auto is public-first and resolves to web,community; use all or explicit social sources only when social evidence is intentional." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -569,7 +569,8 @@ ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning." + "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.", + "Treat buyingReadiness.status as the buying authority gate. Transport success or emitted offers are not enough for purchase guidance when buyingReadiness is partial or fail." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -601,7 +602,8 @@ ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Confirm whether the returned pack is visual-ready or metadata-first before briefing production." + "Confirm whether the returned pack is visual-ready or metadata-first before briefing production.", + "Use presentationReadiness and productVideoReadiness as production authority gates. Completion text and JSON expose those statuses separately from transport success." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -641,6 +643,7 @@ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", "Pinterest harvest uses manifest-backed pin-media evidence as the required product-readiness authority for canonical pin-media harvests; screenshot evidence and screencast evidence remain useful capture or motion lanes but are not substitutes. DOM/clone/deep capture is disabled for Pinterest harvest, and remote media URLs are not product-ready unless persisted first-party bytes appear in pin-media-index.json.", "Pinterest broad-query harvests become product-ready only when query discovery accepts canonical /pin/{id}/ references and the workflow captures manifest-backed first-party pin-media bytes for those pins. discovery-diagnostics.json records accepted and rejected URLs, blocker diagnostics, and recovery context; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence.", + "Default Pinterest harvest applies extension auth defaults only when extensionAuthReady is current and the caller has not set incompatible auth transport or cookie settings. Explicit browserMode, profile, useCookies, cookiePolicyOverride, or challengeAutomationMode values other than browser_with_helper opt out; explicit challengeAutomationMode=browser_with_helper is compatible and may merge with the implicit extension, cookies, and required cookie policy defaults.", "inspiredesign run forces captureMode=deep for any explicit --url so the workflow can collect DOM/layout diagnostics. inspiredesign harvest forces deep capture for non-Pinterest explicit --url references, while Pinterest-only discovery and compatible Pinterest URL recovery force captureMode=off even when --capture-mode deep is requested.", "Repeat --url for multiple references. There is no --urls alias.", "inspiredesign harvest keeps the daemon method as inspiredesign.run, requires --query or at least one --url, defaults to path output, requires visual evidence, and caps discovery at 5 references unless --max-references changes it.", @@ -656,7 +659,7 @@ "Canonical Pinterest pin-media harvest opens the exact canonical pin in the active managed or extension workflow session before extracting persisted first-party bytes. Extension /ops remains the best live-tab reuse path, and explicit CDP profiles remain a browser/session primitive lane until provider workflows expose an explicit-CDP transport selector.", "Harvest JSON is metadata-only: screenshots, motion evidence, and pin-media evidence are artifact files referenced by relative paths, hashes, viewport metadata, frame counts, dimensions, provenance, and warnings.", "ranked-references.json includes rejectedReferences for captured-but-rejected diagnostics such as interface_chrome_shell without promoting those captures into design references.", - "Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when first-party pin-media evidence is captured, persisted, manifest-backed, and free of blocking warnings. Snapshot and screencast artifacts can inform diagnostics or motion, but they do not satisfy required Pinterest pin-media readiness. The exact login_or_challenge_state and strict byte-backed interface_chrome_shell diagnostics are non-blocking only for trusted first-party manifest-backed pin-media bytes; broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers still demote readiness unless query-discovered canonical pins also produce complete pin-media authority. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure; pin-media-index.json remains Pinterest authority, media-analysis.json remains advisory, and motion-evidence.json remains browser replay authority.", + "Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when first-party pin-media evidence is captured, persisted, manifest-backed, and free of blocking warnings. Snapshot and screencast artifacts can inform diagnostics or motion, but they do not satisfy required Pinterest pin-media readiness. login_or_challenge_state, broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers demote readiness; strict byte-backed interface_chrome_shell diagnostics remain non-blocking only for trusted first-party manifest-backed pin-media bytes. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure; pin-media-index.json remains Pinterest authority, media-analysis.json remains advisory, and motion-evidence.json remains browser replay authority.", "Load opendevbrowser-motion-design before turning harvest motion posture into implementation timing, scroll choreography, reduced-motion behavior, or temporal proof." ], "groupId": "provider_workflows", @@ -706,7 +709,7 @@ "notes": [ "Use --browser-mode and --challenge-automation-mode only with --execute.", "Use --browser-mode extension --use-cookies --cookie-policy required when provider macros must reuse relay-backed browser state and verify that provider cookies are observable.", - "When --execute is enabled, inspect execution.meta.blocker before trusting a blocked result as complete." + "When --execute is enabled, inspect execution.meta.blocker before trusting a blocked result as complete. Also inspect execution.meta.ok, execution.meta.partial, and execution.failures because transport success can still be unblocked but incomplete." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -2652,7 +2655,8 @@ "cliEquivalent": "research", "example": "npx opendevbrowser research run --topic \"Chrome extension debugging workflows\" --days 30 --sources web,community --browser-mode managed --mode json --output-format json", "notes": [ - "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /." + "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", + "sourceSelection=auto is public-first and resolves to web,community; explicit all or social selection is required for social providers." ] }, { @@ -2662,7 +2666,8 @@ "example": "npx opendevbrowser shopping run --query \"wireless ergonomic mouse\" --providers shopping/bestbuy,shopping/ebay --budget 150 --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --mode json --output-format json", "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning." + "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.", + "Use buyingReadiness.status as the buying authority gate, separate from command transport success." ] }, { @@ -2671,7 +2676,8 @@ "cliEquivalent": "product-video", "example": "npx opendevbrowser product-video run --product-url \"https://example.com/p/1\" --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-screenshots --output-format json", "notes": [ - "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /." + "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", + "Use presentationReadiness and productVideoReadiness as production authority gates, separate from command transport success." ] }, { diff --git a/src/public-surface/generated-manifest.ts b/src/public-surface/generated-manifest.ts index dbb9b2cc..f6ffdca2 100644 --- a/src/public-surface/generated-manifest.ts +++ b/src/public-surface/generated-manifest.ts @@ -554,7 +554,7 @@ export const PUBLIC_SURFACE_MANIFEST = { ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Load opendevbrowser-research first, use explicit source families such as --sources web,community, and inspect artifacts before final claims." + "Load opendevbrowser-research first, use explicit source families such as --sources web,community, and inspect artifacts before final claims. sourceSelection=auto is public-first and resolves to web,community; use all or explicit social sources only when social evidence is intentional." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -586,7 +586,8 @@ export const PUBLIC_SURFACE_MANIFEST = { ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning." + "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.", + "Treat buyingReadiness.status as the buying authority gate. Transport success or emitted offers are not enough for purchase guidance when buyingReadiness is partial or fail." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -618,7 +619,8 @@ export const PUBLIC_SURFACE_MANIFEST = { ], "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Confirm whether the returned pack is visual-ready or metadata-first before briefing production." + "Confirm whether the returned pack is visual-ready or metadata-first before briefing production.", + "Use presentationReadiness and productVideoReadiness as production authority gates. Completion text and JSON expose those statuses separately from transport success." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -658,6 +660,7 @@ export const PUBLIC_SURFACE_MANIFEST = { "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", "Pinterest harvest uses manifest-backed pin-media evidence as the required product-readiness authority for canonical pin-media harvests; screenshot evidence and screencast evidence remain useful capture or motion lanes but are not substitutes. DOM/clone/deep capture is disabled for Pinterest harvest, and remote media URLs are not product-ready unless persisted first-party bytes appear in pin-media-index.json.", "Pinterest broad-query harvests become product-ready only when query discovery accepts canonical /pin/{id}/ references and the workflow captures manifest-backed first-party pin-media bytes for those pins. discovery-diagnostics.json records accepted and rejected URLs, blocker diagnostics, and recovery context; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence.", + "Default Pinterest harvest applies extension auth defaults only when extensionAuthReady is current and the caller has not set incompatible auth transport or cookie settings. Explicit browserMode, profile, useCookies, cookiePolicyOverride, or challengeAutomationMode values other than browser_with_helper opt out; explicit challengeAutomationMode=browser_with_helper is compatible and may merge with the implicit extension, cookies, and required cookie policy defaults.", "inspiredesign run forces captureMode=deep for any explicit --url so the workflow can collect DOM/layout diagnostics. inspiredesign harvest forces deep capture for non-Pinterest explicit --url references, while Pinterest-only discovery and compatible Pinterest URL recovery force captureMode=off even when --capture-mode deep is requested.", "Repeat --url for multiple references. There is no --urls alias.", "inspiredesign harvest keeps the daemon method as inspiredesign.run, requires --query or at least one --url, defaults to path output, requires visual evidence, and caps discovery at 5 references unless --max-references changes it.", @@ -673,7 +676,7 @@ export const PUBLIC_SURFACE_MANIFEST = { "Canonical Pinterest pin-media harvest opens the exact canonical pin in the active managed or extension workflow session before extracting persisted first-party bytes. Extension /ops remains the best live-tab reuse path, and explicit CDP profiles remain a browser/session primitive lane until provider workflows expose an explicit-CDP transport selector.", "Harvest JSON is metadata-only: screenshots, motion evidence, and pin-media evidence are artifact files referenced by relative paths, hashes, viewport metadata, frame counts, dimensions, provenance, and warnings.", "ranked-references.json includes rejectedReferences for captured-but-rejected diagnostics such as interface_chrome_shell without promoting those captures into design references.", - "Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when first-party pin-media evidence is captured, persisted, manifest-backed, and free of blocking warnings. Snapshot and screencast artifacts can inform diagnostics or motion, but they do not satisfy required Pinterest pin-media readiness. The exact login_or_challenge_state and strict byte-backed interface_chrome_shell diagnostics are non-blocking only for trusted first-party manifest-backed pin-media bytes; broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers still demote readiness unless query-discovered canonical pins also produce complete pin-media authority. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure; pin-media-index.json remains Pinterest authority, media-analysis.json remains advisory, and motion-evidence.json remains browser replay authority.", + "Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when first-party pin-media evidence is captured, persisted, manifest-backed, and free of blocking warnings. Snapshot and screencast artifacts can inform diagnostics or motion, but they do not satisfy required Pinterest pin-media readiness. login_or_challenge_state, broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers demote readiness; strict byte-backed interface_chrome_shell diagnostics remain non-blocking only for trusted first-party manifest-backed pin-media bytes. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure; pin-media-index.json remains Pinterest authority, media-analysis.json remains advisory, and motion-evidence.json remains browser replay authority.", "Load opendevbrowser-motion-design before turning harvest motion posture into implementation timing, scroll choreography, reduced-motion behavior, or temporal proof." ], "groupId": "provider_workflows", @@ -723,7 +726,7 @@ export const PUBLIC_SURFACE_MANIFEST = { "notes": [ "Use --browser-mode and --challenge-automation-mode only with --execute.", "Use --browser-mode extension --use-cookies --cookie-policy required when provider macros must reuse relay-backed browser state and verify that provider cookies are observable.", - "When --execute is enabled, inspect execution.meta.blocker before trusting a blocked result as complete." + "When --execute is enabled, inspect execution.meta.blocker before trusting a blocked result as complete. Also inspect execution.meta.ok, execution.meta.partial, and execution.failures because transport success can still be unblocked but incomplete." ], "groupId": "provider_workflows", "groupTitle": "Provider Workflows", @@ -2669,7 +2672,8 @@ export const PUBLIC_SURFACE_MANIFEST = { "cliEquivalent": "research", "example": "npx opendevbrowser research run --topic \"Chrome extension debugging workflows\" --days 30 --sources web,community --browser-mode managed --mode json --output-format json", "notes": [ - "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /." + "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", + "sourceSelection=auto is public-first and resolves to web,community; explicit all or social selection is required for social providers." ] }, { @@ -2679,7 +2683,8 @@ export const PUBLIC_SURFACE_MANIFEST = { "example": "npx opendevbrowser shopping run --query \"wireless ergonomic mouse\" --providers shopping/bestbuy,shopping/ebay --budget 150 --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --mode json --output-format json", "notes": [ "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", - "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning." + "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.", + "Use buyingReadiness.status as the buying authority gate, separate from command transport success." ] }, { @@ -2688,7 +2693,8 @@ export const PUBLIC_SURFACE_MANIFEST = { "cliEquivalent": "product-video", "example": "npx opendevbrowser product-video run --product-url \"https://example.com/p/1\" --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-screenshots --output-format json", "notes": [ - "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /." + "Routine workflow runs should omit --output-dir and inspect the returned artifact_path first; when --output-dir is omitted, persisted bundles use .opendevbrowser//. If a wrapper requires an explicit workflow root, use --output-dir .opendevbrowser so the runtime appends /.", + "Use presentationReadiness and productVideoReadiness as production authority gates, separate from command transport success." ] }, { diff --git a/src/public-surface/source.ts b/src/public-surface/source.ts index 06e9f60a..58808c0e 100644 --- a/src/public-surface/source.ts +++ b/src/public-surface/source.ts @@ -863,15 +863,17 @@ const CLI_COMMAND_NOTES: Partial> = { "Tool-only helper. Use it to load opendevbrowser-best-practices or design-agent guidance directly into the current agent context." ], opendevbrowser_research_run: [ - WORKFLOW_OUTPUT_PREFERENCE_NOTE + WORKFLOW_OUTPUT_PREFERENCE_NOTE, + "sourceSelection=auto is public-first and resolves to web,community; explicit all or social selection is required for social providers." ], opendevbrowser_shopping_run: [ WORKFLOW_OUTPUT_PREFERENCE_NOTE, - "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning." + "Treat --region as advisory unless the workflow output reports region_authoritative=true. The region_unenforced alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.", + "Use buyingReadiness.status as the buying authority gate, separate from command transport success." ], opendevbrowser_product_video_run: [ - WORKFLOW_OUTPUT_PREFERENCE_NOTE + WORKFLOW_OUTPUT_PREFERENCE_NOTE, + "Use presentationReadiness and productVideoReadiness as production authority gates, separate from command transport success." ], opendevbrowser_status_capabilities: [ MEDIA_ANALYSIS_STATUS_NOTE diff --git a/src/tools/inspiredesign_run.ts b/src/tools/inspiredesign_run.ts index b5023414..18a12235 100644 --- a/src/tools/inspiredesign_run.ts +++ b/src/tools/inspiredesign_run.ts @@ -89,6 +89,7 @@ export function createInspiredesignRunTool(deps: ToolDeps): ToolDefinition { } } const cookieSource = deps.config.get().providers?.cookieSource; + const extensionAuthReady = deps.relay?.status().extensionHandshakeComplete === true; if (isHarvest && !args.query && (!args.urls || args.urls.length === 0)) { throw new Error("inspiredesign harvest requires query or URLs."); } @@ -110,7 +111,8 @@ export function createInspiredesignRunTool(deps: ToolDeps): ToolDefinition { browserMode: args.browserMode, useCookies: args.useCookies, challengeAutomationMode: args.challengeAutomationMode, - cookiePolicyOverride: args.cookiePolicyOverride + cookiePolicyOverride: args.cookiePolicyOverride, + extensionAuthReady }, { mediaAnalysisConfig: deps.config.get().inspiredesign.mediaAnalysis, captureReference: shouldProvideCaptureReference diff --git a/src/tools/macro_resolve.ts b/src/tools/macro_resolve.ts index 5661eae4..90b3ab3b 100644 --- a/src/tools/macro_resolve.ts +++ b/src/tools/macro_resolve.ts @@ -4,7 +4,10 @@ import { type MacroResolution } from "../macros/execute"; import { executeMacroWithRuntime } from "../macros/execute-runtime"; -import { buildMacroResolveSuccessHandoff } from "../providers/workflow-handoff"; +import { + buildMacroResolveSuccessHandoff, + macroExecutionNeedsCompletionReview +} from "../providers/workflow-handoff"; import { CHALLENGE_AUTOMATION_MODES } from "../challenges/types"; import type { ToolDeps } from "./deps"; import { failure, ok, serializeError } from "./response"; @@ -159,7 +162,8 @@ export function createMacroResolveTool(deps: ToolDeps): ToolDefinition { expression: args.expression, defaultProvider: args.defaultProvider, execute: true, - blocked: Boolean(execution.meta.blocker) + blocked: Boolean(execution.meta.blocker), + executionNeedsCompletionReview: macroExecutionNeedsCompletionReview(execution) }); return ok({ diff --git a/tests/cli-macro-resolve.test.ts b/tests/cli-macro-resolve.test.ts index 547ce918..a269eb12 100644 --- a/tests/cli-macro-resolve.test.ts +++ b/tests/cli-macro-resolve.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ParsedArgs } from "../src/cli/args"; import { runMacroResolve } from "../src/cli/commands/macro-resolve"; +import { buildMacroResolveSuccessHandoff } from "../src/providers/workflow-handoff"; const { callDaemon } = vi.hoisted(() => ({ callDaemon: vi.fn() @@ -131,6 +132,87 @@ describe("macro-resolve CLI command", () => { expect(executionMeta).not.toHaveProperty("blocker"); }); + it.each([ + { + name: "meta.ok is false", + meta: { ok: false, partial: false }, + failures: [] + }, + { + name: "meta.partial is true", + meta: { ok: true, partial: true }, + failures: [] + }, + { + name: "failures are present", + meta: { ok: true, partial: false }, + failures: [{ provider: "web/default", source: "web", error: { code: "rate_limited", message: "provider skipped" } }] + } + ])("reports unblocked incomplete execution when $name", async ({ meta, failures }) => { + callDaemon.mockResolvedValue({ + runtime: "macros", + resolution: { action: { source: "web", operation: "search", input: { query: "openai" } } }, + execution: { + records: [{ id: "partial-record" }], + failures, + metrics: { attempted: 2, succeeded: 1, failed: failures.length, retries: 0, latencyMs: 1 }, + meta: { + ok: meta.ok, + partial: meta.partial, + sourceSelection: "web", + providerOrder: ["web/default"], + trace: { requestId: "req-partial" } + } + } + }); + + const result = await runMacroResolve(makeArgs([ + "--expression=@web.search(\"openai\")", + "--execute" + ])); + + expect(result.success).toBe(true); + expect(result.message).toContain("transport succeeded"); + expect(result.message).toContain("execution is incomplete and unblocked"); + expect(result.message).toContain("execution.meta.ok"); + expect(result.message).toContain("execution.meta.partial"); + expect(result.message).toContain("execution.failures"); + expect(result.data).toMatchObject({ + execution: { + failures, + meta: { + ok: meta.ok, + partial: meta.partial + } + } + }); + const executionMeta = (result.data as { execution?: { meta?: Record } }).execution?.meta; + expect(executionMeta).not.toHaveProperty("blocker"); + }); + + it("builds unblocked incomplete handoff without replacing blocker authority", () => { + const incompleteHandoff = buildMacroResolveSuccessHandoff({ + expression: "@web.search(\"openai\")", + execute: true, + blocked: false, + executionNeedsCompletionReview: true + }); + expect(incompleteHandoff.followthroughSummary).toContain("transport succeeded"); + expect(incompleteHandoff.followthroughSummary).toContain("execution is incomplete and unblocked"); + expect(incompleteHandoff.followthroughSummary).toContain("execution.meta.ok"); + expect(incompleteHandoff.followthroughSummary).toContain("execution.meta.partial"); + expect(incompleteHandoff.followthroughSummary).toContain("execution.failures"); + + const blockedHandoff = buildMacroResolveSuccessHandoff({ + expression: "@web.search(\"openai\")", + execute: true, + blocked: true, + executionNeedsCompletionReview: true + }); + expect(blockedHandoff.followthroughSummary).toContain("execution.meta.blocker"); + expect(blockedHandoff.followthroughSummary).not.toContain("incomplete and unblocked"); + }); + it("uses the shared runnable next-step reader when explicit macro next action is absent", async () => { callDaemon.mockResolvedValue({ runtime: "macros", diff --git a/tests/cli-workflows.test.ts b/tests/cli-workflows.test.ts index 55a9a0f2..6d3f8ec8 100644 --- a/tests/cli-workflows.test.ts +++ b/tests/cli-workflows.test.ts @@ -421,6 +421,25 @@ describe("workflow CLI commands", () => { ); }); + it("surfaces shopping buying readiness in completion messages", async () => { + callDaemon.mockResolvedValue({ + buyingReadiness: { + status: "partial", + summary: "Enough offers for a constrained shortlist, but not a confident buying recommendation." + }, + meta: {} + }); + + const result = await runShoppingCommand(makeArgs("shopping", [ + "run", + "--query=wireless mouse" + ])); + + expect(result.message).toBe( + "Shopping workflow completed. Buying readiness: partial." + ); + }); + it("prefers explicit camelCase workflow summaries in completion messages", async () => { callDaemon.mockResolvedValue({ meta: { @@ -1147,6 +1166,36 @@ describe("workflow CLI commands", () => { }); }); + it.each([ + { + name: "top-level", + data: { + presentationReadiness: { status: "partial" }, + productVideoReadiness: { status: "fail" } + }, + expected: "Product video asset workflow completed. Presentation readiness: partial. Product-video readiness: fail." + }, + { + name: "meta", + data: { + meta: { + presentationReadiness: { status: "pass" }, + productVideoReadiness: { status: "partial" } + } + }, + expected: "Product video asset workflow completed. Presentation readiness: pass. Product-video readiness: partial." + } + ])("adds product-video readiness suffixes from $name workflow data", async ({ data, expected }) => { + callDaemon.mockResolvedValue(data); + + const result = await runProductVideoCommand(makeArgs("product-video", [ + "run", + "--product-name=Sample Product" + ])); + + expect(result.message).toBe(expected); + }); + it("parses and dispatches product-video run payload", async () => { callDaemon.mockResolvedValue({ ok: true }); diff --git a/tests/inspiredesign-pinterest-pin-media-evidence.test.ts b/tests/inspiredesign-pinterest-pin-media-evidence.test.ts index 0f0385bf..d3b1c646 100644 --- a/tests/inspiredesign-pinterest-pin-media-evidence.test.ts +++ b/tests/inspiredesign-pinterest-pin-media-evidence.test.ts @@ -480,6 +480,20 @@ describe("Pinterest pin media evidence helpers", () => { })); }); + it("keeps login warning capture advisory but blocks product authority", () => { + const persisted = persistValidEvidence({ warnings: ["login_or_challenge_state"] }); + const indexEntry = buildInspiredesignPinterestPinMediaIndexEntry(persisted); + + expect(persisted.authority).toBe("design_evidence"); + expect(persisted.rejectionReasons).not.toContain("blocking_warning"); + expect(indexEntry).toEqual(expect.objectContaining({ + authority: "design_evidence", + warnings: ["login_or_challenge_state"] + })); + expect(hasPinterestPinMediaAuthorityBlockingWarning(persisted)).toBe(true); + expect(hasPinterestPinMediaAuthorityBlockingWarning(indexEntry ?? {})).toBe(true); + }); + it("does not delete caller-supplied blocking warning reasons for interface chrome media", () => { const persisted = persistValidEvidence({ warnings: ["interface_chrome_shell"], diff --git a/tests/inspiredesign-product-readiness.test.ts b/tests/inspiredesign-product-readiness.test.ts index a23c59da..8a003433 100644 --- a/tests/inspiredesign-product-readiness.test.ts +++ b/tests/inspiredesign-product-readiness.test.ts @@ -1020,6 +1020,44 @@ describe("inspiredesign product readiness helpers", () => { snapshotReadyReferenceCount: 1 })); + const genericTrailingSlashReference = { + id: "generic-trailing-slash", + url: "https://www.apple.com/airpods-pro", + evidenceAuthority: "snapshot_ready" + }; + expect(deriveInspiredesignProductReadinessFields({ + nextStepGuidance: { readiness: "ready", doNotProceedIf: [] }, + rankedReferences: [ + genericTrailingSlashReference, + { + id: "pin-ref", + url: "https://www.pinterest.com/pin/1234567890/", + evidenceAuthority: "pin_media_ready", + capturedVia: ["pin_media_ready"] + } + ], + screenshotIndex: [{ + referenceId: "generic-trailing-slash", + url: "https://www.apple.com/airpods-pro", + sourceUrl: "https://www.apple.com/airpods-pro/", + path: "visual-evidence/generic-trailing-slash/viewport.png", + sha256: "e".repeat(64), + bytes: 55_720, + warnings: [] + }], + pinMediaIndex: [makePinterestPinMediaIndexEntry()], + qualitySummary: { missingScreenshotCount: 0 } + })).toEqual(expect.objectContaining({ + productSuccess: true, + artifactAuthority: "product_ready", + evidenceAuthority: "pin_media_ready", + rankedReferenceCount: 2, + authoritativeReferenceCount: 2, + snapshotReadyReferenceCount: 1, + motionReadyReferenceCount: 0, + pinMediaReadyReferenceCount: 1 + })); + expect(deriveInspiredesignProductReadinessFields({ nextStepGuidance: { readiness: "ready", doNotProceedIf: [] }, rankedReferences: [{ @@ -1151,7 +1189,7 @@ describe("inspiredesign product readiness helpers", () => { })).toBe(true); expect(isInspiredesignAuthoritativeRankedReference(rankedReference, { pinMedia: [makePinterestPinMediaIndexEntry({ warnings: ["login_or_challenge_state"] })] - })).toBe(true); + })).toBe(false); expect(isInspiredesignAuthoritativeRankedReference(rankedReference, { pinMedia: [makePinterestPinMediaIndexEntry({ warnings: ["interface_chrome_shell"], @@ -1197,6 +1235,21 @@ describe("inspiredesign product readiness helpers", () => { pinMediaReadyReferenceCount: 1 })); + expect(deriveInspiredesignProductReadinessFields({ + nextStepGuidance: { readiness: "ready", doNotProceedIf: [] }, + pinterestEvidenceRequired: true, + rankedReferences: [rankedReference], + pinMediaIndex: [makePinterestPinMediaIndexEntry({ warnings: ["login_or_challenge_state"] })], + qualitySummary: { missingScreenshotCount: 0 } + })).toEqual(expect.objectContaining({ + productSuccess: false, + artifactAuthority: "diagnostic_only", + evidenceAuthority: "diagnostic_only", + rankedReferenceCount: 1, + authoritativeReferenceCount: 0, + pinMediaReadyReferenceCount: 0 + })); + expect(deriveInspiredesignProductReadinessFields({ nextStepGuidance: { readiness: "ready", doNotProceedIf: [] }, pinterestEvidenceRequired: true, diff --git a/tests/providers-artifacts-workflows.test.ts b/tests/providers-artifacts-workflows.test.ts index 9ca6bc68..f1d3aa44 100644 --- a/tests/providers-artifacts-workflows.test.ts +++ b/tests/providers-artifacts-workflows.test.ts @@ -941,6 +941,7 @@ describe("artifact and workflow runtime", () => { const output = await runResearchWorkflow(runtime, { topic: "coffee shop website design inspiration", days: 30, + sources: ["web", "community", "social"], sourceSelection: "auto", mode: "json", outputDir: root diff --git a/tests/providers-inspiredesign-capture.test.ts b/tests/providers-inspiredesign-capture.test.ts index ab407ce2..efe8bd51 100644 --- a/tests/providers-inspiredesign-capture.test.ts +++ b/tests/providers-inspiredesign-capture.test.ts @@ -1703,6 +1703,69 @@ describe("inspiredesign capture helper", () => { } }); + it("launches explicit managed canonical Pinterest pin media capture with the requested persistent profile", async () => { + const manager = { + launch: vi.fn().mockResolvedValue({ sessionId: "session-managed-profile-pin-media" }), + setSessionChallengeAutomationMode: vi.fn(), + goto: vi.fn().mockResolvedValue(undefined), + waitForLoad: vi.fn().mockResolvedValue(undefined), + snapshot: vi.fn().mockResolvedValue({ + url: "https://www.pinterest.com/pin/27654985208435505/", + title: "Pinterest", + content: "Pin media", + refCount: 1, + warnings: [] + }), + clonePageHtmlWithOptions: vi.fn().mockResolvedValue({ + html: "" + }), + capturePinterestPinMedia: vi.fn().mockResolvedValue({ + status: "captured", + sourceUrl: "https://www.pinterest.com/pin/27654985208435505/", + targetId: "target-pin", + kind: "image", + path: "/tmp/managed-profile-primary-pin-media", + mediaUrl: "https://i.pinimg.com/originals/pin.jpg", + contentType: "image/jpeg", + naturalWidth: 1200, + naturalHeight: 1600, + candidateSelector: "[data-test-id='closeup-image-main-MainPinImage']", + rejectedCandidates: [] + }), + disconnect: vi.fn().mockResolvedValue(undefined) + }; + + const { captureInspiredesignPrimaryPinMediaEvidenceFromManager } = await import("../src/inspiredesign/capture"); + await captureInspiredesignPrimaryPinMediaEvidenceFromManager( + manager as never, + "https://www.pinterest.com/pin/27654985208435505/", + { + referenceId: "pin-ref", + pinMediaEvidencePath: "/tmp/managed-profile-primary-pin-media", + browserMode: "managed", + profile: "pinterest-design", + cookiePolicyOverride: "off", + challengeAutomationMode: "browser_with_helper" + } + ); + + expect(manager.launch).toHaveBeenCalledTimes(1); + expect(manager.launch).toHaveBeenCalledWith({ + headless: true, + startUrl: "about:blank", + persistProfile: true, + noExtension: true, + profile: "pinterest-design" + }, expect.any(Number)); + expect(manager.goto).toHaveBeenCalledTimes(1); + expect(manager.goto).toHaveBeenCalledWith( + "session-managed-profile-pin-media", + "https://www.pinterest.com/pin/27654985208435505/", + "domcontentloaded", + expect.any(Number) + ); + }); + it("lets direct pin media proof override page-level Pinterest login chrome", async () => { const manager = { launch: vi.fn().mockResolvedValue({ sessionId: "session-primary-pin-media-login-shell" }), diff --git a/tests/providers-inspiredesign-workflow.test.ts b/tests/providers-inspiredesign-workflow.test.ts index ee0aa5e6..09c565b5 100644 --- a/tests/providers-inspiredesign-workflow.test.ts +++ b/tests/providers-inspiredesign-workflow.test.ts @@ -31,6 +31,7 @@ import type { ProviderError, ProviderFailureEntry, ProviderReasonCode, + ProviderRunOptions, ProviderSource } from "../src/providers/types"; import type { NextStepGuidance } from "../src/guidance/types"; @@ -899,15 +900,15 @@ describe("inspiredesign workflow", () => { records: [ normalizeRecord("web/default", "web", { url: input.url, - title: "Visual reference", - content: "Full-bleed hero with cinematic product staging and refined CTA." + title: "Visual harvest product staging reference", + content: "Visual harvest product staging full-bleed hero with refined CTA." }) ] }) }); const output = await runInspiredesignWorkflow(runtime, { - brief: "Design a visual harvest landing page", + brief: "Design a visual harvest product staging landing page", urls: ["https://example.com/visual"], outputDir, mode: "path", @@ -920,7 +921,7 @@ describe("inspiredesign workflow", () => { stagedTempPath = options.visualEvidencePath; writeFileSync(options.visualEvidencePath, Buffer.from("png bytes")); return { - ...makeCapture("Visual reference full-bleed hero cinematic product staging refined CTA"), + ...makeCapture("Visual harvest product staging reference full-bleed hero refined CTA"), visual: { status: "captured", kind: "viewport", @@ -946,6 +947,13 @@ describe("inspiredesign workflow", () => { screenshots: Array<{ path: string; sha256: string; bytes: number }>; }; + expect(output).toEqual(expect.objectContaining({ + productSuccess: true, + artifactAuthority: "product_ready", + evidenceAuthority: "snapshot_ready", + snapshotReadyReferenceCount: 1, + authoritativeReferenceCount: 1 + })); expect(meta.selection).toEqual(expect.objectContaining({ visual_evidence: "required", capture_mode: "deep" @@ -969,7 +977,7 @@ describe("inspiredesign workflow", () => { })); expect(readFileSync(join(artifactPath, "visual-evidence/b710f7bd0da7/viewport.png"))).toEqual(Buffer.from("png bytes")); expect(rankedReferencesJson).toContain("\"rank\": 1"); - expect(metaPrompt).toContain("Rank 1: Visual reference"); + expect(metaPrompt).toContain("Rank 1: Visual harvest product staging reference"); for (const jsonText of [visualEvidenceJson, screenshotIndexJson, rankedReferencesJson]) { expect(jsonText).not.toContain(stagedTempPath); expect(jsonText).not.toContain("base64"); @@ -1824,7 +1832,7 @@ describe("inspiredesign workflow", () => { expect(JSON.stringify(evidence.mediaAnalysis?.savedMediaMotionNotice)).not.toContain("product_ready"); }); - it("keeps trusted Pinterest pin media authoritative when page chrome reports login state", async () => { + it("keeps login-state Pinterest pin media diagnostic while preserving captured bytes", async () => { const runtime = toRuntime({ fetch: async (input: { url: string }) => makeAggregate({ records: [ @@ -1892,9 +1900,11 @@ describe("inspiredesign workflow", () => { const meta = output.meta as InspiredesignWorkflowMeta; expect(output).toEqual(expect.objectContaining({ - productSuccess: true, - artifactAuthority: "product_ready", - evidenceAuthority: "pin_media_ready" + productSuccess: false, + artifactAuthority: "diagnostic_only", + evidenceAuthority: "diagnostic_only", + pinMediaReadyReferenceCount: 0, + authoritativeReferenceCount: 0 })); expect(pinMediaEvidence.pinMediaEvidence[0]?.pinMedia).toEqual(expect.objectContaining({ authority: "design_evidence", @@ -4874,6 +4884,187 @@ describe("inspiredesign workflow", () => { expect(output.suggestedNextAction).toEqual(meta.nextStepGuidance?.primaryAction.summary); }); + it.each([ + { label: "canonical provider", providers: ["social/pinterest"], urls: [] }, + { label: "bare provider alias", providers: ["pinterest"], urls: [] }, + { label: "Pinterest URL recovery", providers: undefined, urls: ["https://www.pinterest.com/pin/61572719900827789/"] } + ])("applies Pinterest extension auth defaults for $label when extension readiness is current", async ({ providers, urls }) => { + const pinUrl = "https://www.pinterest.com/pin/61572719900827789/"; + const fetch = vi.fn(async (input: { url: string }) => { + if (input.url.includes("/search/pins/")) { + return makeAggregate({ + records: [makePinterestSearchShellDiscoveryRecord(input.url, pinUrl)] + }); + } + return makeAggregate({ + records: [makePinterestDiscoveredImagePinRecord(input.url)] + }); + }); + + await runInspiredesignWorkflow(toRuntime({ fetch }), { + brief: "Design a cinematic photography studio landing page", + harvest: true, + query: urls.length === 0 ? "premium photography studio landing page" : undefined, + providers, + urls, + extensionAuthReady: true, + visualEvidence: "off", + mode: "json" + }); + + const runtimePolicy = (fetch.mock.calls[0]?.[1] as ProviderRunOptions | undefined)?.runtimePolicy; + expect(runtimePolicy).toEqual(expect.objectContaining({ + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: "browser_with_helper" + })); + }); + + it.each([ + { label: "managed browser mode", input: { browserMode: "managed" as const }, expected: { browserMode: "managed" } }, + { label: "profile", input: { profile: "pinterest-design" }, expected: { profile: "pinterest-design", browserMode: "managed" } }, + { label: "cookies off", input: { useCookies: false }, expected: { useCookies: false } }, + { label: "cookie policy off", input: { cookiePolicyOverride: "off" as const }, expected: { cookiePolicyOverride: "off" } }, + { label: "challenge automation off", input: { challengeAutomationMode: "off" as const }, expected: { challengeAutomationMode: "off" } } + ])("preserves explicit Pinterest auth setting for $label", async ({ input, expected }) => { + const pinUrl = "https://www.pinterest.com/pin/61572719900827789/"; + const fetch = vi.fn(async (request: { url: string }) => { + if (request.url.includes("/search/pins/")) { + return makeAggregate({ + records: [makePinterestSearchShellDiscoveryRecord(request.url, pinUrl)] + }); + } + return makeAggregate({ + records: [makePinterestDiscoveredImagePinRecord(request.url)] + }); + }); + + await runInspiredesignWorkflow(toRuntime({ fetch }), { + brief: "Design a cinematic photography studio landing page", + harvest: true, + query: "premium photography studio landing page", + providers: ["social/pinterest"], + extensionAuthReady: true, + visualEvidence: "off", + mode: "json", + ...input + }); + + const runtimePolicy = (fetch.mock.calls[0]?.[1] as ProviderRunOptions | undefined)?.runtimePolicy; + expect(runtimePolicy).toEqual(expect.objectContaining(expected)); + expect(runtimePolicy).not.toEqual(expect.objectContaining({ + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: "browser_with_helper" + })); + }); + + it("applies Pinterest extension cookie defaults when browser_with_helper is already set", async () => { + const pinUrl = "https://www.pinterest.com/pin/61572719900827789/"; + const fetch = vi.fn(async (input: { url: string }) => { + if (input.url.includes("/search/pins/")) { + return makeAggregate({ + records: [makePinterestSearchShellDiscoveryRecord(input.url, pinUrl)] + }); + } + return makeAggregate({ + records: [makePinterestDiscoveredImagePinRecord(input.url)] + }); + }); + + await runInspiredesignWorkflow(toRuntime({ fetch }), { + brief: "Design a cinematic photography studio landing page", + harvest: true, + query: "premium photography studio landing page", + providers: ["pinterest"], + extensionAuthReady: true, + challengeAutomationMode: "browser_with_helper", + visualEvidence: "off", + mode: "json" + }); + + const runtimePolicy = (fetch.mock.calls[0]?.[1] as ProviderRunOptions | undefined)?.runtimePolicy; + expect(runtimePolicy).toEqual(expect.objectContaining({ + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: "browser_with_helper" + })); + }); + + it("does not apply Pinterest extension auth defaults for non-Pinterest harvest", async () => { + const search = vi.fn(async () => makeAggregate({ + records: [ + normalizeRecord("web/default", "web", { + url: "https://example.com/reference", + title: "Editorial reference", + content: "Premium editorial layout reference." + }) + ] + })); + const fetch = vi.fn(async (input: { url: string }) => makeAggregate({ + records: [ + normalizeRecord("web/default", "web", { + url: input.url, + title: "Fetched editorial reference", + content: "Fetched premium editorial layout reference." + }) + ] + })); + + await runInspiredesignWorkflow(toRuntime({ fetch, search }), { + brief: "Design a premium editorial docs landing page", + harvest: true, + query: "premium editorial docs references", + providers: ["web/default"], + extensionAuthReady: true, + maxReferences: 1, + visualEvidence: "off", + mode: "json" + }); + + const runtimePolicy = (search.mock.calls[0]?.[1] as ProviderRunOptions | undefined)?.runtimePolicy; + expect(runtimePolicy).not.toEqual(expect.objectContaining({ + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: "browser_with_helper" + })); + }); + + it("does not apply Pinterest extension auth defaults when readiness is missing", async () => { + const pinUrl = "https://www.pinterest.com/pin/61572719900827789/"; + const fetch = vi.fn(async (input: { url: string }) => { + if (input.url.includes("/search/pins/")) { + return makeAggregate({ + records: [makePinterestSearchShellDiscoveryRecord(input.url, pinUrl)] + }); + } + return makeAggregate({ + records: [makePinterestDiscoveredImagePinRecord(input.url)] + }); + }); + + await runInspiredesignWorkflow(toRuntime({ fetch }), { + brief: "Design a cinematic photography studio landing page", + harvest: true, + query: "premium photography studio landing page", + providers: ["social/pinterest"], + visualEvidence: "off", + mode: "json" + }); + + const runtimePolicy = (fetch.mock.calls[0]?.[1] as ProviderRunOptions | undefined)?.runtimePolicy; + expect(runtimePolicy).not.toEqual(expect.objectContaining({ + browserMode: "extension", + useCookies: true, + cookiePolicyOverride: "required", + challengeAutomationMode: "browser_with_helper" + })); + }); + it("allows managed-profile Pinterest discovery when the workflow owns profile provenance", async () => { const fetch: ReferenceRetrievalPort["fetch"] = vi.fn(async (input) => makeAggregate({ records: [ diff --git a/tests/providers-product-video-workflow.test.ts b/tests/providers-product-video-workflow.test.ts index 48586955..7412867e 100644 --- a/tests/providers-product-video-workflow.test.ts +++ b/tests/providers-product-video-workflow.test.ts @@ -419,6 +419,56 @@ describe("product-video substrate adoption", () => { expect(output.screenshots).toEqual([]); }); + it("carries Best Buy country-selection provider guidance into product-video handoff", async () => { + const fetch = vi.fn(async () => makeAggregate({ + records: [makeRecord({ + provider: "shopping/bestbuy", + url: "https://www.bestbuy.com/site/logitech-mx-master-3s/6502574.p?skuId=6502574" + })], + failures: [makeFailure("shopping/bestbuy", "shopping", { + message: "Best Buy country-selection interstitial is blocking the product detail page.", + reasonCode: "env_limited", + details: { + constraint: { + kind: "render_required", + evidenceCode: "bestbuy_international_gate", + providerShell: "bestbuy_international_gate", + message: "Choose a country or region to continue." + } + } + })] + })); + + const output = await runProductVideoWorkflow(toRuntime({ fetch }), productVideoInput({ + product_url: "https://www.bestbuy.com/site/logitech-mx-master-3s/6502574.p?skuId=6502574" + })); + const recoveryStep = (output.suggestedSteps as Array<{ reason?: string }>).find((step) => ( + step.reason?.includes("Best Buy country-selection interstitial") + )); + const meta = output.meta as { + primaryConstraint?: { + summary?: string; + guidance?: { + reason?: string; + recommendedNextCommands?: string[]; + }; + }; + }; + + expect(meta.primaryConstraint?.summary).toBe( + "Bestbuy is blocked by the Best Buy country-selection interstitial." + ); + expect(meta.primaryConstraint?.guidance?.reason).toBe( + "Bestbuy needs the Best Buy country-selection interstitial cleared before retrying." + ); + expect(recoveryStep?.reason).toContain( + "Bestbuy is blocked by the Best Buy country-selection interstitial." + ); + expect(recoveryStep?.reason).toContain( + "Choose the shopping country or region in the preserved browser session." + ); + }); + it("uses presentation compiler output for noisy marketplace copy and features", async () => { const fetch = vi.fn(async () => makeAggregate({ records: [makeRecord({ diff --git a/tests/providers-research-executor.test.ts b/tests/providers-research-executor.test.ts index 7890eba9..361bc005 100644 --- a/tests/providers-research-executor.test.ts +++ b/tests/providers-research-executor.test.ts @@ -1,4 +1,7 @@ -import { describe, expect, it, vi } from "vitest"; +import { mkdtemp, rm } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { compileResearchExecutionPlan, createResearchFetchStepId, @@ -10,9 +13,16 @@ import { resolveResearchWebFetchCandidates } from "../src/providers/research-executor"; import { buildWorkflowResumeEnvelope, type WorkflowCheckpoint } from "../src/providers/workflow-contracts"; -import type { ProviderExecutor, ResearchRunInput } from "../src/providers/workflows"; +import { + runResearchWorkflow, + workflowTestUtils, + type ProviderExecutor, + type ResearchRunInput +} from "../src/providers/workflows"; import type { NormalizedRecord, ProviderAggregateResult, ProviderSource } from "../src/providers/types"; +const tempDirs: string[] = []; + const isoHoursAgo = (hours: number): string => new Date(Date.now() - hours * 60 * 60 * 1000).toISOString(); const makeRecord = (overrides: Partial = {}): NormalizedRecord => ({ @@ -81,6 +91,13 @@ const researchInput = (overrides: Partial = {}): ResearchRunIn }); describe("research workflow executor", () => { + afterEach(async () => { + workflowTestUtils.resetProviderSignalState(); + await Promise.all(tempDirs.splice(0).map(async (directory) => { + await rm(directory, { recursive: true, force: true }); + })); + }); + it("compiles research search steps, timebox state, exclusions, and capped follow-up budget", () => { const plan = compileResearchExecutionPlan({ input: researchInput({ @@ -89,14 +106,14 @@ describe("research workflow executor", () => { includeEngagement: true }), now: new Date("2026-03-30T23:00:00.000Z"), - getDegradedProviders: () => new Set(["social/youtube", "shopping/amazon"]) + getDegradedProviders: () => new Set(["social/youtube", "community/reddit", "shopping/amazon"]) }); expect(plan.compiled).toMatchObject({ topic: "phase 4 research", sourceSelection: "auto", - resolvedSources: ["web", "community", "social"], - autoExcludedProviders: ["social/youtube"], + resolvedSources: ["web", "community"], + autoExcludedProviders: ["community/reddit"], searchLimit: 5, followUpFetchLimit: 5, allowFollowUpWebFetch: true @@ -108,8 +125,7 @@ describe("research workflow executor", () => { }); expect(plan.plan.steps.map((step) => step.id)).toEqual([ "search:web", - "search:community", - "search:social" + "search:community" ]); expect(plan.plan.steps[0]?.input.filters).toEqual({ include_engagement: true, @@ -124,14 +140,6 @@ describe("research workflow executor", () => { hopLimit: 1, expansionPerRecord: 2 }); - expect(plan.plan.steps[2]?.input.filters).toEqual({ - include_engagement: true, - timebox_from: plan.compiled.timebox.from, - timebox_to: plan.compiled.timebox.to, - pageLimit: 1, - hopLimit: 0, - expansionPerRecord: 0 - }); }); it("rejects malformed checkpoint state before execution", () => { @@ -222,6 +230,67 @@ describe("research workflow executor", () => { expect(allPlan.compiled.sourceSelection).toBe("all"); expect(allPlan.compiled.resolvedSources).toEqual(["web", "community", "social"]); expect(allPlan.compiled.allowFollowUpWebFetch).toBe(true); + + const socialPlan = compileResearchExecutionPlan({ + input: researchInput({ + sourceSelection: "social" + }), + now: new Date("2026-03-30T23:00:00.000Z") + }); + + expect(socialPlan.compiled.sourceSelection).toBe("social"); + expect(socialPlan.compiled.resolvedSources).toEqual(["social"]); + expect(socialPlan.compiled.allowFollowUpWebFetch).toBe(false); + expect(socialPlan.plan.steps.map((step) => step.id)).toEqual(["search:social"]); + }); + + it("scopes fallback workflow alerts to selected research providers", async () => { + workflowTestUtils.trackProviderSignals(makeAggregate({ + ok: false, + providerOrder: ["social/youtube"], + failures: [{ + provider: "social/youtube", + source: "social", + error: { + code: "rate_limited", + message: "social provider rate limited", + retryable: true, + reasonCode: "rate_limited", + provider: "social/youtube", + source: "social" + } + }], + metrics: { attempted: 1, succeeded: 0, failed: 1, retries: 0, latencyMs: 1 } + })); + const outputDir = await mkdtemp(join(tmpdir(), "odb-research-alert-scope-")); + tempDirs.push(outputDir); + const search = vi.fn(async (_input, options) => { + const source = options?.source === "community" ? "community" : "web"; + const provider = source === "community" ? "community/reddit" : "web/default"; + return makeAggregate({ + sourceSelection: source, + providerOrder: [provider], + records: [makeRecord({ + id: `${source}-result`, + source, + provider, + url: `https://example.com/${source}`, + title: `${source} research result` + })] + }); + }); + + const output = await runResearchWorkflow(toRuntime({ search }), researchInput({ + sourceSelection: "auto", + outputDir + })); + const meta = output.meta as { + selection?: { resolved_sources?: string[] }; + alerts?: Array>; + }; + + expect(meta.selection?.resolved_sources).toEqual(["web", "community"]); + expect(meta.alerts).toEqual([]); }); it("accepts enriched checkpoint aggregates with mixed provider sources and optional metadata", () => { diff --git a/tests/providers-shopping-report.test.ts b/tests/providers-shopping-report.test.ts index 43a5e972..44b02acb 100644 --- a/tests/providers-shopping-report.test.ts +++ b/tests/providers-shopping-report.test.ts @@ -175,6 +175,32 @@ describe("shopping-report", () => { expect(fail.gate.status).toBe("fail"); }); + it("describes readiness criteria against the current evidence set without relaxing gate status", () => { + const pass = buildShoppingBriefing(briefingInput()); + const partial = buildShoppingBriefing(briefingInput({ + offers: [offer({ id: "unknown", availability: "unknown" })] + })); + const fail = buildShoppingBriefing(briefingInput({ offers: [] })); + const thresholds = partial.gate.criteria.map((entry) => entry.threshold).join("\n"); + const availability = partial.gate.criteria.find((entry) => entry.label === "Availability"); + const titleQuality = partial.gate.criteria.find((entry) => entry.label === "Query and title quality"); + + expect(pass.gate.status).toBe("pass"); + expect(partial.gate.status).toBe("partial"); + expect(fail.gate.status).toBe("fail"); + expect(thresholds).toContain("No unknown or out-of-stock offer in the current evidence set"); + expect(thresholds).toContain("No weak relevance or suspicious title in the current evidence set"); + expect(thresholds).not.toContain("confident set"); + expect(availability).toMatchObject({ + observed: "1 unknown, 0 out of stock", + passed: false + }); + expect(titleQuality).toMatchObject({ + observed: "0 weak relevance, 0 suspicious title", + passed: true + }); + }); + it("renders missing buyer limitations without downgrading otherwise healthy pass guidance", () => { const briefing = buildShoppingBriefing(briefingInput()); const markdown = renderShoppingBriefingMarkdown(briefing); diff --git a/tests/providers-workflow-primitives.test.ts b/tests/providers-workflow-primitives.test.ts index 2b64c084..c868714e 100644 --- a/tests/providers-workflow-primitives.test.ts +++ b/tests/providers-workflow-primitives.test.ts @@ -551,37 +551,43 @@ describe("workflow primitives", () => { expect(rendered.files.find((file) => file.path === "meta.json")?.content).toBe(meta); const context = rendered.files.find((file) => file.path === "deals-context.json")?.content as { query: string; + buyingReadiness: { status: string }; highlights: string[]; offers: ShoppingOffer[]; meta: Record; }; - expect(Object.keys(context).sort()).toEqual(["highlights", "meta", "offers", "query"]); + expect(Object.keys(context).sort()).toEqual(["buyingReadiness", "highlights", "meta", "offers", "query"]); + expect(context.buyingReadiness.status).toBe("pass"); expect(context.highlights[0]).toContain("Buying readiness:"); expect(context.highlights[1]).toContain("Recommendation:"); expect(context.highlights).toContain("Key constraint: No major report constraint surfaced."); expect(context.offers).toBe(offers); if (mode === "compact") { - expect(Object.keys(rendered.response).sort()).toEqual(["meta", "mode", "summary"]); + expect(Object.keys(rendered.response).sort()).toEqual(["buyingReadiness", "meta", "mode", "summary"]); + expect((rendered.response.buyingReadiness as { status: string }).status).toBe("pass"); expect(String(rendered.response.summary)).toContain("Buying readiness:"); expect(String(rendered.response.summary)).toContain("Recommendation:"); expect(String(rendered.response.summary)).not.toContain("deal="); expect(String(rendered.response.summary)).not.toMatch(/^1\. /m); } if (mode === "json") { - expect(Object.keys(rendered.response).sort()).toEqual(["meta", "mode", "offers"]); + expect(Object.keys(rendered.response).sort()).toEqual(["buyingReadiness", "meta", "mode", "offers"]); expect(rendered.response).toMatchObject({ mode: "json", offers, meta }); + expect((rendered.response.buyingReadiness as { status: string }).status).toBe("pass"); } if (mode === "md") { - expect(Object.keys(rendered.response).sort()).toEqual(["markdown", "meta", "mode"]); + expect(Object.keys(rendered.response).sort()).toEqual(["buyingReadiness", "markdown", "meta", "mode"]); expect(rendered.response).toMatchObject({ mode: "md", markdown: dealsMarkdown, meta }); + expect((rendered.response.buyingReadiness as { status: string }).status).toBe("pass"); } if (mode === "context") { - expect(Object.keys(rendered.response).sort()).toEqual(["context", "meta", "mode"]); + expect(Object.keys(rendered.response).sort()).toEqual(["buyingReadiness", "context", "meta", "mode"]); expect(rendered.response).toMatchObject({ mode: "context", context, meta }); + expect((rendered.response.buyingReadiness as { status: string }).status).toBe("pass"); } if (mode === "path") { - expect(rendered.response).toEqual({ mode: "path", meta }); + expect(rendered.response).toEqual({ mode: "path", buyingReadiness: expect.objectContaining({ status: "pass" }), meta }); } } }); diff --git a/tests/providers-workflows-branches.test.ts b/tests/providers-workflows-branches.test.ts index aaaaa3b2..2c12105f 100644 --- a/tests/providers-workflows-branches.test.ts +++ b/tests/providers-workflows-branches.test.ts @@ -1064,9 +1064,11 @@ describe("workflow branch coverage", () => { expect(workflowTestUtils.getDegradedProviders()).toContain("social/youtube"); + const calledSources: ProviderSource[] = []; const autoRuntime = toRuntime({ search: async (_input, options) => { const source = (options?.source ?? "web") as ProviderSource; + calledSources.push(source); const provider = source === "social" ? "social/youtube" : `${source}/default`; return makeAggregate({ sourceSelection: source, @@ -1088,10 +1090,13 @@ describe("workflow branch coverage", () => { }); const providers = (output.records as Array<{ provider: string }>).map((record) => record.provider); + const selection = (output.meta as { + selection: { excluded_providers?: string[]; resolved_sources: string[] }; + }).selection; + expect(calledSources).toEqual(["web", "community"]); + expect(selection.resolved_sources).toEqual(["web", "community"]); expect(providers).not.toContain("social/youtube"); - expect((output.meta as { - selection: { excluded_providers?: string[] }; - }).selection.excluded_providers).toContain("social/youtube"); + expect(selection.excluded_providers).toBeUndefined(); }); it("excludes degraded shopping providers from default provider routing but allows explicit override", async () => { @@ -2873,7 +2878,7 @@ describe("workflow branch coverage", () => { const meta = output.meta as { selection: { source_selection: string; resolved_sources: string[] } }; expect(meta.selection).toEqual({ source_selection: "auto", - resolved_sources: ["web", "community", "social"] + resolved_sources: ["web", "community"] }); }); @@ -5089,6 +5094,7 @@ describe("workflow branch coverage", () => { const output = await runResearchWorkflow(runtime, { topic: "coffee shop website design inspiration", + sources: ["web", "community", "social"], sourceSelection: "auto", days: 30, mode: "json" diff --git a/tests/tools-workflows.test.ts b/tests/tools-workflows.test.ts index 26194d3e..66db87b8 100644 --- a/tests/tools-workflows.test.ts +++ b/tests/tools-workflows.test.ts @@ -440,8 +440,7 @@ describe("workflow tools", () => { })); expect((research.meta as { selection: { resolved_sources: string[] } }).selection.resolved_sources).toEqual([ "web", - "community", - "social" + "community" ]); const shopping = parse(await shoppingTool.execute({ diff --git a/tests/workflow-handoff.test.ts b/tests/workflow-handoff.test.ts index 7744523a..475e1590 100644 --- a/tests/workflow-handoff.test.ts +++ b/tests/workflow-handoff.test.ts @@ -291,6 +291,31 @@ describe("workflow handoff builders", () => { ); }); + it("adds provider recovery guidance to product-video handoff steps", () => { + const handoff = buildProductVideoSuccessHandoff({ + productUrl: "https://www.bestbuy.com/site/logitech-mx-master-3s/6502574.p?skuId=6502574", + primaryConstraintSummary: "Bestbuy is blocked by the Best Buy country-selection interstitial.", + providerGuidance: { + reason: "Bestbuy needs the Best Buy country-selection interstitial cleared before retrying.", + recommendedNextCommands: [ + "Choose the shopping country or region in the preserved browser session.", + "Rerun the same provider or workflow after the Best Buy PDP or search results are visible." + ] + } + }); + + expect(handoff.suggestedSteps[1]?.reason).toContain( + "Bestbuy is blocked by the Best Buy country-selection interstitial." + ); + expect(handoff.suggestedSteps[1]?.reason).toContain( + "Bestbuy needs the Best Buy country-selection interstitial cleared before retrying." + ); + expect(handoff.suggestedSteps[1]?.reason).toContain( + "Choose the shopping country or region in the preserved browser session." + ); + expect(handoff.suggestedSteps[2]?.command).toBe(`${PRODUCT_VIDEO_BRIEF_HELPER_PATH} /manifest.json`); + }); + it("gates product-video handoff wording from readiness", () => { const partial = buildProductVideoSuccessHandoff({ productUrl: "https://shop.example/item-1",