Skip to content

fix: close the migrated-host race, and stop character create exiting 1 - #701

Merged
ffroliva merged 1 commit into
developfrom
bugfix/692-close-the-recheck-race
Sep 6, 2026
Merged

ffroliva merged 1 commit into
developfrom
bugfix/692-close-the-recheck-race

Conversation

@ffroliva

@ffroliva ffroliva commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

Two fixes, same family: a labs-only path that fails unclassified on the migrated host instead of exiting 36.

1. The reCAPTCHA re-check lost a race it did not need to (#692 residual)

#694 re-checks migration when a mint fails, but read page.url once, at the instant of failure. Playwright updates page.url on framenavigated, so a mint that dies while the navigation is committing reads the old host on an account that is in fact migrated — and the run exits 1 anyway.

It now polls for a bounded 1.5 s (0.25 s interval), turning "who won this instant" into "did the hop land at all". Error path only: a successful mint never waits.

2. character create died as a bare RuntimeError, exit 1, after 20 seconds

The character editor is labs-only — flow.google.com renders no prompt textbox for it, ever. The readiness gate burned its full 20 s timeout, then raised RuntimeError("Character editor not ready: prompt textbox not visible within 20 s"), which the CLI reports as "Unexpected error".

raise_if_migrated now runs after the post-goto settles (so the client-side hop has landed) and before the wait (so nobody pays 20 s to be told something knowable immediately).

# before
Unexpected error. Re-run with GFLOW_CLI_DEBUG_TRACEBACK=1…     exit 1, ~20s

# after, verified live on a moved account
Flow served the migrated flow.google.com frontend: …            exit 36, immediate

How both were found

Dogfooding. Producing a real two-character piece on a moved account, where character create is the first thing you reach for.

Fourth instance of one shape

#673 image path minted reCAPTCHA before the migration guard
#690 health_check matched only ".google"
#694 a handler catching only RecaptchaError
this a 20 s wait for a labs-only editor, and a single-read re-check

Each is a check encoding an assumption that was true on labs.google.

Test plan

  • 2 new tests, each watched fail first
  • The character test asserts the guard fires before the DOM is probed (page.locator must never be called), mirroring the existing TestMigratedOriginFailsFast, and pins exit 36 + non-retryable
  • Live: gflow character create on a moved account → exit 36 with the migration message (was exit 1 after 20 s)
  • tests/api — 1525 passed; test_ui_mode.py — 32 passed
  • ruff check / ruff format --check / uv run pyright src (0 errors) / doc links

MCP parity

No MCP surface. Both changes are inside the transport/client error-classification paths, below both doors. The MCP tools reach the identical code and inherit the corrected exit class automatically. No leaf, option, payload key or docstring changes.

Refs #692, #639

Two fixes, same family: a labs-only path that fails unclassified on the
migrated host instead of exiting 36.

1. THE RECAPTCHA RE-CHECK LOST A RACE IT DID NOT NEED TO (#692 residual).
   #694 re-checks migration when a mint fails, but read page.url ONCE, at the
   instant of failure. Playwright updates page.url on framenavigated, so a mint
   that dies WHILE the navigation is committing reads the old host on an
   account that is in fact migrated -- and the run exits 1 anyway. It now polls
   for a bounded 1.5s (0.25s interval), which turns "who won this instant" into
   "did the hop land at all". Error path only: a successful mint never waits,
   and a genuine labs-side RecaptchaError is delayed by at most the budget on
   its way to the user.

2. CHARACTER CREATE DIED AS A BARE RuntimeError, EXIT 1, AFTER 20 SECONDS.
   The character editor is labs-only; flow.google.com renders no prompt textbox
   for it, ever. So the readiness gate burned its full 20s timeout and raised
   RuntimeError("Character editor not ready: prompt textbox not visible within
   20 s"), which the CLI reports as "Unexpected error". raise_if_migrated now
   runs after the post-goto settles and before the wait -- after, so the
   client-side hop has landed; before, so nobody pays 20s to be told something
   knowable immediately.

Both found by dogfooding: producing a real two-character piece on a moved
account, where character create is the first thing you reach for.

Verified:
* 2 new tests, each watched fail first. The character one asserts the guard
  fires BEFORE the DOM is probed (page.locator must never be called), mirroring
  the existing TestMigratedOriginFailsFast, and pins exit 36 + non-retryable.
* live: `gflow character create` on a moved account now exits 36 with the
  migration message, was exit 1 after 20s
* tests/api 1525 passed; ui_mode 32 passed
* ruff / ruff format / pyright src (0 errors) / doc links

This is the fourth instance today of the same shape, after #673 (image path),
#690 (health_check knew only ".google") and #694 (a handler catching only
RecaptchaError). Each is a check that encodes an assumption true on labs.

Refs #692, #639
@ffroliva
ffroliva merged commit 7b0819c into develop Sep 6, 2026
15 checks passed
@ffroliva
ffroliva deleted the bugfix/692-close-the-recheck-race branch September 6, 2026 17:05
ffroliva added a commit that referenced this pull request Sep 6, 2026
The ledger recorded the bug as the fix. Its exit-code table presented
`character create` on a moved account exiting 36 as a v0.70.0 improvement,
and its not-verified list said CHARACTER entities "cannot run on any
account available here". Both were false, from one root: a 20 s readiness
timeout was read as proof the feature does not exist on flow.google.com,
and #701 then added a guard that aborted before probing the DOM — which
made the claim unfalsifiable, because no run could look.

#703 disproved it. The editor is fully present on the migrated host, on
the same labs tRPC + aisandbox backend; only the view layer differs (React
+ Slate vs Angular + ProseMirror). Replaces both statements with what was
actually run: a complete character on ci-probe — name, personality, voice,
portrait and body, two distinct workflow ids, two distinct media ids, both
images on disk with different checksums, confirmed by read-back. Plus
`--model` determinism over four alternating runs and the orphan-rollback
A/B (1 -> 2 entities without the fix, 2 -> 2 with).

Records what was NOT verified rather than omitting it: the labs.google
path for all seven changed anchors, since every account here is migrated.

Also updates the #692 entry with evidence that changes what that issue is.
flow.google.com CAN mint a reCAPTCHA Enterprise token: on /project/<id>
the enterprise script and site key are present and a mint returns a
2404-char token, while the root grid carries no script at all. The mint
runs on the pooled bootstrap page, which after the handoff IS the root
grid — which is exactly why the reporter's bundle showed route: "/". So
"any mint failure on a migrated page yields exit 36" refuses the host
rather than fixing the cause.

The probe and its write-up are deliberately not linked from here while
they sit on the spike-skill branch: `check_doc_links.py` does not cover
this file, so a cross-branch reference would have shipped dangling with
every gate green.
ffroliva added a commit that referenced this pull request Sep 6, 2026
The ledger recorded the bug as the fix. Its exit-code table presented
`character create` on a moved account exiting 36 as a v0.70.0 improvement,
and its not-verified list said CHARACTER entities "cannot run on any
account available here". Both were false, from one root: a 20 s readiness
timeout was read as proof the feature does not exist on flow.google.com,
and #701 then added a guard that aborted before probing the DOM — which
made the claim unfalsifiable, because no run could look.

#703 disproved it. The editor is fully present on the migrated host, on
the same labs tRPC + aisandbox backend; only the view layer differs (React
+ Slate vs Angular + ProseMirror). Replaces both statements with what was
actually run: a complete character on ci-probe — name, personality, voice,
portrait and body, two distinct workflow ids, two distinct media ids, both
images on disk with different checksums, confirmed by read-back. Plus
`--model` determinism over four alternating runs and the orphan-rollback
A/B (1 -> 2 entities without the fix, 2 -> 2 with).

Records what was NOT verified rather than omitting it: the labs.google
path for all seven changed anchors, since every account here is migrated.

Also updates the #692 entry with evidence that changes what that issue is.
flow.google.com CAN mint a reCAPTCHA Enterprise token: on /project/<id>
the enterprise script and site key are present and a mint returns a
2404-char token, while the root grid carries no script at all. The mint
runs on the pooled bootstrap page, which after the handoff IS the root
grid — which is exactly why the reporter's bundle showed route: "/". So
"any mint failure on a migrated page yields exit 36" refuses the host
rather than fixing the cause.

The probe and its write-up are deliberately not linked from here while
they sit on the spike-skill branch: `check_doc_links.py` does not cover
this file, so a cross-branch reference would have shipped dangling with
every gate green.
ffroliva added a commit that referenced this pull request Sep 7, 2026
…ch half only

The project owner asked why a five-shot film created two Flow character entities
and then anchored every shot on a loose JPEG. The answer was that gflow refuses
entity references on this host -- a refusal I had made stricter that morning
(#716) and then never questioned, which is the guard-before-probe shape (#701)
arriving through the front door of a correct bug fix.

MEASURED FIRST, twice, before any code:

  1. `@` + the character's name + Enter commits
     <span class="mention-chip" data-reference-type="entity"
           data-entity-id="75cd2b39-...">Kael</span>
     ArrowDown instead of a query commits reference_type="media" -- a JPEG that
     merely shares the name. Flow lists characters and media under ONE search
     and does not rank them.
  2. A route-aborted submit (nothing generated) carried that entity id in the
     MZZa6b payload TWICE: as a mention node beside the prompt text, and in its
     own reference slot at the tail. Mode key `abra_r2v_8s`.

So the host takes character references and gflow does not drive them: a porting
gap, not a capability gap.

WHAT THIS COMMIT ADDS, and it is verified:

`MigratedComposer.attach_character_entities` drives that gesture through the
existing `_mention_by_name`, then READS THE CHIPS BACK and refuses unless every
chip it added is reference_type="entity" AND every requested entity_id is
present. `clear=False` lets an r2v run carry media references and characters
together without wiping the media mentions, judging only the chips it added.

The read-back is the point, not a nicety. Because Flow does not rank the two,
a driver that trusts the gesture will eventually bind an image where a character
was asked for and produce footage that looks right and drifts on the next cut.
The labs path already asserts this on the wire (`_assert_entities_attached`);
this is parity.

Four tests, each watched red first: entity chips land; a MEDIA chip where a
character was asked for is refused; the WRONG entity id is refused even though a
chip landed; and the gate still refuses.

Also: the Ingredients submode is now selected whenever an entity is attached,
whatever mode the caller named, because Flow treats a character reference as
reference-to-video -- the captured payload said `abra_r2v_8s`. Evidence-led, and
kept, though it did not fix the submit.

WHAT DOES NOT WORK, AND WHY THE GATE STAYS SHUT:

The submit after a successful attach produces no YhhmEf/eb1hJf/MZZa6b reply in
60s. Three runs: Frames+omni-flash, Ingredients+omni-flash, Ingredients+Flow's
own default model. Attach succeeded every time -- the trace even shows Flow
fetching the character's voice sample -- and the submit never replied. Cause
unknown.

I relaxed `_unported_form` while testing and PUT IT BACK. An open gate trades a
clear instant exit 36 for a 60-second timeout, which is worse for the user. A
test pins the refusal with that reasoning in its docstring so nobody reopens it
just because the attach half works.

All three runs were undiagnosable because the video path parks the page at
about:blank before the incident capture runs (#722), so every bundle reported an
empty DOM and a blank screenshot. #722 is a blocker for this issue, not an
annoyance: fixing the capture ordering turns "unknown" into "look at the page".

The attach code ships unused, deliberately -- proven, tested, and what the
eventual fix builds on.

Refs #723, #722, #716
ffroliva added a commit that referenced this pull request Sep 7, 2026
…ch half only

The project owner asked why a five-shot film created two Flow character entities
and then anchored every shot on a loose JPEG. The answer was that gflow refuses
entity references on this host -- a refusal I had made stricter that morning
(#716) and then never questioned, which is the guard-before-probe shape (#701)
arriving through the front door of a correct bug fix.

MEASURED FIRST, twice, before any code:

  1. `@` + the character's name + Enter commits
     <span class="mention-chip" data-reference-type="entity"
           data-entity-id="75cd2b39-...">Kael</span>
     ArrowDown instead of a query commits reference_type="media" -- a JPEG that
     merely shares the name. Flow lists characters and media under ONE search
     and does not rank them.
  2. A route-aborted submit (nothing generated) carried that entity id in the
     MZZa6b payload TWICE: as a mention node beside the prompt text, and in its
     own reference slot at the tail. Mode key `abra_r2v_8s`.

So the host takes character references and gflow does not drive them: a porting
gap, not a capability gap.

WHAT THIS COMMIT ADDS, and it is verified:

`MigratedComposer.attach_character_entities` drives that gesture through the
existing `_mention_by_name`, then READS THE CHIPS BACK and refuses unless every
chip it added is reference_type="entity" AND every requested entity_id is
present. `clear=False` lets an r2v run carry media references and characters
together without wiping the media mentions, judging only the chips it added.

The read-back is the point, not a nicety. Because Flow does not rank the two,
a driver that trusts the gesture will eventually bind an image where a character
was asked for and produce footage that looks right and drifts on the next cut.
The labs path already asserts this on the wire (`_assert_entities_attached`);
this is parity.

Four tests, each watched red first: entity chips land; a MEDIA chip where a
character was asked for is refused; the WRONG entity id is refused even though a
chip landed; and the gate still refuses.

Also: the Ingredients submode is now selected whenever an entity is attached,
whatever mode the caller named, because Flow treats a character reference as
reference-to-video -- the captured payload said `abra_r2v_8s`. Evidence-led, and
kept, though it did not fix the submit.

WHAT DOES NOT WORK, AND WHY THE GATE STAYS SHUT:

The submit after a successful attach produces no YhhmEf/eb1hJf/MZZa6b reply in
60s. Three runs: Frames+omni-flash, Ingredients+omni-flash, Ingredients+Flow's
own default model. Attach succeeded every time -- the trace even shows Flow
fetching the character's voice sample -- and the submit never replied. Cause
unknown.

I relaxed `_unported_form` while testing and PUT IT BACK. An open gate trades a
clear instant exit 36 for a 60-second timeout, which is worse for the user. A
test pins the refusal with that reasoning in its docstring so nobody reopens it
just because the attach half works.

All three runs were undiagnosable because the video path parks the page at
about:blank before the incident capture runs (#722), so every bundle reported an
empty DOM and a blank screenshot. #722 is a blocker for this issue, not an
annoyance: fixing the capture ordering turns "unknown" into "look at the page".

The attach code ships unused, deliberately -- proven, tested, and what the
eventual fix builds on.

Refs #723, #722, #716
ffroliva added a commit that referenced this pull request Sep 7, 2026
Nine-dimension council on 7c1d4c6 returned 4 GREEN / 5 YELLOW. Every finding
below was raised by a reviewer, not by the author.

D1/D14 - the retained 2026-07-27 comment still said, present tense, that the EN
  `span:text-is('Format')` fallback "has to be" the structural anchor, describing
  an entry the same commit deletes. The block also told the spike doc's story a
  second time, in the copy that will drift. Cut to the two DOM captures (the
  evidence) plus four lines, and a link to the finding.

D4/D6 - `test_clicks_ligature_selector_first` kept its name and docstring while
  only its assertion moved to the custom element; the e2e's failure message still
  told a future drift victim the cascade is "anchored on the personal_recommendations
  ligature". A label that lies outlives the bug it describes. Renamed, reworded,
  and the e2e message now names the probe to run. Assertions themselves untouched -
  only the string a failure prints.

D4 - the fall-through test drove only `PROMPT_FORMAT_SELECTORS[-1]`, so the new
  `<mat-icon>` entry at index 1 was never exercised. Parametrized over the whole
  cascade.

D6 - `is_visible(timeout=1000)` is inert: Playwright documents the argument as
  ignored, and the call answers from the current DOM without waiting. Dropped, with
  the reason, so nobody re-derives "a 4-entry cascade costs 4s before the submit".

D9 - the CHANGELOG called the capture "DOM read only" while the method it cites
  does a free `createEntity`, which is a write. Two files, one method, disagreeing.

D5 - `flow-locale-leak-icon-ligatures.md` still prescribed the `<i>`-only pattern
  that caused this: #703 fixed three constants for the carrier split and never
  updated the memory that teaches it, so the fourth was written the old way on
  purpose. Corrected, and the split promoted to its own entry.

D13/D2 - the spike put its guard in FRONT of its probe: a control miss raised
  before the dump, the screenshots and the report, deleting the evidence that
  would correct it. That is the #701 failure this file cites in its own docstring.
  Both controls now raise from a `finally` that has already written everything.
  A second control was missing entirely - nothing checked that the typed text
  landed, so a click intercepted by a consent overlay would have recorded a false
  absence indistinguishable from a real measurement. The scratch entity is deleted
  again, and the output path is root-anchored: `.gitignore`'s entry is slash-anchored,
  so running the spike from `scripts/dev/` wrote an UNIGNORED capture carrying the
  profile name, live URL, entity ids and authenticated outer_html.

D14 - the occlusion probe measured the prompt box, not the button under
  investigation, and the spike doc filed that under "confirmed, not assumed".
  Deleted; the live e2e click landing is stronger evidence anyway.

Re-verified live on denon82 after the rewrite ($0, 14s, clean exit):
  control_check    count=1
  typed_control    chars=47
  flow-format-prompt-button button                          -> 1
  button:has(mat-icon:text-is('personal_recommendations'))  -> 1
  button:has(i.google-symbols:text-is(...))                 -> 0
  button:has(i:text-is(...))                                -> 0

Declined, with reason: dropping `button:has(i.google-symbols:...)` as redundant
with the broader `button:has(i:...)`. The superset argument is correct, but those
are the labs anchors and no unmoved account exists here to re-verify against -
narrowing an untested surface to save one line is the wrong trade.

Refs #727, #730, #731
ffroliva added a commit that referenced this pull request Sep 7, 2026
/code-review at high effort, run on the full branch - the first review to see
anything past 7c1d4c6. Seven findings; these are the four that are claims rather
than behaviour, and a claim is what outlives the code.

CHANGELOG (finding 3) sat under "### Fixed" describing the bug as "the image
quota spent on a run whose requested prompt-engineering step never happened",
which implies that step now happens. It does not. The entry is scoped to what was
measured - the anchor - and a new "### Known issues" section states plainly that
--format-prompt still submits the prompt as typed.

--help (finding 2) promised Flow would rewrite the prompt and named "button not
found" as the only failure. That is the one case it gets wrong: the prompt is
submitted as typed even when the button IS found and clicked. Now names the real
limitation and keeps the best-effort promise it can actually honour.

The framing is deliberate and comes from a peer session reviewing the same class
of defect. The bug is NOT that the wait is too short - that reads as tuning. It is
that gflow reports success without observing it: prompt_formatted is emitted on
the click, so the absence of a completion inside a window we chose is recorded as
a completion. Same class as a 20s selector timeout read as "the feature is absent"
(#701) and a bundle photographed after teardown read as "the DOM was gone" (#722).
This one is the worst of the three because it fails toward SUCCESS - a premature
green looks exactly like the feature working, and nobody investigates a pass.

test_cascade_carries_no_display_label_anchor (finding 6) did not enforce the rule
its own docstring stated. It banned the byte sequence 'Format' in single quotes,
so it passed for "Format", for [aria-label*="Format"], and for 'Formatar' - the
exact string the spike observed on the pt account. Banning spellings loses to
translation by construction. Replaced with a whitelist: every text argument in the
cascade must BE a known ligature. Proven non-vacuous - accepts all four shipped
selectors, rejects all four bad spellings including 'Formatar'.

The is_visible comment (finding 7) claimed "a miss is one round trip". True for a
miss, false for a click timeout: entries 0 and 1 resolve to the SAME element on
migrated, 2 and 3 to the same element on labs, so a timeout is retried on an
identical element and the 5s budget is paid twice. Comment now states the real
worst case. Not de-duplicating the resolved elements here - that belongs with the
observed-rewrite rewrite, not in front of it.

NOT fixed here, recorded instead:
  - finding 1/5, the ~5.4s wait itself. /gflow:predict returned GO with conditions
    (8.2/10); it is a behaviour change on a paid path and gets its own PR.
  - finding 4, format_character_prompt resolves .first page-globally while
    _submit_body_prompt deliberately targets the LAST mounted box. On a two-box
    cohort the format click lands on the PORTRAIT composer. Real, and I have no
    two-box cohort to verify a fix against - naming it rather than blind-fixing it.

Verified: ruff + format clean, pyright 0 errors, 295 passed, doc links, hygiene
(1022 files), website mirror, 49 memory files, PII scan, and the corrected --help
renders.
ffroliva added a commit that referenced this pull request Sep 7, 2026
Fixes the second half of #727, and the half that actually made the flag useless.
The anchor fix made the button findable; this makes the click mean something.

Flow rewrites SERVER-SIDE. Measured 2026-09-07 on denon82: the click fires a
batchexecute round trip and the reshaped text reaches the composer at ~5.4s. The
old code waited _jitter_ms(500), logged ui_automation.prompt_formatted and
returned True; _send_prompt submits on the very next line. So --format-prompt
shipped the prompt the user typed and threw the rewrite away, on every run, with
a success event in the log and exit 0.

THE DEFECT WAS NOT THE DURATION. It was reporting a success we had not observed:
the absence of a completion inside a window we chose, recorded as a completion.
Same class as a 20s selector timeout read as "the feature is absent" (#701) and a
bundle captured after teardown read as "the DOM was gone" (#722) - a measurement
that could not be taken, recorded as a measurement. This one is the worst of the
three because it fails toward SUCCESS: a premature green looks exactly like the
feature working, so nobody investigates it. Framing owed to the peer session that
named the class while reviewing the same shape in its own judge.

format_character_prompt now polls the bound composer until its text actually
changes, and True means observed-rewrite, not button-pressed.

Design per /gflow:predict (GO with conditions, 8.2/10, five personas):
  - Gate on the DOM, NOT on the wire. eAenfb's response arrives 4.2s BEFORE the
    text settles, so awaiting it reproduces the same early submit - and an
    undocumented batchexecute rpcid is not an anchor worth depending on. Comparing
    against the string we inserted never reads a display label, so it is
    locale-invariant by construction.
  - Length delta, not `!=`: Slate/ProseMirror normalise whitespace and bare
    inequality would fire on churn, re-reporting the false success with better
    telemetry. Normalisation moves a handful of chars; the observed rewrite moved
    632 (19 -> 651). Threshold 16.
  - Baseline is a PARAMETER, not instance state - holding it on self would couple
    it across generations on a reused page. Both callers already had the string.
  - Existing settle-gate idiom (monotonic deadline + jittered poll), not a new
    helper; module constants, no env var nobody would set.
  - Still best-effort, still `-> bool`: promoting a miss to an exception would land
    in EXIT_CODE_MAP, the MCP schema and the docs at once, and the body-prompt call
    site runs AFTER the face image already spent quota, so failing there aborts a
    saga mid-spend.
  - 30s budget, generous on purpose. Costs are asymmetric: too long is bounded
    latency on an opt-in flag whose expiry falls back to exactly the old behaviour;
    too short silently reintroduces the bug. elapsed_ms is logged on every success
    so a real distribution accrues instead of being fitted to n=1.
  - Lengths and a stable hash only, never the text. Flow ELABORATES a terse
    description into a detailed physical one, so the rewrite is more PII-dense than
    the input, and structlog is not governed by GFLOW_CLI_HISTORY_PROMPTS.
    Pinned by test_never_logs_the_prompt_text.

Also fixes /code-review finding 4, which no other reviewer caught: the button was
resolved page-globally with .first while _locate_body_prompt_box documents "the
LAST mounted box is the target - never the portrait's". On a two-box cohort the
format click landed on the PORTRAIT composer, reshaping the wrong prompt or
finding a disabled button and giving up. _locate_format_button now mirrors that
existing convention instead of inventing a second one. And a disabled match
`continue`s rather than `return False` - one stale anchor used to kill every
selector behind it, including the one that would have worked.

Events: prompt_formatted now means the observed rewrite (it always claimed to);
format_button_clicked carries the old semantics; format_not_observed reports a
timeout without claiming Flow failed - an unchanged box also covers Flow
declining, erroring, or judging the prompt already formatted, none of which were
provoked, so none are claimed.

Five tests written first and watched failing:
  TypeError: format_character_prompt() got an unexpected keyword argument 'prompt_box'
Then 912 passed in tests/api/transports/, pyright 0 errors, ruff clean.
An autouse fixture shrinks the 30s budget for the offline suite - wait_for_timeout
is mocked, so the loop would otherwise spin on wall-clock for a full minute across
two never-lands cases.

Still owed and not claimed: a live run proving the gate on real Flow.
tranzmatt pushed a commit to tranzmatt/gflow-cli-unofficial that referenced this pull request Sep 9, 2026
gflow-cli drives a product it does not own, so every claim about what Flow
does is either measured or guessed — and a guess written into code or docs
becomes a fact nobody re-checks. The harness for measuring existed but was
not routed to, so it was not used.

Adds `skills/spike/SKILL.md` (canonical, agent-agnostic) plus its
`/gflow:spike` wrapper, and wires it in where it actually bites:

  * AGENTS.md Skill Routing — first row, because it gates the others
  * the Standard Workflow Sequence gains Phase 0 (Recon) before Triage
  * `issue-assessment` gets a "Before you classify an absence" section: a
    verdict of INVALID / "not supported on this host" is a claim about the
    live product, and that skill is read-only, so it cannot produce one from
    the code alone

The rule it enforces:

    A selector that does not match is evidence about the SELECTOR.
    It is never evidence about the FEATURE.
    Never put a guard in front of a probe.

Both halves are written from the same failure. "character create is a
labs-only surface, it renders no prompt textbox EVER" came from one 20 s
selector timeout and reached four places — a code comment, the CHANGELOG,
the v0.70.0 release ledger, and a test class NAME. ffroliva#701 then added a guard
that aborted before probing the DOM, which made the claim unfalsifiable:
no run could look. The feature had worked the whole time.

Also records the recon itself, including a result that is new and not yet
acted on: flow.google.com CAN mint a reCAPTCHA Enterprise token. On
/project/<id> the enterprise script and site key are present and a mint
returns a 2404-char token; on the root grid there is no script at all. The
mint guard refuses the host when the pooled page is simply on the wrong
route — the mechanism behind ffroliva#692, whose reporter's bundle showed
route: "/". Deliberately NOT claimed: that fixing the mint unblocks
`gflow image`. That path is UI-driven and the migrated project composer
inventories as video-only, so the mint is necessary, not sufficient.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant