Skip to content

fix(reference): decide browser setup availability from the selected artifact - #627

Open
tnunamak wants to merge 17 commits into
mainfrom
fix/connector-readiness-projection
Open

tnunamak wants to merge 17 commits into
mainfrom
fix/connector-readiness-projection

Conversation

@tnunamak

@tnunamak tnunamak commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #617: the base is its branch, so review only the commits after it.

Whether the owner could add a browser connector was decided by an optional bundled list of connectors. Without that list, published and installable connectors such as LinkedIn and Goodreads read "Not built yet", and the console's copy of the rule could disagree with the server's.

The server now projects one verdict per connector from the selected package (developer-local first, else installed and re-verified), the host's attended-enrollment requirements and the catalog, which is used for discovery only. The action is connect, install, repair, configure, retry or unavailable, with a reason. A package that declares capabilities.collection.implemented: false is unavailable, not connect.

GET /v1/owner/connector-install/readiness and the template field connector_readiness carry the verdict. The console only reads it. POST /_ref/connectors/:id/browser-enrollment-shell checks it again at request time and answers 409 unless the action is connect. The roster, is_known_scaffold and the console's duplicate rule are deleted.

For the owner, a published connector offers Install, then account setup once it is installed and the host is ready; a blocked connector says why in one fixed sentence. The server's detail text is not shown, because it can name a store error or a host path.

Open dependency: a placeholder package must publish implemented: false. Until it does, a not-yet-installed placeholder still shows Install.

Proof: each test file ran alone on this head (server readiness, routes, shell route, templates, setup plan; console verdict contract, catalog, presentation, picker, install client). tsc --noEmit exits 0 in both packages. The desktop build's staging probe failed on an earlier head with e[a] is not a function, and passes now after a local console build and stage.

Not verified: PostgreSQL and the full suite. The mutation job rejects this change because it is over the 60-line sampling bound.

Assisted-by: AI

@tnunamak
tnunamak force-pushed the fix/connector-readiness-projection branch from e4c7947 to 54f2e2f Compare November 5, 2026 21:58
@tnunamak
tnunamak changed the base branch from main to fix/console-install-before-account-setup November 5, 2026 21:58
@tnunamak
tnunamak force-pushed the fix/connector-readiness-projection branch 2 times, most recently from 16ba397 to a269447 Compare November 6, 2026 13:20
…a roster

Whether the owner could add an account for a browser connector depended on
an optional vendored conformance roster. When the roster did not load, no
connector could be added. When it did, a connector on its scaffold list was
refused even after it was published and installed.

The new projection in server/connector-readiness.ts answers from three
separate facts: the artifact a run would execute (a developer-local source,
else the installed version, re-verified against its recorded bytes), whether
this host meets that artifact's declared requirements (the scheduler's own
checks), and the signed catalog, which only decides between "install" and
"not published" when nothing is selected. The result is an action (connect,
install, repair, configure, retry, unavailable) and a reason. Roster
membership and the listing tier are not inputs.

One resolver feeds the owner template list, a new readiness endpoint
(GET /v1/owner/connector-install/readiness) and the browser-enrollment shell
route. The route recomputes the verdict when it is called and returns 409
unless the action is connect, so a stale console cannot enroll a connector
whose artifact has since broken.

Removes browserEnrollmentSupport, isKnownScaffoldConnector and the
is_known_scaffold template field. The roster exports stay in
polyfill-connectors-runtime.ts for non-availability callers.

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

The console ran the same roster-dependent rule in its own process, so the
picker and the server could answer differently from the same inputs. A
published, installable connector (LinkedIn, Goodreads) read "Not built yet"
and its Install control was hidden.

The owner template now carries the server's readiness verdict
(connector_readiness), and the browser-session routes fetch it from
GET /v1/owner/connector-install/readiness. The console parses it at the
boundary, drops a malformed verdict, and lets only a "connect" action add an
account. A blocked verdict is explained next to the package control, which
stays available so an invalid package can be reinstalled. A manifest alone
grants no browser enrollment, so the manifest-only fallback catalog fails
closed. A server that predates the endpoint answers 404; the console then
defers to that server's own enrollment check instead of blocking every
browser connector.

