Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1e445af
fix(mcp): surface PoolExhausted/PoolShuttingDown error context (P0)
Alajmah Jul 8, 2026
78bdad0
feat(detector): P1 model-aware stall detector state machine
Alajmah Jul 8, 2026
6eaf796
fix(detector): P1 review fixes — state transition, auth-expiry, markers
Alajmah Jul 8, 2026
abb5d14
feat(pool): P1.5 lease accounting instrumentation
Alajmah Jul 8, 2026
470d5f3
fix(pool): P1.5 review fix — double-release skip + legacy caller safety
Alajmah Jul 8, 2026
38ddc01
feat(navigation): P2 staged readiness probe + fast-fail on displacement
Alajmah Jul 9, 2026
77faec5
feat(recovery): ZCode SessionStart hook for bridge auto-recovery
Alajmah Jul 9, 2026
b89a316
fix(navigation): P2 review fixes — displacement debounce + JS error d…
Alajmah Jul 9, 2026
38ff8d8
feat(send): P2.5 send-readiness selector hardening + rich diagnostics
Alajmah Jul 9, 2026
34c82a2
fix(send): scope broad fallback to composer form (ChatGPT review caveat)
Alajmah Jul 9, 2026
4d2f2df
docs: P3 multi-client CDP degradation note + corrected root cause
Alajmah Jul 9, 2026
ffef9f7
fix(reconcile): treat non_text as retryable, not terminal (e2e placeh…
Alajmah Jul 9, 2026
63e0db2
fix(navigation): _is_url_at_conversation now handles project-scoped URLs
Alajmah Jul 10, 2026
23af499
refactor(url): use adjacent-pair match per ChatGPT review
Alajmah Jul 10, 2026
3c7c819
fix(owned-mode): remove adopt fallback — never steal another process'…
Alajmah Jul 11, 2026
255046b
fix(owned-mode): use OwnedTabRequiredError consistently in connect path
Alajmah Jul 11, 2026
c92df8a
fix(pool): closing-slot race — don't discard active_keys for replacem…
Alajmah Jul 11, 2026
b3f8f3b
style: fix lint warnings in new test files (unused imports, sort order)
Alajmah Jul 11, 2026
22840ea
style: fix unused imports in source files (ruff)
Alajmah Jul 12, 2026
5a2869b
fix(composer): P0 text verification round-trip — recursive extractor,…
Alajmah Jul 12, 2026
6dbd022
fix(composer): ChatGPT review — placeholder <br> handling + executabl…
Alajmah Jul 12, 2026
5526583
fix(send): P0 send acknowledgment + diagnostic preservation
Alajmah Jul 12, 2026
f440635
fix(send): ChatGPT review amendments — pre-send baseline, tri-state, …
Alajmah Jul 12, 2026
96161a0
fix(send): ChatGPT review C — move valid_probe_seen below composerPre…
Alajmah Jul 12, 2026
b78e200
style: remove unused test scaffolding (ChatGPT minor note)
Alajmah Jul 12, 2026
1711491
Merge branch 'master' of https://github.com/Octo-Lex/ChatGPT-Web2API
Alajmah Jul 12, 2026
8e319d6
fix(#51): no-replay send mutations — at-most-one automatic USER-send …
Alajmah Sep 21, 2026
d0995e1
docs(phase0): upstream contract audit, experiments 1A/1B, recon harne…
Alajmah Sep 21, 2026
9534700
fix(#51): preserve UNKNOWN distinction across REST/SSE/MCP surfaces (…
Alajmah Sep 21, 2026
82a37d7
fix(#51): x-should-retry:false on 409 — close the SDK auto-replay hol…
Alajmah Sep 21, 2026
0a5dd0d
fix(phase0): harness provenance — Bearer-authed probes + IIFE data in…
Alajmah Sep 21, 2026
f94f02e
chore(lint): deterministic import classification + clear inherited ba…
Alajmah Sep 21, 2026
346411c
docs(#51): record independent-review fixes and green CI
Alajmah Sep 21, 2026
162c80c
docs(#51): refresh stale 409 comment to match the header fix
Alajmah Sep 21, 2026
d1e2bd3
test(#51): live ambiguous-write certification PASSED — executed-but-a…
Alajmah Sep 21, 2026
54d5593
fix(#51): cancellation preserves UNKNOWN via side channel; breaker do…
Alajmah Sep 21, 2026
9f52fe6
fix(phase0): harness defects from automated review — same-session CDP…
Alajmah Sep 21, 2026
b810534
docs(#51): record automated-review round 2 (cancellation, breaker fal…
Alajmah Sep 21, 2026
611f53a
docs(#51): restore claim-boundary heading swallowed by the round-2 in…
Alajmah Sep 21, 2026
546c345
fix(#51): cancellation no longer leaks the CDP pending-future entry
Alajmah Oct 22, 2026
78b8ed2
fix(phase0): 1B early-stop actually stops; evidence provenance derive…
Alajmah Oct 22, 2026
4e42ce1
docs(#51): record round-2 follow-ups (pending-entry leak, 1B early-st…
Alajmah Oct 22, 2026
929baa4
fix(phase0): provenance helper anchored to the script's repository, f…
Alajmah Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ sentinel_flow.json

# ChatGPT collaboration binding (local only)
.chatgpt-collaboration.json

# Phase 0 recon raw captures (conversation ids/titles — local only)
captures/
223 changes: 223 additions & 0 deletions docs/research/chatgpt-web-contract-2026-09.md

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions docs/research/experiment-1a-mounting-race.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Experiment 1A — Long-Chat Mounting Race (2026-09-21)

Controlled read-only experiment, executed under the authorization recorded
in the Phase 0 report addendum. Question: does the composer become ready
before the mounted DOM of a sectioned long conversation is stable — the
ordering the bridge's pre-send DOM-count baseline implicitly assumes?

## Verdict

**The race was not observed: 0 of 5 runs.** In every run the mounted DOM was
completely empty through document-complete, then the composer and the entire
mounted conversation window appeared together in a single atomic commit. The
claim is scoped exactly as the acceptance criterion requires: no
post-readiness mounting change was observed in *this* environment,
conversation, Chrome build, account, and capture runs. This falsifies
nothing globally; it fails to confirm the hazard on the path tested.

## Method

- One positively owned tab, created via `PUT /json/new?about:blank` and
closed afterward. No existing tab was touched; no content was mutated.
- `Page.navigate` cold load — the same mechanism the bridge uses
(`cdp_driver.py:1301`) — to the natural 314-node specimen conversation
(`6a8149d3`, web2api project; read-only).
- Sampling every ~150 ms from navigation: URL path, `readyState`, composer
presence, user/assistant counts, mounted `conversation-turn` ids, mounted
`data-message-id`s. After 8 s of unchanged count+identity, a 10 s extended
watch at 250 ms looked for late flaps and identity churn at constant
count. Five cold-load runs.
- Harness: `scripts/phase0_experiment_1a.py`; raw data:
`captures/phase0/exp1a-20260921-092848.json`.

## Results

All five runs showed the same shape (times from navigate):

| Milestone | Range across runs |
| --- | --- |
| `readyState` → loading | ~0.97–1.08 s |
| `readyState` → interactive | ~2.55–2.66 s |
| `readyState` → complete (DOM still empty: 0 composer, 0 messages) | ~2.84–2.94 s |
| Atomic appearance: composer + full window (3 user + 3 assistant, 6 message ids) | ~8.91–9.55 s |
| Post-appearance identity churn (10 s extended watch) | 0 events in 5/5 runs |
| Intermediate count values before the final window | none in 5/5 runs |
| Sample errors | 0 |

Run-to-run timing was tight (composer within a 0.64 s band), and the
ordering was deterministic: composer-ready and mounted-identity-stable
coincide, because they are the same React commit.

## Findings

1. **Atomic mount, not progressive mounting, on this path.** The hydration
commit brings the composer and the conversation tail window up together.
Under this model, "composer ready" implies "DOM baseline readable" —
the bridge's existing sequencing (composer readiness poll, then
`_read_assistant_count_baseline`) lands after the commit in practice.
2. **Sectioned loading re-confirmed from a second angle.** The stable
mounted window is 6 turns (3+3) against 314 backend mapping nodes. The
DOM is a tail window, and it is small even when loading is fully
complete.
3. **A ~6 s hydration dead zone exists after `readyState=complete`.**
Between ~2.9 s and ~9 s the document is complete but React has rendered
nothing. Any logic gating on `readyState` alone would act ~6 s before
content exists. The bridge is not exposed today because its readiness
polls the composer, but this explains the historical
`send_baseline_unavailable` failure class if a baseline read is ever
reached from a readyState-only gate.
4. **The mounted window is not a fixed size across sessions.** The 2026-09-20
passive snapshot saw 5 mounted turns for this conversation; today's runs
saw 6, with no intervening sends. Window size varies; only the backend
graph is stable. (Consistent with the provisional invariant that DOM
counts are diagnostics, not authority.)

## Scope and exclusions

- One account, one Chrome build (153), one network condition, one
conversation, five runs, cold-load path only.
- Not covered: memory-saver/discarded-tab revival, heavy CPU or network
stress during hydration, conversations whose mounted window is larger
than 6 turns (progressive rendering may appear with bigger windows), and
any non-`Page.navigate` navigation path. The bridge's
`navigate_conversation` uses `Page.navigate`, so the tested path is the
bridge's real path shape — but revival and stress remain open.
- Per the authorization: this experiment made no writes. Experiment 1B
(one normal controlled send in a disposable project) is the next
authorized step and had not been executed at the time of writing.
112 changes: 112 additions & 0 deletions docs/research/experiment-1b-send-timeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Experiment 1B — Normal-Send Causal Timeline (2026-09-21)

Controlled mutation experiment under the Phase 0 authorization: exactly one
sentinel turn (`PHASE0-1B-d69d36b8`) through the real bridge pipeline of an
isolated second instance, instrumented end-to-end. Outcome class: **A —
outgoing-UUID evidence is the earliest causal acknowledgment signal, ahead
of every DOM signal.**

## Setup

- Experiment bridge: second instance, REST 8081, config
`captures/phase0/config-1b.json` (owned tabs, parallel per-target locks,
pool disabled). Live bridge on 8080 untouched throughout.
- Browser: **shared Chrome/CDP 9222 with the live bridge** — recorded
environmental limitation (the 9223 listener is Edge/MSN, not an
authenticated ChatGPT Chrome; an independent profile cannot be logged in
without user credentials). Observers attached only to the experiment
bridge's owned tab (`34372C4A…`, diffed against a pre-start snapshot).
- Disposable project: `Web2API Phase0 Controlled Recon`
(`g-p-6ab0df421d288191b97e2781eb7e16a1`, memory_scope `project_v2`).
- Code exercised: working tree `feat/sse-pool-status-endpoint` (master +
`/health` telemetry) — not literally `master`.
- Instrumentation: two CDP observer sessions (Network recorder; DOM sampler
at ~100 ms), bridge log (ms-precision), post-hoc authed projection fetch.
Harness: `scripts/phase0_experiment_1b.py`; raw:
`captures/phase0/exp1b-20260921-104735.json`; bridge log:
`captures/phase0/exp1b-bridge.log`.
- No failure injected; model/effort not changed (observed: effort chip
"High", runtime slug `gpt-5-6-thinking`, reply "ACK", REST 200 in
15.4 s).

## Timeline (offsets from the send mutation: POST `/f/conversation`)

| Offset | Event |
| ---: | --- |
| −8460 ms | REST request fired at bridge (includes navigation) |
| −2180 ms | speculative POST `/conversation/init` (project-page conversation shell, before typing) |
| −578 ms | POST `/f/conversation/prepare` |
| −530 ms | composer filled (first sample >50 chars) |
| −55 ms | typing completed (bridge log) |
| **0 ms** | **POST `/backend-api/f/conversation` — message dispatch** |
| +139 ms | IdentityListener captures outgoing user UUID |
| +141 ms | click `Runtime.evaluate` returns ("Message sent") |
| −39 ms | backend user-node `create_time` (server clock; skew ≈ −40 ms, not the ~+5.1 s the June `SKEW_TOLERANCE` note assumed) |
| +437 ms | composer cleared; stop-button appears |
| +437 ms | DOM user message + `data-message-id`; DOM assistant message (same 100 ms tick) |
| +758 ms | bridge detector: "Assistant message appeared" |
| +1879 ms | POST `/sentinel/req` |
| +3830 ms | `data-message-model-slug` = `gpt-5-6-thinking` visible |
| +3832 ms | conversation id resolved |
| +6910 ms | REST response returned |

## Evidence table

| Evidence | Observed? | Offset | What it proves |
| --- | --- | ---: | --- |
| JS click returned | yes | +141 ms | local JS execution returned |
| Composer cleared | yes | +437 ms | UI reacted |
| DOM user count +1 | yes | +437 ms | mounted DOM changed |
| Outgoing UUID captured | yes | **+139 ms** | submission carried causal identity |
| Backend contains UUID | yes | post-hoc | remote user node exists — **node id == captured UUID** |
| Assistant generation started | yes | +437/+758 ms | model turn began (stop-button; detector) |

The four previously conflated claims separate cleanly here: CDP command
executed (click returned, +141), React accepted the submit (composer
cleared, +437), backend created the USER turn (UUID in projection),
assistant generation started (stop/detector, +437–758).

## Key contract findings

1. **The send path is now `/backend-api/f/conversation` (+
`/f/conversation/prepare`), wrapped in a live sentinel flow**
(`chat-requirements/prepare` → `ping` → `finalize` → `sentinel/req`).
The plain conversation POST is not what carries the message.
`/conversation/init` fires after the send (+3.66 s), and a speculative
shell init precedes typing. The July-era IdentityListener still captured
the client-message UUID from this path unchanged.
2. **Identity chain is exact**: outgoing client-message-id == backend
mapping node id == message id (`b2de19c3-f641-47cd-a4f8-1d7b2ecc473b`).
UUID capture (+139 ms) precedes composer clearing and DOM-count change
(+437 ms) by ~300 ms — the A-outcome. IdentityListener is the strongest
*and* earliest acknowledgment signal; DOM count is strictly slower.
3. **Backend clock skew ≈ −40 ms** on this run — the June-era
`~+5.1 s lead` assumption baked into `turn_anchor.py`'s degraded-mode
freshness floor is stale and should be re-derived (it is env-overridable
but the default encodes the old measurement).
4. **Generation-state classes absent**: `.result-thinking` and
`.result-streaming` never appeared at 100 ms sampling during a
`gpt-5-6-thinking` generation. Generation was observable via stop-button,
assistant-node appearance, and `data-message-model-slug` (+3.8 s) instead.
One controlled run; the generation-lifecycle experiment remains queued,
now with a concrete negative data point.
5. **User and assistant DOM nodes appear in the same 100 ms tick** (+437 ms)
— the assistant placeholder mounts with the user turn, before any text.

## Scope and exclusions

- One run, one sentinel, shared Chrome (timing co-tenancy), single
account/plan, working tree ≠ master, 100 ms DOM sampling (sub-100 ms
class toggles could be missed — though stop-button and text were caught).
- No failure injection (by design — ambiguity tests come later, offline
first). Nothing here tests reconnect/rate-limit replay.
- The disposable project now contains one conversation (`6ab0dff4…`) with
the sentinel turn; retained for the remaining controlled experiments.

## Consequence for #51

The A-outcome supports the recorded design direction directly: primary
acknowledgment = outgoing UUID captured → backend UUID reconciliation;
`SEND_CONFIRMED` on match; DOM count/composer-clearing demoted to
diagnostics. The deterministic transport replay fixtures remain the next
step before implementation.
Loading
Loading