Skip to content

Integration: reason copy, resumable runs and Mobile capture - #627

Draft
tnunamak wants to merge 57 commits into
mainfrom
tim/deferred-integration
Draft

tnunamak wants to merge 57 commits into
mainfrom
tim/deferred-integration

Conversation

@tnunamak

@tnunamak tnunamak commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Draft. Integration branch for connector work that is ready but held for one review. Each PR below merges here when its own checks and review pass, and this PR merges the set to main at once. Do not merge yet.

PR What it changes State
#610 Owner-facing reason copy is written once in reason-copy.json and stamped into each manifest Merged here
#626 Shopify, Oura, Uber, X and Discord continue from where the last run stopped, instead of repeating the same pages Merged here
#617 Mobile capture helper, page_init_script, Instagram posts, and other Mobile ports Not merged yet

Merging #626 after #610 moved its page-budget codes from limit_repeats to continues_later in reason-copy.json, because those runs now resume. Uber trip_details_unavailable maps to skipped_items. Where #610 and #626 bumped the same connector to the same version, each now sits one past the higher number.

Merging main brought in Spotify 0.1.52 to 0.1.56, so Spotify is now 0.1.57 here. Main's two new skipped-entry codes keep their own wording as custom copy. spotify_stream_outcome_unproven and spotify_playlist_revision_unproven map to incomplete_retry.

To do

  • Revisit per-run budgets: they may be too low now that runs resume.
  • Oura reports the same code when a single day needs more than 100 pages. That case repeats on every run, so "later runs collect the rest" is wrong for it. It needs its own code.

Assisted-by: AI

Rewrite eight reason_display_messages strings that used internal terms
("coverage gap", "unread tail remains unproven", "inventory") or gave no
detail. Each new string says what happened, what was kept, and whether
the next run retries, checked against the code that emits the code.
icloud-notes promises no retry, because an unrecognized response waits
for a connector upgrade.

Patch-bump each connector and regenerate the implementation index and
library metadata.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Connectors throw terminal codes (createConnectorFailure, code-shaped bare
Errors, and the shared Amazon and browser sign-in helpers) that the
completeness test never saw, so the Desktop app kept its own copy for
them. Declare plain copy for the reachable codes it shows, and extend the
scan so a thrown code without copy fails CI; the remaining undeclared codes
are listed in a shrink-only allowlist for a later review.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
About 330 codes that connectors throw had no owner-facing copy, and the
copy that existed repeated the same few sentences in many spellings. An
audit of all 693 (connector, code) pairs found 11 owner situations: sign
in again, run again later, internal problem, items skipped, export file
problem, continues next run, settings problem, not offered, nothing to
import, site busy, and capped by a limit.

packages/polyfill-connectors/src/reason-copy.json now holds those 11
sentences once, and maps each connector's codes to one of them. 22 codes
keep a connector-specific sentence because the owner's next step depends
on that site. scripts/generate-reason-display-messages.mjs writes the
resolved text into each manifest's reason_display_messages, replacing
{site} with the manifest display_name. Manifests keep their format, so
Desktop and the reference implementation read plain sentences as before
and need no change. Connectors keep emitting their own codes.

reason-copy:check runs in npm test and fails when a manifest drifts from
the copy file. The shrink-only allowlist of thrown codes without copy is
gone: every thrown code now needs copy or must be runtime-generic.
Four connector tests that compared the copy set to the SKIP_RESULT
vocabulary now include the connector's thrown codes.

Every connector whose copy changed gets a patch bump.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…mbers

A review of the spotify, chatgpt, github, amazon and gmail mappings found
three that misled the owner:

- amazon older_pages_deferred_page_budget said later runs collect the
  rest, but each run restarts the year at page one and keeps no resume
  offset, so an unchanged large year repeats the same pages. heb's copy of
  the same code already said the tail stays unproven. Both now get their
  own sentence.
- gmail_progress_invalid_instant and _invalid_time_range come from the
  requested time range, so the owner can fix them: fix_settings, not
  unexpected_data.
- spotify_recent_context_boundary_unverified is emitted even after a good
  read and retrying cannot help, so it no longer says to run again.