Removes the console's browser-connect-support rule, the isKnownScaffold entry
field and the "Not built yet" copy. The picker test that compared the card to
browserEnrollmentSupport now asserts the picker's action follows the verdict.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
CONFORMANCE_ROSTER_LOADED and KNOWN_SCAFFOLD_CONNECTORS had one consumer
each, both removed with the roster-based availability rule. Keep
PRODUCTION_READY_CONNECTORS, which the supported-collector tier helpers
still read. Also remove an unused type from the readiness route test.

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

The browser-enrollment-shell route now refuses a connector that has no
verified selected artifact. This test registered the ChatGPT manifest
only, so the route answered 409 not_installed before the draft was
classified. Publish the manifest as a developer-local source and set
browser host capability, the same fixtures the readiness tests use, so
the test again reaches the classification it asserts.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The staged-console check looked for a bundled roster predicate
(isSupportedBrowserCollectorConnector or isKnownScaffoldConnector) and
failed the stage when none answered from the roster. The console no longer
bundles either predicate: whether the owner can add a browser connector is
now decided by the server's readiness verdict, so the Windows and Linux
reference-stack staging steps failed with "no bundled roster predicate
answers from the roster".

Keep the checks that still protect something the console reads: the roster
module is required by a bundled module, it and the two other connector
package modules resolve inside the stage, and PRODUCTION_READY_CONNECTORS is
non-empty. Drop the predicate probe, its scaffold-only test and the fixture's
predicate option.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Render the catalog card with a ready verdict and with a failed-verification
verdict and assert what the owner sees: account setup and no note for the
ready one, the repair explanation and no setup link for the other. Both
cases fail against the card without the readiness note.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…bound the readiness reads

The enrollment verdict reused the scheduler's host rule, which requires
a browser surface configured through environment variables because a
scheduled run has no one at the keyboard. Enrollment is attended: the
owner opens the browser themselves. A bare host with no such variables
therefore got `configure` and a 409 for a connector that could run. The
host rule is now one function with two callers: the scheduler keeps the
unattended browser requirement, enrollment checks only required tools
and local paths.

Resolution is now total. A manifest that cannot be hashed, a malformed
requirements block or a throwing probe yields `inspection_failed` for
that connector instead of failing the whole template list. The list read
reuses host-check results for 30 seconds and bounds the catalog read to
5 seconds; the action-time check in the enrollment route always asks the
host. `/readiness` accepts at most 50 `connector_id` values and takes its
candidate keys from the install store without hashing each artifact twice.

The 409 message and the console copy now use a fixed sentence per reason.
`detail` stays on the wire for diagnosis but no longer reaches owner-facing
text, so an absolute path or a raw store error cannot leak through it. The
wire shape drops the unused `artifact`, `catalog` and `host` fields, and
the dead `not_consulted` branch is removed.

A test compares the console's copy of the action and reason vocabulary
with the server's.

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

The console OR-ed the server's `browserBound` with the bundled manifest,
so a connector the server called not browser-bound still opened the
repair routes, and any failed fetch of the verdict read as "not found".
The verdict now decides both fields. The bundled manifest is consulted
only when the server answers 404, meaning it predates the readiness
endpoint; that branch is labeled as a stopgap with its removal
condition. Any other fetch failure is rethrown so the page shows the
error state with its retry action.

The catalog card shows a fixed sentence per reason and no longer
appends the server's `detail`. The note is computed once per card.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…res it does not collect

Removing the conformance roster also removed the only thing that kept a
placeholder connector out of enrollment. A placeholder's package verifies,
its host requirements are met, and its collection step only reports a
skip, so the readiness verdict said `connect`: the owner could create an
enrollment shell, sign in, and collect nothing, repeatedly.

The package now declares this about itself:
`capabilities.collection.implemented: false` in its manifest. A verified,
browser-bound package with that declaration reads `unavailable` with the
new reason `not_implemented`, before the host is asked, so the enrollment
route answers 409 and the catalog card shows a fixed placeholder note.
The server holds no connector list for this, and an absent or other
declaration changes nothing. Manifest validation already accepts the
key, and an older server ignores it.

Until a placeholder package publishes the declaration, the server cannot
tell it from a working one; that is a change in the connector package, not
here.

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

