The first half of reverse engineering — mechanical splitting, algorithm-family identification, differential verification — produces the conclusion "I understand what it computes." But a conclusion can't ship as-is: the logic is still embedded in the original runtime, and you have to decide what form it lands as — a pure function that goes into CI, or an external process someone has to babysit. Choose wrong and the time you saved earlier gets paid back, with interest, during operations.
The four-stage overview gives this step in a single line: "Stage 4, degrade by transport layer." This piece expands it. The core in one sentence: each tier down jumps the dependency surface, the failure modes, and the deployment cost by an order of magnitude — so you fight upward by default.
Three tiers, fixed order
There are only three landing forms, top to bottom, and this order should be written into your conventions — not decided ad hoc by "whichever runs first":
Native rewrite. Rewrite in your target language, detached from the original runtime, depending only on the standard library. The precondition is that the algorithm family is correctly identified — once the fingerprinting step passes, 90% is copied from the public implementation, the remaining deviation points are handled separately, and it lands as a pure function.
A local JS engine running a minimal fragment. Some logic is too expensive to purify in the short term, so you keep a small slice of the original JS and run the necessary few dozen lines on a local Node/V8 — not the whole page.
A passive browser bridge. One class of state exists only in a real, logged-in page runtime — a runtime-delivered signature, a session-bound dynamic identifier — that static reconstruction can't reproduce, and for now can only be read inside the browser. This is a temporary form; it must be flagged in the interface notes, and must never be rolled out as the default.
The real cost of each tier
The reason the order is fixed: the three tiers' costs aren't linearly increasing — each tier is an order of magnitude over the last.
Tier | Dependency surface | Failure mode | CI-able? |
|---|---|---|---|
Native rewrite | Standard library, zero external processes | Output mismatch, located by one | Yes — it's a pure function |
Local JS engine | One extra Node runtime; V8 context isn't thread-safe, concurrency needs a lock | Engine version, a global the fragment depends on missing | Barely — you have to install the engine |
Passive browser bridge | A real Chrome + extension + a human-maintained session + a local loopback channel | Page not open, session expired, structure changed, tab closed | No — it needs a live human |
The first tier's failures are caught by a unit test; the third tier's failure is "the user closed that tab today." Making something that could be a pure function into a third-tier thing binds a human to every call. Reference figure: once the algorithm family is identified, an obfuscated signature SDK can land as a standalone implementation under 600 lines depending only on the built-in crypto, running at the first tier — the thing you assumed could only go through the browser bridge is usually just an algorithm you haven't fully identified yet.
The passive bridge's boundary: one snapshot, never active
There's only one way a passive bridge spirals out of control: it starts "helping" — auto-refreshing, auto-logging-in, auto-waiting for loads. Every "auto" you add slides it from a forwarder toward a crawler. So the boundary has to be drawn extremely narrow. This set of constraints was learned the hard way:
Take one snapshot probe only; never modify the page. Probe the cookies, session state, and page runtime of an already-open page once each, and only once. Don't create tabs, don't refresh, don't navigate, don't focus, don't poll-and-wait. A missing page is a missing page — don't open it for the user.
Return an explicit error the instant something's missing. No matching tab returns tab_unavailable; page present but not logged in returns not_logged_in; logged in but the runtime isn't ready returns runtime_unavailable. Each of the three error codes maps to one real-world state and one next action — wait for the page, log in, or switch targets — rather than throwing a single "it failed" for the caller to guess at.
Sensitive state never leaves the browser. The extension requests no cookies / webRequest permissions; it only queries an already-open matching tab, completes the request within that page's context, and scrubs fields before returning — cookies and page signature state never leave Chrome for a single step, and the channel connects to a local loopback address by default. The bridge carries results, not credentials.
Whitelist scope granularity: why 15 scopes cover only a handful of platforms
What the passive bridge forwards is whitelisted requests — path, parameters, and referer all constrained by an adapter. Its granularity is the most counterintuitive part: there are 15 scopes in total for only a handful of platforms, because scopes are cut by "page context," not by "platform." The same TikTok has its Creative Center, Top Ads, Creator platform, influencer library, and Ads Manager as five independent scopes — five independent session states, five independent page runtimes; logging into the ad backend won't get you the Creator platform's runtime. Cut one slice per platform and the first "logged into sub-site A but can't serve sub-site B" throws you right back. Xiaohongshu's main site, its app-equivalent paths, and its creator marketplace are likewise three scopes.
Scheduling granularity follows: the lock is applied only at the platform-family level. Requests within the same family (the Douyin family) run serially, because they reuse the same real tab and firing concurrently into one page context makes them trample each other; different families (Douyin and Xiaohongshu) run in parallel — they're two unrelated tabs. Within a family, add a minimum request interval on top. Too coarse and you serialize work that could have been parallel; too fine and requests sharing a tab collide — the platform family is precisely the natural boundary of "shares the same page runtime."
Explicit login handoff: the only permitted human intervention
The passive bridge doesn't operate the page, but sessions expire. The fix is to collapse the human intervention into one explicit, one-shot action: an interactive command enters a handoff, the program opens the corresponding business page through the operating system, waits for you to log in by hand and for the page to be ready, then replays the original request. Throughout, the extension clicks no buttons, fills no forms, exports no cookies — the login is something you do in a real browser, and the program only picks the request back up once you're done.
The critical constraint is that it must not trigger implicitly. A non-interactive command (CI, a scheduled task) never pops a browser; it just cleanly returns a session error and lets the upper layer decide. The handoff also has to guard against false positives: after a successful login, the runtime gets at most a short additional wait (20 seconds in the implementation) before there must be a verdict; if the business page has already redirected to an account page that lacks the target interface context, end the current stage immediately rather than mistaking a deterministic "can't get there" for "still loading" and waiting dumbly. A flow that permits degradation records this stage as unavailable and continues, instead of the whole thing failing.
Stage status: completed, deliberately skipped, needs a session
The common substrate beneath all of the above: no stage may return only "success" or "failure." In an orchestration pipeline, each stage's outcome has six shapes: completed (done), empty (ran but no data), ready (staged, awaiting submission), skipped (deliberately skipped by rule), unavailable (currently unavailable, usually meaning a session is needed), blocked (a precondition isn't met).
"Deliberately skipped," "needs a session," and "genuinely empty" are three completely different signals. Give back one opaque result and you can't tell whether that empty is "supposed to be empty" or "the session died and nobody noticed" — the pipeline can't be operated. Model the stage status as a finite enum, and the orchestration layer (a script or a model) can decide from it whether to degrade, re-authenticate, or abort — the same principle as the passive bridge's three error codes, lifted up to the flow level.
Using the model to judge which tier a capability lands in
Only here does the model come on stage, in a restrained role: it doesn't purify for you, it helps you judge whether and how far to purify — an architecture judgment, not a signature crack. The core is one question: is the state it depends on statically reconstructable, or only available at runtime? From there you weigh the purification effort against how often it changes. A few steps ask different things of the model:
Step | Capability it needs | Pick | model id |
|---|---|---|---|
Read the whole module to see the dependency surface | Long context, reads the call graph in one pass | Kimi K3 |
|
Argue the tier both ways, push back on "just get it working" | Strong reasoning; will make the case for spending an extra day purifying | Claude Opus 5 |
|
Bulk first-pass triage of dozens-to-hundreds of capabilities | Cheap, high concurrency | Claude Sonnet 5 |
|
Attribution after a degradation | Mid reasoning; explains against a failure log | GPT-5.6 Sol |
|
The second tier matters most. The mistake most easily made when placing a tier is that the model follows your "just get it working" tone and hands you "the browser bridge is easiest" — it's echoing you, not costing out the long term for you. A strong-reasoning tier pushes back: "this segment is a standard hash plus one constant perturbation, worth a day to land as a pure function, and shouldn't go on the bridge." Don't take my word — test it yourself: pick 3 pieces of logic, at least 1 with a placement you already know is correct as a control, feed the same "give a placement + argue it + push back on going to the bridge prematurely" prompt to claude-opus-5 and gpt-5.6-sol, and look at one thing only — does it fight to move you up a tier, or lazily default to the third.
The switching cost is the real obstacle
Four tiers from three vendors: three SDKs, three auth schemes, three error formats. Rewriting your client to switch models between steps isn't worth it — which is why most people end up running one model for everything, and then, on the placement judgment that most needs the reasoning tier, use a model that only echoes.
AIReiter flattens that layer: one key, one OpenAI-compatible interface, all four tiers behind it, and switching is just changing the model field in the request body.
# Placement argument: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<placement prompt + the reversed logic fragment + dependency list>"}]
}'
# Bulk first-pass triage: change one field
# "model": "claude-sonnet-5"
# Degradation attribution:
# "model": "gpt-5.6-sol"
Already on the OpenAI SDK? Point base_url at https://aireiter.com/api/v1. On the Anthropic SDK? Hit POST /api/v1/messages with the same key. On price, Claude models run at 30% off list and GPT models at half price. The cost of this workflow sits in two places: the bulk first-pass triage of dozens-to-hundreds of capabilities (Sonnet, high call volume), and the long-context single input of reading a whole module for its dependency surface (Kimi, many tokens per call). Bulk triage runs on a Claude model, so the discount lands right on the densest step; the reasoning-tier placement argument is a Claude model too, at 30% off. Kimi K3's long-context tier is available on the same key.
Try it without signing up — feed a few logic fragments by hand first and watch whether the two models echo you or push back on "should this go on the browser bridge."
In closing
Landing reverse-engineered logic isn't a technical problem, it's a cost problem. The three-tier ladder — native rewrite > local JS engine > passive browser bridge — can't be inverted, because each tier down swaps a pure function for a process with external dependencies, human intervention, and a live tab. The passive bridge isn't a forbidden zone, it's a temporary component with strict boundaries: one snapshot, never active, an explicit error the instant a page is missing, sensitive state never leaving the browser, login only by explicit handoff, stage status always legible — hold those and it's a trustworthy stopgap; drop any one and it's a black box nobody dares maintain. The model helps you judge which tier a capability lands in and holds the line against "just get it working" inertia; as for whether each rewrite is correct, the judge is differential testing, not the model.