The generator now refuses a manifest with two reason_display_messages
members. JSON.parse keeps the last one but the splice edited the first, so
--check could pass while the effective copy was stale.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…t deliver

A review of every connector's code-to-sentence mapping found two
patterns where the shared sentence told the owner something false.

Ten page- and run-budget codes (shopify, google-calendar, google-contacts,
oura, doordash, uber, tiktok, x-browser) mapped to continues_later, which
says later runs collect the rest. These connectors save no continuation
cursor when the budget stops them, so the next run rereads the same
range. They now map to a new limit_repeats sentence that says older data
may stay missing.

Generic codes such as stream_collection_failed also fire for failures
marked retry_on_connector_upgrade, so "Run again later" alone was wrong
for them. incomplete_retry now adds that a connector update may be needed
if the problem keeps happening.

Single-cause codes move to a closer sentence: browser sign-in handoffs
and interruptions to a new sign_in_unfinished, claude-code local reads
to a new local_file_unreadable, anthropic export parse failures to
unexpected_data, and about a dozen codes get their own sentence where
the owner's next step is specific (a Steam privacy setting, gmcli limit
settings, a Muse bot check, an ical subscription address).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
jellyfin_error covered rejected sign-ins, bad settings, replies the
connector cannot read and transient faults, and jellyfin_session_failed
mapped every first-sign-in failure to 'sign in again'. Classify by cause:
jellyfin_auth_failed for rejected credentials, the existing base-url codes
and jellyfin_settings_rejected for settings, jellyfin_response_unreadable
for replies that need a connector update, jellyfin_error for transport
faults. First-sign-in failures stay non-retryable: the stored password
was already sent, and a redispatch would resend it.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
ics_fetch_failed covered HTTP 401/403/404 and invalid URLs as well as
network faults. Rejected access now reports auth_failure, an address the
server refuses or cannot use reports ics_subscription_address_invalid,
429 reports rate_limited, and only transient faults keep ics_fetch_failed.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ead failures

The per-chat catch in fetchAndParseGmcliMessages reported every error as
gmcli_query_failed, so a helper that lost its pairing or went missing
mid-run told the owner to run again. Reuse classifyGmcliFetchError there.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
HTTP 401/403 now reports auth_failed, unsafe redirects report
unsafe_redirect_refused, and a missing Location, oversized body or
redirect loop reports carddav_response_unusable. Only HTTP-status
transient failures keep carddav_request_failed.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…tp errors

http_error carried both retryable statuses and non-retryable 4xx. A 4xx
that is not 401/403/404 now reports anthropic_live_request_rejected,
which only a connector update fixes. 401 already throws the sign-in error
and 403/404 already report not_available or a null plan, so credentials
never reached http_error.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
A non-auth 4xx (for example 404) was classified unrecognized_response,
which is upgrade-only, but reported icloud_http_error, whose copy tells
the owner to run again. Give it icloud_request_rejected so the reason
matches the recovery.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
muse_stream_incomplete carried retryable failures and the page-cap and
unstorable-text failures that only a connector update fixes. The
non-retryable run failure now reports muse_stream_needs_update. Runs under
the runtime still reach the owner through stream_collection_failed.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…e reporter

The no-reporter path threw instinct_stream_incomplete for every failure,
hiding classified codes and the upgrade-only invalid-shape and
duplicate-key failures. Reuse the error's own code, report
instinct_source_unreadable for upgrade-only failures, and keep
instinct_stream_incomplete for what a retry can clear.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…an update

failStream threw whatsapp_web_stream_incomplete for text that cannot be
stored, which no retry fixes. The non-retryable failure now reports
whatsapp_web_stream_needs_update.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…load

failStream threw character_ai_stream_incomplete for unkeyed chat-list rows,
which no retry fixes. The non-retryable failure now reports the existing
character_ai_invalid_payload code.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
failStream threw perplexity_stream_incomplete both for a library that
changed while it was read (a retry clears it) and for list rows the parser
cannot place (only a connector update helps). The non-retryable failure now
reports perplexity_stream_needs_update.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…dance_missing

