| title | ADR-013: Retirement of the Zero-Browser Static Path |
|---|---|
| status | reviewed |
| last_verified | 2026-09-19 |
| verified_against | capability_profile.py:11, registry.py:691, browser_cascade.py:126 |
| audience | contributors |
Status: Accepted Date: 2026-09-08 (implemented) · 2026-09-19 (recorded) Supersedes: ADR-006 — BeautifulSoup Zero-Browser Fallback Deciders: Nicholas Liebmann
This ADR is written eleven days after the change it records. That delay is itself the reason ADR-017 exists: for those eleven days, eight documents advertised a capability the code had removed.
ADR-006 committed AA to a complete static-HTML perception path for machines
where no browser can be launched. It was implemented: BS4PerceptionAdapter
(438 lines), UrllibHTTPClient (94 lines, standard library only) and
HTTPClientPort (62 lines). The composition root constructed the adapter on its
driver is None branch, and a session mode named STATIC_ASSISTED described
the resulting capability.
The claim mattered more than most: AA's first commitment is a library computer with no administrator rights, and "works with no browser at all" is the strongest possible form of that commitment.
The measurement that ended it. During the 2026-09-08 Lubuntu runs, a session
whose cascade exhausted did not degrade to static operation. It idled. Tracing
found why: STATIC_ASSISTED named a mode, and the perception adapter existed,
but no discovery provider exists that runs without a browser. Every provider
navigates. The static perception adapter was constructed on the driver-None
branch and then never reached by anything, because there was no producer
upstream of it.
The capability was present at the adapter and absent at the pipeline. A user on a machine that could not launch a browser received a session that claimed to be running and did nothing — the worst available outcome, and precisely AA's defining defect class: capability built, never connected.
Retire the static path and make the absence explicit.
BS4PerceptionAdapter,UrllibHTTPClient,HTTPClientPortand their tests are retired, not deleted — they live indocs/old_retired_files/with their origin stamped and their completeness recorded in the ledger (ADR-016).STATIC_ASSISTEDis removed. A mode name with no implementation behind it is a lie the type system helps tell.- When the browser cascade exhausts,
build_orchestratorrefuses to construct a session. It does not build a degraded one. ResolvedCapabilityProfilewithhas_browser=Falsereports an emptyallowed_task_types, because the orchestrator requires a live browser for every task type. The profile says what is true rather than what was hoped.- A browser is now a documented requirement, stated in the README, the
installation guide and
STATUS.md.
A. Keep the adapter and build the missing static provider. Rejected for now, not on merit. A static discovery provider is real work — search engines serve results through JavaScript, and AA's own URL research established that Google's redirect wrapper is encrypted protobuf with no DOM anchor to climb. Building it while the browser path has never completed a single submission would be optimising the second route before the first one works.
B. Keep the mode, make it fail loudly. Rejected. A mode that always fails loudly is a worse version of refusing, with a name that still appears in every menu, document and config file.
C. Delete everything and stop claiming it. Rejected on policy. Four modules were once deleted as "unimportable dead code", and within hours the pin that proved them dead turned out to be the broken thing. Retirement preserves 594 lines of working implementation for the day option A is taken.
D. Retire the implementation, keep the capability as a named seam. — Chosen. It tells the truth today and forecloses nothing.
What becomes true
- AA's behaviour matches its documentation: no browser, no session, stated clearly and early.
- The port inventory stops carrying a promise nothing keeps.
- One fewer mode, one fewer branch in the composition root, three fewer files in
src/.
What becomes harder
- AA's reach narrows. A machine that cannot launch any browser is no longer supported. That is a real reduction against the worst-case-user commitment, and it is recorded here rather than absorbed quietly.
- The static path's re-entry cost is now a provider, not an adapter.
Cost recorded at the decision site: a Tier 0 property (worst-case user reach) was traded for a Tier 1 property (honesty about what the system does). Under AA's lexicographic ordering that trade should normally fail. It is accepted because the reach was never real — the mode could not produce a single posting — so nothing was lost except the claim.
Writing "out of scope" in a way that removes a future possibility is the wrong call, not merely wrong wording. Deferral is written as deferral.
CAP-STATIC — AA must remain able to operate without a browser.
| Requirement | Where the seam is |
|---|---|
| Perception stays behind a port | PerceptionPort; the retired adapter satisfies it as written |
| Fetching stays behind a port | HTTPClientPort, retired intact — recall it first if this is revived |
| Discovery providers must not assume a driver | DiscoveryPort; a static provider is a sibling, not a special case |
| The capability profile stays honest | ResolvedCapabilityProfile reports absence rather than substituting |
Restoration trigger. Revisit when either (a) a live run proves discovery and application work through a browser end to end, so a second route is worth building, or (b) a target deployment is found where no browser can be launched at all — at which point CAP-STATIC becomes a commitment and this ADR is superseded.
Nothing may be merged that forecloses it. A change that moves fetching into a browser adapter, or that lets a workflow assume a driver exists rather than asking the capability profile, breaks this seam and must be rejected on those grounds.
src/auto_apply/domain/models/capability_profile.py:11— the ruling, in codesrc/auto_apply/infrastructure/registry.py:691— emptyallowed_task_typessrc/auto_apply/infrastructure/browser_cascade.py:126src/auto_apply/infrastructure/composition_root.py:103,168— refusal pathdocs/old_retired_files/README.md— ledger rows dated 2026-09-09- ADR-006 — superseded by this record