Skip to content

feat(runtime): stop acting on connector claims about recovery and coverage - #636

Draft
tnunamak wants to merge 7 commits into
feat/collection-profile-declarationfrom
feat/checkpoint-and-repair-proof-rules
Draft

tnunamak wants to merge 7 commits into
feat/collection-profile-declarationfrom
feat/checkpoint-and-repair-proof-rules

Conversation

@tnunamak

@tnunamak tnunamak commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Base: #635. Merge that first, or retarget this PR to main after it lands.

The runtime no longer acts on a connector's own claims about recovery and coverage (Collection Profile 0.2.0 Sections 5.5 and 5.11). A connector's refresh_credentials hint, "401" text, session_required text or retryable flag no longer raises an owner prompt, marks owner repair or starts a retry. Hints go through a runtime gate and anything it cannot authorize is stored as unknown. Only hydrated_keys credit coverage, and a continuation-free skip always withholds its cursor. The tests that asserted the old behavior are inverted and each says which clause it now pins.
What the owner sees: a runtime-recorded cancelled sign-in prompt still flags the connection for owner repair after one attempt. A gap-only 401 reported by the connector, with no runtime evidence, now gives no prompt. The runtime's replacement proof, provider_invalidation_proof, has no production caller yet, so that case is covered by #647 and is not wired here.
Proved by collection-profile-owner-repair-flow.test.ts, which runs a real connector child through the real runtime and scheduler for both cases. The gap-only 401 test is named KNOWN GAP (#647) and passes by asserting the current no-prompt result, so it must be flipped when #647 lands. The gate, retry, repair and coverage changes have their own tests in collection-profile, scheduler-retry-classifier, connection-health and checkpoint-commit-decision.

Assisted-by: AI

A connector's recovery hint is a claim. Section 5.11 lets the runtime act
on only the hints it can hold: today `not_retriable` and `unknown`, and
only while the diagnostic record is not truncated. Every other hint is
shown as the connector's attributed suggestion and acted on by nothing.

The gate is `gateRecoveryHint` on the observation run. It returns
`authorized`, `blocked` (the record is truncated) or `unsupported`. An
unauthorized hint is recorded once per origin and action in a bounded
`connector_suggestions` list of closed values (no connector text), and a
declaring connector also gets one `hint_unsupported` defect count. The
observation store validates and persists the list.

This code first landed in the OBSERVATION PR with no caller. It moves here
with its caller, which is the next commit. The action vocabulary is the
single `RECOVERY_ACTIONS` set in connector-gap-bounding.ts; the second
copy that the gate carried is removed.

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

This change removes the code that read a connector's own text as proof.
It also removes the tests that asserted that behavior: a connector's
`refresh_credentials` hint or "401" message raising an owner credential
prompt, a connector's `retryable: true` starting an automatic retry, and
a connector's `session_required` text marking the connection for owner
repair. Collection Profile 0.2.0 Sections 5.5 and 5.11 say these are
claims. The runtime may act only on a record it holds itself.

The runtime now:
- gates every DONE.error and SKIP_RESULT recovery hint. Only an action
  the runtime can authorize is stored. Any other hint is stored as
  `unknown`, and the connector's message stays visible;
- never starts a retry from a connector terminal error, its `retryable`
  flag, its hint, or a `run_failed` gap derived from them. A failure
  with no connector error keeps the runtime's own verdict, and the
  managed scheduler path now forwards that verdict;
- no longer treats the text of a connector's gap reason, or of its
  message, as an owner-auth failure. A gap reason is not the run's
  reason code. A provider-invalidation proof still raises a browser
  session prompt on its own;
- credits `hydrated_keys` for coverage and nothing else. `empty_keys`,
  `unchanged_keys` and `optional_skip_keys` credit no key and release no
  checkpoint. A manifest that declares DETAIL_COVERAGE_KEY_SETS still
  needs an accepted record for each hydrated key;
- withholds the cursor of a continuation-free SKIP_RESULT unless the
  skip is informational, whatever its hint or retryable flag says;
- keeps STREAM_EVIDENCE diagnostic: `unaccounted` never gates a
  checkpoint. The cursor commits, and one stream-level known gap
  records the loss because the message names no keys.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
… and stop retrying connector-reported failures

A SKIP_RESULT without a continuation withheld its cursor only when the
gap's display severity was not informational. That severity is derived
from the connector's own reason, so a connector could say `out_of_scope`
for a stream the owner requested and release its cursor with no rows
collected. The runtime cannot name a scope that excludes a stream it has
already validated as in scope (a stream outside the default scope is
rejected as undeclared), so every continuation-free skip is now an
omission. The test that pinned the old release now asserts the withhold.

The scheduler retry classifier stopped a failed run only when a
connector error object was present. A failed DONE with no error object
(valid in 0.1.0) has terminal_reason connector_reported_failed and was
retried. The terminal reason now stops the retry, and both the managed
and unmanaged scheduler paths have a no-error scenario.

Remove the second copy of the gating omission sources in the commit
decision and the cast-based test that kept a removed source alive.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…key-set manifest too

A manifest that declared DETAIL_COVERAGE_KEY_SETS needed an accepted
detail record behind each hydrated key, although START omits the
extension and the connector sends only base `hydrated_keys`. Collection
Profile 0.2.0 ties detail coverage to the manifest's parent_streams and
to the portable hydrated_keys accounting, and says runtime-specific
members do not establish portable coverage. The accepted-record check was
a rule of this runtime only. It also stalled an older connector whose
parent is empty: the key is listed as hydrated, no detail record exists,
and the cursor stayed put on every run.

The declaration now changes no coverage decision. A hydrated key is
credited unless the RS rejected a record for it, as for every other
manifest. CoverageReport loses `evidencedKeys` and
`hydratedKeysNeedEvidence`, and the accepted-key store no longer looks up
accepted records for coverage.

Tests: the two tests that pinned the old rule in
detail-coverage-shortfall-severity and the SQLite and PostgreSQL parity
scenario are inverted (they failed before this change). Three tests that
used an unrecorded hydrated key only to force a withheld cursor now use an
unaccounted required key instead.

Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Assisted-by: AI
…ew file names from comments

Gate 3 review of this PR found that required_keys is taken from the
connector as declared, so a report with required_keys=[] beside accepted
parent records passes. The review proposed rejecting such a report.

Collection Profile 0.2.0 Section 5.6 makes required_keys the connector's
own complete key accounting for one detail stream and parent boundary, and
does not tie it to the parent keys a run accepted. Shipped connectors
(amazon `orders` and `order_items`) report `required_keys: []` beside
accepted parent records, because a parent record whose detail is already
held needs no detail this run. A runtime rejection would stall them, the
same kind of runtime-only rule removed in the previous commit. So the
gate keeps crediting against the declared set. A comment at the intake
cites the section, and a new test pins the behavior so a later change to
it is deliberate.

Comments that named internal review files or scratch paths now state the
fact instead. They were already on main: STREAM-EVIDENCE-*-REVIEW.md names
and tmp/ paths in runtime/index.ts, runtime/connector-gap-bounding.ts and
two stream-evidence test files. Other files carry the same kind of
reference and are not touched here.

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

The projection no longer takes a connector-authored known-gap reason as the
run reason code, and no longer derives a credential repair from connector
claim text. Two tests still asserted the old behavior and failed on this branch
only; they pass on the branch's parent and on main.

- connector summary connection health degrades succeeded runs with coverage
  gaps: the gap still degrades the connection (state and coverage axis are
  unchanged); reason_code is now null instead of the gap reason.
- ChatGPT-shaped browser_collector connection with no credential row: a
  connector-reported 401 gap now yields CredentialsValid unknown
  (credentials_not_probed) with no remediation and no reauth action, matching
  the already-updated connection-health-acceptance test for the same input.

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

A stub connector child runs through the real runtime, and its real result
goes through the scheduler's run path. Two cases are asserted by what the
owner is told:

- the owner cancels a sign-in prompt: the connection is flagged for owner
  repair after one attempt, with no retry;
- the connector reports a 401 with no runtime evidence: no owner repair.

The second test is named KNOWN GAP (#647). provider_invalidation_proof, the
runtime's replacement proof for that case, has no production caller yet, so
the test passes by pinning the current no-prompt result and must be flipped
when #647 lands.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak
tnunamak force-pushed the feat/collection-profile-declaration branch from e6affb6 to a4fb70d Compare November 6, 2026 17:19
@tnunamak
tnunamak force-pushed the feat/checkpoint-and-repair-proof-rules branch from d9f7072 to 3bc67b6 Compare November 6, 2026 17:19
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