credit_card_export_unverified also covered source_structure_changed, where a
retry is pointless until the selectors are repaired. A structural outcome
now reports export_affordance_missing for credit cards too.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
runOptionalStream reported optional_stream_failed, copy that says a run
again may help, for a SlackApiAuthError that needs refreshed credentials.
Report the existing slack_auth_failed code for that case.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ibrary metadata

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…n scanner

Map each new branch to a plain literal so the reason-code completeness
scanner can resolve it, and drop the instinct classified-code passthrough
that needed a computed code.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
A run stops at 200 Activities pages or 200 trip-detail fetches. Before
this change it saved no position, so every run started again at the
newest trip, re-hydrated the same first 200 trips, and never reached
older history on a large account.

The trips cursor now records the newest trip already read, the
Activities page tokens where the walk stopped, the trip IDs still
waiting for detail, and the IDs already hydrated. The next run reads
new trips down to the saved head, then continues the saved pages
within the same page budget. The position is tagged with the run's
since, until and resources; a different scope or full_refresh starts
over. When the history ends and no detail is pending, the backfill is
cleared and the stream completes. While it is open, the budget skips
now say a later run continues (retry_by_runtime, retryable).

A rejected or repeated page token fails the stream instead of
restarting from the newest trip. Activities ordering and token
lifetime across sessions are not verified against a live account.
The hydrated-ID lists grow with trip count.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…un stopped

A run stops at about 1,000 messages, 25 servers or 100 requests. A
server whose walk was cut short kept no position, so the next run
searched it again from the newest message. A server with more history
than one run's budget never got past it, and every run re-read the
same head.

Each server's cursor now keeps a scope tag and a backfill: a list of
ID ranges still to read, each with the oldest message ID consumed
(before_id) and, for a head segment, the old newest ID to stop at
(stop_id). The next run reads new messages down to the saved head,
then continues the ranges with Discord search's max_id parameter. The
page script's search allow-list now accepts a numeric max_id. A head
larger than the budget becomes its own range, so neither position is
lost. A changed since, until or resources, or full_refresh, drops the
saved ranges. When every range is finished, backfill is null and the
server is complete.

The search offset ceiling (about 10,000 results) used to count as the
end of a server. The walk now restarts at offset zero with max_id set
to the oldest message read and continues to the real end. If Discord
returns messages at or above the max_id it was given, the walk stops
as source_unreadable instead of working around it.

The run-limit and interrupted messages in the manifest no longer say
the next run starts again from the newest message.

Not verified against live Discord: that search honours max_id.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
A run stops after 90 seconds or its scroll ceiling. Before this change
it saved only record fingerprints, so the next run started again on
the newest orders and an account with more orders than one run could
read never got its older history.

When orders come from the order-history API, the cursor now keeps the
API's "after" token where the walk stopped and the newest order
already read, tagged with the run's since, until and resources. The
next run reads new orders down to that head, then continues from the
saved token. A head that does not finish within the run is kept as
its own pending segment ahead of the older one. If the API refuses a
saved token, the run drops it and starts from the newest order. A
different scope or full_refresh starts over. At the end of the list
the position is cleared and the stream completes.

Orders read by scrolling the page (the Apollo cache or DOM path)
cannot seek to a saved position, so that path keeps its earlier
behaviour and its earlier recovery hint.

Not verified against a live shop: how long an "after" token stays
valid across sessions.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… full one

A run stops at 100 pages. Before this change a capped walk kept its
starting day as the cursor, so the next run read the same pages again
and never got past them. The recovery hint said only a connector
upgrade could help.

Oura's pages have no documented order, so a day seen mid-walk is not a
safe place to resume. The walk now reads fixed 90-day windows
(start_date to end_date). A window counts only when all its pages have
been read; the saved frontier is then that window's end. If the page
budget stops a run inside a window, the frontier stays at the window's
start and the next run reads the whole window again. Records keep
their keys, so the overlap changes nothing. Sleep and its daily_sleep
helper must both finish a window before sleep records are emitted.

