ffroliva / scenario
Pre-implementation edge-case and scenario explorer for gflow-cli. Decomposes a proposed feature or change across 13 structured dimensions specific to gflow-cli's failure surfaces (WAF/reCAPTCHA, Playwright selectors, auth token lifecycle, batch resume, data layer safety, cross-platform paths). Produces a severity-ranked scenario table to feed into the PLAN spec and the test matrix before EXECUTE begins.
scenario — Edge Case & Scenario Explorer
Systematic pre-implementation scenario analysis. For a given feature or change,
produces a severity-ranked table of test scenarios across 13 dimensions tuned
to gflow-cli's known failure surfaces. Feed the output into PLAN.md tasks andtests/features/ BDD scenarios before entering EXECUTE mode.
When to invoke
Use before implementing any of:
- A new generation path (T2V, I2V, R2V, batch manifest runner)
- Transport changes (any new HTTP call against
aisandbox-pa.googleapis.com) - Auth or session changes (new strategy, cookie extraction, SAPISIDHASH wiring)
- Selector cascade changes (
ONBOARDING_SELECTORS,NEW_PROJECT_SELECTORS,FRAME_SLOTS_STRUCT,IMAGE_MODEL_OPTION_SELECTORS) - Data layer changes (schema migration, new
OperationRecordercallsite, redaction change) - New CLI subcommand, flag, or exit code
Skip for: pure doc changes, CHANGELOG/version bumps, scripts/ tooling with no production callpath.
Invocation
/gflow:scenario <feature or change description><feature or change description> is a brief summary of what you're about to implement. Examples:
- "SAPISIDHASH auth header wired into
_post_jsonforaisandbox-paroutes" - "gflow video batch manifest ledger (skip already-completed rows)"
- "Image model picker converted from English has-text to structural anchor"
- "CDP-attach transport as opt-in alongside ui_automation"
The 13 dimensions
For each dimension, enumerate scenarios that are non-obvious — do not list
things that a basic happy-path test already covers. Focus on things that break
in production but pass in unit tests.
D1 — Auth & session lifecycle
The SAPISID cookie expires. The user re-runs gflow auth login mid-batch. A
profile is created but the Flow session was never verified. The session is valid
for labs.google tRPC but not for aisandbox-pa. Two profiles are in use
simultaneously (Chromium profile-lock).
D2 — WAF / reCAPTCHA scoring
A profile's WAF heat score is elevated from prior automation runs. reCAPTCHA
Enterprise detects navigator.webdriver=true despite the --disable-blink-features
stealth flag. The grecaptcha.execute() call times out or returns a challenge
that requires human interaction. The same token is submitted twice (single-use
token reuse). A batch run fires multiple rapid token mints within one session.
D3 — Selector cascade drift & Locale Invariance (Flow UI updates)
Google Flow ships a UI update that renames or restructures a selector anchor.FRAME_SLOTS_STRUCT matches zero elements (the PR #70 regression). The
structural-first tier matches but selects the wrong element (e.g., two elements
matching div[type='button'][aria-haspopup='dialog'] after Flow adds a new
dialog button). A non-English locale is used (e.g. GFLOW_CLI_LOCALE=pt-BR or a Brazilian Google account) and single-language English text selectors fail because they lack Tier 1 structural anchors (a[href*='changelog'], button:has(i:text('close'))) or Tier 2 multi-locale text cascades. A new locale is used whose CMP dialog is not in_ONBOARDING_TEXT_SELECTORS. The --lang=en-US Chromium arg is removed beforeIMAGE_MODEL_OPTION_SELECTORS is converted to structural anchors.
D4 — Batch manifest & resume
A 50-row TSV manifest dies at row 23 (auth expiry). On resume, rows 1–22 are
re-submitted and double-billed. A row has an empty start_image field (T2V)
while the column parser expected a path. The output path already exists on disk
from a prior run (overwrite vs skip decision). Two gflow video batch invocations
against the same profile run concurrently (Chromium profile-lock).
D5 — Concurrency & Page pool
GFLOW_CLI_CONCURRENCY=16 fans out 16 simultaneous page.evaluate() reCAPTCHA
token mints — do they share site key state safely? A Page is returned to the pool
while still in a modal dialog (state contamination for the next checkout). AQueueFull double-checkin race where two coroutines both attempt to return the
same Page object. asyncio.gather propagates one failure but leaves the other
coroutines in a half-completed state with no cleanup path.
D6 — Data layer (SQLite / DataStore)
DataStore migration runs on a schema that's one version ahead (newer-schema
detection should raise DataStoreError exit 16, not silently corrupt). AnOperationRecorder.on_started callback raises inside a generation loop — does
the batch continue or crash? GFLOW_CLI_HISTORY_PROMPTS=redacted is set — does
the new callsite respect the redaction gate? A Windows path with a drive letter
is stored in the DB and retrieved on Linux (or vice versa). The DB is on a
network filesystem and the write locks time out.
D7 — Error propagation & exit codes
A new exception class is added but not registered in EXIT_CODE_MAP. AFlowApiError subclass is raised inside a retry loop — does reraise=True
propagate the right typed exception or does tenacity eat it? WireFormatError
discovery payload logs the route body prefix — does it redact reCAPTCHA tokens
and auth headers? An unhandled exception escapes run_with_handlers — is it
SHA-256-hashed before logging (never raw message)?
D8 — Cross-platform paths
A Windows user has a Unicode character in their %LOCALAPPDATA% path. The
output directory path contains spaces. platformdirs returns a different base
path on macOS vs Linux vs Windows — does the feature hardcode any of these?PYTHONUTF8=1 is not set — are there cp1252 encoding traps on Windows for
prompt text or file names?
D9 — Transport edge cases
The HTTP response body is valid JSON but has an unexpected top-level key (should
emit WireFormatError with discovery payload, not crash). A video generation
response omits operations[0].operation.name (the omni-flash NULLflow_operation_id issue). A status-poll response arrives before the listener
is attached (_attach_status_response_listener race). A video download URL has
a googleapis.com host not in the SSRF allowlist.
D10 — Headless vs headed environment
The feature is run in a CI environment with no display server (Linux, no Xvfb).
The Playwright context is launched headed (user has GFLOW_CLI_HEADLESS=false)
and the window is closed manually mid-batch. `gflow auth login --browser
internal` is used on a machine where Playwright bundled Chromium is flagged by
Google's G12 block.
D11 — Input validation & boundary values
A prompt string is 4001 characters (Flow's apparent limit). A manifest TSV has
zero rows (after the header). --seed receives a negative integer or a string.-n 5 is passed to gflow image t2i (Flow's UI cap is x4). An aspect ratio
of 3:2 is passed (valid-looking but not in the whitelist). A --profile name
contains path-separator characters.
D12 — Observability & structured log contract
A new structlog event is emitted — is its key name stable and documented indocs/ARCHITECTURE.md? A new error_raised path: does it carry the full RFC
9457 Problem Details shape (type, title, status, detail, instance,remediation_hint)? correlation_id is bound at the process boundary — does
it propagate into the new code path or is it missing from the event?
D13 — MCP surface parity
gflow ships the same capability twice: as a CLI command and as an MCP tool. Every
edge case above therefore has an MCP twin — enumerate it, because the paths are not
the same code. The MCP tool can run direct or **queued throughworker/codec.py**, so a param that works via the CLI can be accepted and silently
dropped on the queued path. Ask: what does this feature look like invoked as an MCP
tool? Which errors reach the agent as a Problem Details envelope rather than an exit
code? Does the tool's docstring still describe the behaviour truthfully? The six
mirror axes are enumerated once, in skills/check/SKILL.md step 1b — do not restate
them here, scenario's job is only to name the MCP cases that need scenarios.
Artifact location
Write the analysis to docs/superpowers/plans/<YYYY-MM-DD>-<feature-slug>/SCENARIO.md
(same directory the subsequent /gflow:plan writes its PLAN.md into) and commit it
with the plan on the feature branch. Established by the image-upscale (#171) and
locale-selectors (#170) cycles — do not leave the analysis only in conversation.
Output format
# Scenario: <feature short title>
## Coverage map
<Which of the 13 dimensions are relevant for this feature? List active dimensions and why skipped dimensions are skipped.>
## Scenario table
| # | Dimension | Scenario | Severity | Expected behaviour | Test category |
|---|---|---|---|---|---|
| 1 | D2 WAF/reCAPTCHA | WAF score elevated on profile from prior run | Critical | WafRejectionError raised with remediation hint "let score decay or use a different profile"; exit code 403-mapped | E2E live (`@pytest.mark.live`) |
| … | … | … | … | … | … |
Severity: **Critical** (data loss / billed twice / unrecoverable) · **High** (feature broken, workaround exists) · **Medium** (degraded UX, explicit error) · **Low** (cosmetic or edge-only)
Test category: **Unit** (no I/O) · **Integration** (mocked HTTP/Playwright) · **BDD** (Gherkin feature file) · **E2E smoke** (`@pytest.mark.smoke`) · **E2E live** (`@pytest.mark.live`, opt-in)
## Must-cover before merge (Critical + High)
1. …
## Deferred (Medium + Low — log as issues, not blockers)
1. …
## Suggested BDD scenarios@e2e @e2e_auth
Feature: <feature name>
Scenario: <scenario title>
Given …
When …
Then …
**Tag by surface — the tag is the binding, not decoration.** pytest-bdd turns each
Gherkin tag into a pytest marker, and `addopts`' `-m 'not e2e …'` filters on exactly
that. So the tag decides where the scenario runs and who must run it:
| The scenario can only happen… | Feature-level tags | Bound from |
|---|---|---|
| in a real browser / against real Flow | `@e2e` + one cost tier (`@e2e_auth`, `@e2e_image`, `@e2e_video`, …) | `tests/e2e/test_<slug>_bdd.py` |
| in our own code (parsing, routing, exit codes, redaction) | none | `tests/features/test_<slug>_steps.py` |
One feature file is bound by exactly **one** module — bound twice, its scenarios run
twice. `tests/features/test_e2e_binding_guard.py` enforces this offline in four directions
(orphan, missing tier, untagged live binding, double binding), so an `@e2e` scenario
nobody wrote a test for fails normal CI. Mechanics:
[`docs/E2E_TESTING.md`](../../docs/E2E_TESTING.md) § BDD-bound e2e.
## Known-issues cross-reference
<Any scenario that maps to an existing KNOWN_ISSUES entry — link and note whether the proposed implementation resolves, mitigates, or is blocked by it.>Integration & Pipeline Continuation (Next Step Handoff)
- Run
/gflow:predictfirst to validate the approach (GO/CAUTION/STOP). - Run
/gflow:scenarioto enumerate edge cases and build the test matrix. - Use the "Must-cover before merge" list as the acceptance criteria for the PLAN.md task.
Add BDD scenarios before coding (TDD is non-negotiable per AGENTS.md), each
tagged by its surface per the table above — a browser-only scenario is bound fromtests/e2e/, and a mocked stand-in does not discharge it
([`skills/issue-resolve`](../issue-resolve/SKILL.md) § The Bug Lane, step 5).- Next step: Proactively announce: "BDD Scenarios generated. Next step: Phase 4 Implementation Plan (
/gflow:plan <feature>)."
Provenance
Adapted from vc-scenario in vibecode-pro-max-kit (assessment 2026-05-28).
13 dimensions re-scoped to gflow-cli's specific failure surfaces: Google WAF/reCAPTCHA, Playwright selector drift, auth token lifecycle, batch resume idempotency, SQLite data layer, RFC 9457 error propagation, cross-platform Windows/macOS/Linux paths.