When the server predates the readiness endpoint the console has no verdict
and defers to the server's own enrollment check. It opened the add action
for every connector in that case, including ones the bundled manifest does
not mark browser-bound; the old rule returned false for those. Both
`browserBound` and `canAddAccount` now defer only for a connector the
bundled manifest marks browser-bound.

Also adds the `not_implemented` reason to the console's copy of the
vocabulary, with its fixed note. The drift test against the server's list
covers it.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The readiness endpoint answers for many connectors, but
getConnectorReadiness took the first entry of the response. A reordered
or multi-connector answer would have applied another connector's verdict
to the one the owner asked about. It now selects the entry whose
canonical key matches the requested connector and returns null when none
matches.

The `detail` field is documented as a server diagnostic that the console
does not render, matching what the copy module does. It still travels on
the wire for the owner-only endpoint. A comment on the UAT static-secret
branch records why it is not gated on browser readiness.

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

The rule that withholds Install from a connector with no setup path says
that installing a package never creates one. That held while a bundled
list decided browser setup. Now the server's verdict decides, and for a
browser connector it is the package that makes the connector runnable:
a manifest-only entry (no template yet) reads as a runbook disposition,
so it would show "Not installable" and could never be installed.

A browser-bound entry in an offered tier now counts as awaiting its
package when it has no verdict and is not registered, or when the verdict
is install or repair. Any other verdict (placeholder, host requirement,
not browser-bound) still withholds Install. While the package catalog is
busy the offer stays "unknown", as before.

The earlier runbook fixture used a verdict-less entry to mean "no path";
it now carries the verdict that says installing cannot help.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
…ctor on runnable browser setup

A connector that declares credential capture is exposed for UAT without
asking whether its setup can run. For a browser-bound connector that is
wrong: it takes stored credentials but is still set up through the
owner's browser session, so with no verified package it was exposed with
no way to complete setup. A connector that is not browser-bound has no
such dependency and stays exposed.

The check is one helper used by the base rule and the development-tier
allowlist rule, which had the same gap. It replaces a comment that
wrongly said credential-capture connectors never open the browser shell.

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

reference-implementation's tsc program statically resolved the console
contract file through the readiness vocabulary test's literal import and
failed with TS1287 four times, because the console package is compiled as
CommonJS under this package's verbatimModuleSyntax. `tsc --noEmit` in
reference-implementation therefore exited 1.

Build the specifier at runtime and type the result by hand, the way
sources-report-model.test.ts already imports console modules. The test
still loads the real file at runtime and still compares both vocabularies.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
biome's import-order assist flagged five files this PR added or edited, and
the new console contract test used six non-null assertions. Sort the
imports and replace the assertions with one helper that asserts the owner
copy exists. No behaviour change.

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

A browser connector with no readiness verdict counted as awaiting its
package whenever it was not registered. That also matched an entry built
from a template that a server without the readiness endpoint reports with no
verdict, so a connector whose disposition has no setup path (provider-auth
gated, unsupported) offered Install on a page where the earlier rule says
"Not installable". Five of that rule's own tests failed.

Only a manifest-only entry, which has no registrationStatus because no
template exists yet, awaits its package without a verdict. The earlier
revision hid the problem by giving those tests a blocking verdict; they are
back to their original form and pass.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak
tnunamak force-pushed the fix/connector-readiness-projection branch from a269447 to 2223a67 Compare November 6, 2026 14:28
…r-package bundle module

The desktop build stages the console and then probes it
(scripts/check-staged-console-connectors.mjs): it evaluates every webpack
module whose text names the connector conformance roster package, loading
only .next/server/chunks. Selecting a readiness verdict by key made
connector-install-client.ts import connection-modality.ts, which imports the
shared setup planner and, through it, polyfill-connectors-runtime. Webpack
concatenated the client, the owner-token code it imports and that runtime
into one module that names the roster. Evaluating it needed next/headers
externals that live only in route entry files, so the probe threw
"e[a] is not a function" and all four desktop builds failed.

Move the key rule into the pure connector-readiness-contract.ts
(readinessConnectorKey, selectConnectorReadiness) and have the client import
that. A test pins the copy to the console's canonicalConnectorKey, and a
second test fails if the client reaches the setup planner or the connector
package again. Reproduced locally by building the console and running the
staging probe: it failed before this change and passes after it.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Base automatically changed from fix/console-install-before-account-setup to main November 6, 2026 17:12
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