The frontier is tagged with the run's since, until and resources. A
different scope or full_refresh starts from the scope's since. With no
saved day and no since, the walk starts at 2015-01-01. The last window
ends tomorrow (UTC); when it is read, the frontier is cleared and
last_day records the newest day, as before. State from earlier versions ({ last_day }) loads as
the start. While the walk is unfinished, the budget skip now says a
later run continues (retry_by_runtime, retryable).

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… lines

The cause splits shifted 114 allowed await-in-loop sites in 14 connectors, so the conformance check listed them as stale and new. Each row now points at its new line. Three new test loops get their own rows: anthropic-live rejected statuses, google-messages gmcli failure kinds and muse upgrade-only failures.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…Ds once backfill completes

A trip whose GetTrip or GetReceipt call fails every time stayed in the
pending list forever, and each run reported trips_deferred_detail_budget
even when the detail budget was not the reason it stopped. The cursor now
counts failed runs per trip in detail_failure_counts. After three failed
runs the trip leaves the pending list without being marked hydrated, and
the budget skip is reported only when there were more candidates than
MAX_DETAIL_FETCHES.

The hydrated trip and receipt ID arrays grew by about 78 bytes per trip
for the life of the connection. They are only needed while a backfill is
open, to avoid refetching details across overlapping pages, so they are
now cleared when the backfill completes.

New tests cover each permanent-failure case, the cleared arrays, a capped
head burst over an existing backfill, and reordered resources keeping the
saved page position.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…get on excluded messages

When a run had an until bound, the head query still started at the
newest message. It spent the request budget on messages the scope
excluded, so a large server never reached the requested range, and each
run added another saved range. The head and refresh queries now start
at the snowflake for the until bound (max_id), and saved ranges with
identical bounds are merged.

Three smaller fixes come with it:

- Resources are sorted and deduplicated before the scope is saved, so
  the same request in a different order keeps the saved backfill.
- A search hit that can never be parsed no longer pins a capped walk.
  The walk checkpoints past it and reports discord_records_unreadable,
  as the base connector did on a full walk.
- A widened scope keeps separate head and older intervals around the
  coverage it already has, so a run cut off in the new head still
  reaches the older floor.

New tests cover the until bound over three runs, duplicate ranges,
resource order, a permanently malformed hit, and adjacent snowflakes
that collapse to the same millisecond.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Each view (posts, replies, likes, bookmarks) stops after about 100 new
posts per run. Before this change the next run only added posts newer
than the last run's head, so anything older than the first run's cap
was never read.

STATE now keeps, per view, the newest post seen (the head) and the
oldest post reached (the backfill position), tied to the requested
scope. A later run first reads new posts down to the saved head, then
scrolls to the saved position without counting those posts against the
run's cap, and continues into older history from there. A head burst
larger than one run keeps its own position, so it does not cut off the
older backfill. Changed resources, a changed since or until, and
full_refresh start again from the current head. Legacy STATE without
backfill positions still loads.

A post that appears in two views (an original that X also lists among
replies) is still charged once per view, as before, so a first run
reads the same number of posts as the base connector.

The posts, likes and bookmarks descriptions now say that later runs
continue into older history instead of saying it is never read.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…m a rejected saved token

Before resume, every run read all order-history pages (up to the budget)
and the fingerprint gate emitted orders whose content had changed, such
as fulfilled to delivered. With resume, a run stopped at the newest
order it already had, so a later change to an older order was never
seen. The backfill position now continues as a rolling sweep: after
reading new orders, a run spends its remaining pages walking older
pages from the saved position. When the sweep reaches the last page,
fingerprints of orders no longer listed are pruned, and the next sweep
starts again from the newest page. When the whole history fits in one
run, every page is read each run, as before.

If Shopify rejected a saved page token on the last request the run was
allowed, no checkpoint was written, so every later run sent the same
token and stopped. The run now saves a reset position even when it has
no request left. The run deadline is also passed into the reader, so a
rejection that arrives late does not start a new request after the
deadline.

The saved scope is normalized before comparison: missing fields equal
null, resources are sorted and deduplicated, and key order does not
matter.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ame-day ranges

A 90-day window that needed more pages than one run allows never
finished: the run read up to the page limit, emitted nothing for that
window, saved the same frontier, and the next run did the same. The
frontier now stores window_days. A window that stops before it finishes
is halved on the next run, down to one day, and the size returns to 90
days after a window completes. If a single day still needs more pages
than one run allows, the frontier stays and the skip no longer claims
retry_by_runtime, because running again cannot help.

A time range inside one day (since and until on the same date) made no
request and reported the stream complete. Such a range now requests
that day.

The saved scope is normalized before comparison: missing fields equal
null, resources are sorted and deduplicated, and key order does not
matter.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…tail is missing

The previous fix retired a trip after three failed detail fetches, but it
counted every failure. Three runs of rate limits or network errors
retired a trip for good: the next head pass stops at that trip, so it was
never fetched again. Fetch errors are now typed. Only a 4xx other than
408 and 429, or a response the parser rejects, counts toward
retirement. A 5xx or network error keeps the trip pending without a
count. A 429 stops detail fetching at once, saves the remaining IDs and
coverage, and then rethrows uber_rate_limited, so the runtime sees the
rate limit.

A trip whose detail failed left nothing in the output to say so: with
receipts requested, a failed GetReceipt left the stream looking
complete. Each requested stream with attempted trips still missing
detail now gets a trip_details_unavailable SKIP_RESULT. Its hint is
retry_by_runtime while any of those trips are still pending, and
retry_on_connector_upgrade once they are all retired.

New tests cover 429, 408, 503 and network failures on both detail
calls, the rate-limit stop, the detail gap with receipts only and with
both streams, a retired receipt, and the Activities page ceiling shared
by head and continuation pages.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…lk past a broken message

A changed scope reset the saved ranges but kept newest_id from the old
scope. A walk with a narrower until could then save a newest_id above
its own until. When a later run widened until again, it treated that
head as already collected and skipped the messages between the two
bounds. A scope change now drops the old head, a newest_id above the
scope's until is not saved, and old coverage heads are used again only
after they are read inside the bounds.

A walk that reached the end of the list after a permanently malformed
message returned no cursor, so every run replayed the same range and
asked the runtime to retry. Such a walk now saves a completed cursor
for the readable messages and reports only discord_records_unreadable,
which asks for a connector upgrade, not a runtime retry.

Identical or overlapping saved ranges are now merged when the cursor is
loaded, before any range is walked. Before, they were removed only when
the cursor was saved, after one of them had already moved, so the other
re-emitted its messages.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…roll limit

A resumed run scrolls back to the saved backfill position (the anchor)
and then reads older posts. When the anchor was found on the last scroll
the run allowed, nothing older was read, the cursor did not move, and
every later run did the same while asking the runtime to retry. A view
now gets one more scroll after it finds the anchor, within the run's
total scroll limit. A seek that cannot reach the anchor reports a
non-retry outcome.

Finishing a large pending head on likes or bookmarks saved the wrong
newest IDs: the older pending IDs were written before the newer ones and
the list was cut to 50, so the next run treated the newest 100 posts as
new and emitted them again. The pending IDs now go after the newer
arrivals.

The posts, likes and bookmarks descriptions and the refresh rationale
now say that the posts stream reads two lists of about 100 new posts
each, that later runs reach older posts only 60 scroll steps deep, and
that scrolling back past saved posts counts toward X's daily reading
allowance.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The scope-change test changed resources from null to [] but never
between two lists of the same length. A comparator that checked only
the list length would keep a saved frontier from resources ["a"] when
the run asks for ["b"], and no test caught that. The new case saves
["a"] and runs with ["b"]; it fails against a length-only comparator.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The scope-check test added five lines above five allowlisted loops.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
A trip or receipt whose detail failed three runs was dropped from
pending, and its failure count lived inside `backfill`, which the next
complete run removed. Run 4 then emitted no `trip_details_unavailable`
skip, so the stream looked complete.

STATE now keeps up to 100 retired IDs per stream in `retired_trip_ids`.
Every later run reports them as a non-retryable gap and does not fetch
them again. full_refresh or a scope change clears the list.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The saved head keeps the 50 newest post ids, sorted by id. An old pinned
post sorts below them and was dropped, so every later unchanged run
emitted the pinned post again.

The run now records the pinned post it read or skipped as known, and
the saved head keeps that id after the 50 newest.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Search pages advanced by `offset` under a fixed `max_id`. A message
deleted between two page requests shifted later results by one, so a
surviving message was skipped, and the end-of-results check then saved
the server as complete. Main has the same race.

After each page, the next request now uses the oldest readable ID seen
as an exclusive `max_id` at offset 0, plus the count of unreadable
groups after that ID, which were already reported. A page with no
readable hit has no ID to page by and still advances by offset, so the
9,975 offset ceiling check is unchanged for that case.

Tests whose fakes ignored `max_id` or matched `offset=25` now honor
`max_id` like the real endpoint and match on the second page instead.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…wser fixes

Rows are matched per file in sorted order with the same column. The uber retirement loop moved into a per-stream test, so its column changed from 3 to 4.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
These files were format-clean on main. Allowlist rows move with the formatted lines.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
UberHttpError used a parameter property, which erasableSyntaxOnly rejects. The x_browser tests read retryable from a hint that can be a string, and cast object resources straight to StreamScope.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Main published new versions of anthropic, apple_health, fitbit, netflix_export, spotify, strava, strava_browser and whatsapp. This branch had bumped the same connectors to the same numbers with different contents, so each now moves one past main.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@github-actions github-actions Bot added evidence: stale A connector's committed replay claim predates a code/manifest change in this PR evidence: none A touched connector has no committed replay claim yet labels Nov 5, 2026
Both connectors saved their resume scope in STATE as a JSON string, so
the cursor held escaped quotes. The PageShim harness checks that
discord_browser STATE holds only IDs and keys, and that check failed.
STATE now saves the scope as a plain {since, until, resources} object.
A saved scope in the older string form gives the same comparison key,
so STATE written by earlier builds of this branch still resumes.
A changed scope and full_refresh still discard the saved position.

The x_browser PageShim test expected STATE without backfill_scope and
backfill. Both fields are needed: without `complete: true` a later run
treats the cursor as one whose backfill never started and walks the
whole list again, and the scope is what detects a changed range. The
expectation now includes them. The two later subtests that re-emitted
the pinned post did so only because the first subtest failed before it
saved STATE, so they ran with none; they now fail with that cause.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
#610 and #626 both bumped discord_browser, oura, shopify, uber and
x_browser from main, to equal numbers for x_browser, discord_browser
and uber. Each now moves one past the higher of the two.

Owner-facing copy comes from reason-copy.json, so the hand-edited
sentences from #626 give way to it. With resume in place, the budget
stops for oura, shopify, uber, x_browser and discord_browser continue
on the next run, so they map to continues_later instead of
limit_repeats. Uber's new trip_details_unavailable maps to
skipped_items.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Main published spotify 0.1.53 and linkedin 0.3.43. Spotify here moves to 0.1.54; linkedin stays at 0.3.44, which is already past main.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ines

#628 moved LinkedIn's sequential loops and added two, without updating the allowlist, so the conformance check fails on main. Six rows move with their code; the section loop's first await is now the skills walk at 957:14. The two new loops are skills pagination, where each page's start offset depends on the prior page, and the skills-walk test cases, which share one fetch shim.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Main published spotify 0.1.54 with different content, so the merged
spotify moves to 0.1.55. LinkedIn allowlist rows follow main's #629.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Main released Spotify 0.1.52 to 0.1.56. Spotify moves to 0.1.57, because
this branch stamps its reason copy into the manifest. Main's two new
skipped-entry codes keep their specific wording as custom messages, and
spotify_stream_outcome_unproven maps to incomplete_retry. The index and
library metadata are regenerated.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
Main's Spotify 0.1.56 throws spotify_playlist_revision_unproven. The
connector catches it to drop the playlist streams, but the reason-copy
test requires copy for every thrown code. It maps to incomplete_retry:
Spotify did not confirm the playlist revision, so a later run can try
again.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

evidence: none A touched connector has no committed replay claim yet evidence: stale A connector's committed replay claim predates a code/manifest change in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant