Skip to content

feat(pipeline): add the fact-record pipeline core - #474

Open
Menci wants to merge 76 commits into
mainfrom
feat/pipeline-core
Open

Menci wants to merge 76 commits into
mainfrom
feat/pipeline-core

Conversation

@Menci

@Menci Menci commented Aug 16, 2026 •

Copy link
Copy Markdown
Owner

Adds the runtime-independent pipeline infrastructure for #475 and #476. Stages declare return, through and into over immutable facts; composition checks handover, execution preserves structural sharing, and owned resources/deferred outcomes drain with original error chains.

@floway-dev/pipeline owns stage events, object-space encoding and reading. The new @floway-dev/dump owns recording contracts, metadata schemas, broker codec, wire conversion, stream references and the portable cold recorder. Applications provide storage/publication ports; gateway retains admission, attribution, HTTP measurement, retention and SQL adapters. Explicit dependency rules keep dump independent of gateway/vendor/deployment code and keep foundation packages independent of dump.

Portable LogStream infrastructure provides positioned bytes, attach-only lookup, idle reclamation, pull-driven reads and clean completion framing on Node and Cloudflare. Live writer/reader integration is delivered separately in #565; FileStore here retains completed-byte writes. #475 and #476 remain stacked draft PRs.

Validation at 41d0f2066: full root pnpm run verify passed, including 619 files / 6668 tests, all typechecks, genuine installers (106 passed, 4 existing host skips), repository checks and production Web build/output assertions. All eight CI checks passed. Twenty prohibited import cases reject and both allowed boundary cases pass. The isolated existing UI timeout passed separately and in the final full rerun.

Menci added 3 commits August 16, 2026 18:55
The data plane is moving from an onion of nested interceptors to a list of
stages over one immutable record of facts. This is the package everything
else will compose; nothing in it knows what a model is.

A stage declares where control may go and may declare more than one place —
`return`, `through`, `into` — and there is no name for a kind of stage: the
fields are the declaration. Five shapes are legal and `defineStage`'s five
overloads admit exactly those, with the overload fixing what `next` accepts
rather than a marker boolean or a conditional type. `transform` is sugar in
the `execute` slot, two layers deep, and an omitted direction passes through
unchanged — which is most of them, since today's interceptors call the
continuation 107 times across 68 files and 50 of those are a bare return.

Declarations are checked, never applied. After a stage hands on, in either
direction and on the answering path too, the runner asserts that what it
declared `provides` is there and what it declared `consumes` — and did not
also declare providing — is gone. A key in both is a modify: a translation
taking the source and putting the target elsewhere on the way down, a fork
taking ownership of every branch's disposable and handing one onward on the
way up. Nothing is removed on a stage's behalf, because a declaration that
acts cannot also be checked.

`compose` reasons over declarations, which are strings, so a stage written
against one fact space drops into a pipeline over another with no variance
question to lose. It catches what types cannot: an `into` stage that is not
last, a short-circuit that does not cover what the stages above it need, a
stage that can neither answer nor descend, and the load-bearing one — a key
an earlier stage consumed cannot be needed below it, so a translated request
cannot re-enter its own chain. The entry contract is derived from the stages
and asserted when the first request arrives.

Handover is enforced at runtime by one predicate at every place a value can
enter the record. `Object.isFrozen` cannot be that predicate: freezing a
typed array with elements throws, so the walk skips them, and a shallow
builtin freeze satisfies `isFrozen` while its children stay mutable. So a
WeakSet of handed-over roots is the gate and `Object.freeze` is what makes a
later write throw. A handed record is frozen in place rather than copied, so
a stage that hands on what it received hands on the same object.

The dump is the encoding in the design record: events carrying states and
never differences, an object id taken at first sight so a cycle terminates
on its second visit, `$$` escaping a key that already begins with `$` (tool
parameters are JSON Schema), large strings shared by value because a deep
clone defeats reference sharing, and secrets stored as length, redaction and
hash so a reader can see the same secret twice without ever holding one.

Two things the runner does that a reader should not have to discover: the
run context is threaded rather than ambient, because a module-level variable
saved around an await interleaves two concurrent runs and a gateway serves
concurrent requests by definition; and the disposal sweep runs in `finally`,
because a stage that throws must not abandon an open upstream body — an
aborted connection cannot be reused and leaves its billing unsettled.

Tests carry every property, including the one nothing else guards: on a
49-message conversation with one message rewritten, 48 of 51 objects come
back by identity, so a layer costs what its stage touched. A stage rewritten
to rebuild unconditionally drops that to zero while every other test passes.
…ails

An adversarial review of the first commit ran five lenses over the package and
reproduced every claim it made. Nine blocking defects survived refutation, and
they shared one cause: `run` treated the answer as the only channel out, so
everything else — the events, the open bodies — was reachable only through the
success return.

  · a run that 500s produced no dump at all, though a dump of a failing turn is
    the one an operator most wants and the current gateway does keep it;
  · every body opened below a throw was abandoned, because the sweep read a
    variable that is only assigned on success — and the test written for that
    case asserted the empty result as correct;
  · the sweep was awaited before `run` resolved, so a streaming family could
    never hand its stream back: the drain both blocked the handler and ate the
    frames it was supposed to deliver;
  · a body at a key a stage declared it consumes was released by nobody unless
    the stage happened to fork, though ownership is a declaration and not an
    arity;
  · a fork released a body the stage had handed up under a different key, out
    from under whoever was reading it.

Events now leave through a sink the prologue resolves, the moment they exist —
which also makes recording conditional without a mode flag, and is the seam a
live observer will attach to. What the run owns is tracked where the resource is
created rather than where it lands, so a body is known before the stack unwinds
past it. The drain is handed to the caller, because a streaming family's answer
*is* the stream; a run that threw has nothing left to hand back, so it drains
before the throw propagates.

Two encoding defects went the same way. A typed array was walked by index, so a
400 KiB image became 4.4 MB of NDJSON — an eleven-fold expansion of the value
most likely to be large, and enough to break the single-put sizing the storage
rests on. And `undefined` inside a fact vanished, which is precisely the failure
the design names when it explains why `undefined` is not a removal. Buffers,
`undefined`, `NaN`, the infinities and `bigint` each carry a tag now.

Assembly gained the two terminal checks it was missing: a last stage that can
only descend is unsatisfiable, and a stage that can only answer is where the
array ends. The handoff error names the caller as well as the target. The
handover gate covers the response direction and the answering path. `transform`
no longer repeats the runner's check with a worse message. A log line is
snapshotted where it is written, so a stored line is a state that existed.
`secret` takes a renderer whenever the value is not already a string, because
`String(bytes)` is not a rendering anybody would recognise.

The tests were rebuilt against the properties rather than against the outcome,
after the review showed several passed with the behaviour they named deleted.
Each is now checked by removing what it tests: dropping the drain, the handover
loop or the freeze each fails exactly the test that claims it.
A stage that carries both traits makes two statements, not one. The human's
ruling separates them in as many words — 「如果它短路返回,会 provides 哪些东西,
此时只有 provides」 against 「如果它要透传后面 stage 的 await next() 返回的东西
……它要 provides/consumes/needs 什么」 — and `compose` has always checked them
apart, testing a short-circuit's `provides` against what the stages above need
independently of the pass declaration.

`defineStage` had them sharing one type parameter, so a stage could not answer
with a key its descend path never carries. The first real family found it
immediately: a resolver that refuses a request no upstream can serve answers
with a refusal key, and passes through untouched when an upstream can — and the
compiler rejected the passing-through path for not carrying a key that exists
only when it did not pass through.

The reference example could not have found this. Both of its both-trait stages
answer with a key their descend path also carries, so the two slices coincide
and the conflation is invisible. The test added here is the shape where they
differ, and it fails to compile against the old signature.

The bad error message was the second half of the defect: overload resolution
fell through to the `into` shape and reported that `next` takes too few
arguments, which is not the problem.
Menci added 2 commits August 16, 2026 20:18
§1.4 listed this as the last shape it had not written, and called it "where the
load-bearing typing lives". It needs nothing: a pipeline's interior is not part
of its type, because `compose` takes stages whose slices are already erased, so a
provider composes stages typed over its own space and exports a `Pipeline` whose
type mentions only the gateway's.

The proof is a provider that is a separate module with a fact space of its own.
Its key is not closed over — it is created by one of its stages, travels in the
record where the dump can see it, and is consumed by another before the request
leaves. A caller cannot name it, because it cannot learn that it exists.

Three further shapes, because the claim is about composition and not about one
call: failover forking across the boundary from above it, two providers whose
spaces differ reached through one seal type, and the key's absence from what the
run answers with.

The reference example could not have settled this. Its provider closes the
credential over in a closure, so the key never enters the record and the question
of a travelling foreign key never arises.

This unblocks the chat migration, which is built entirely on handoffs of this
kind — four protocol chains that translate into one another, each ending in a
provider's sealed chain.
`Symbol.asyncDispose` is a release mechanism, and the language hands it out on
terms that do not match a gateway's in either direction. Measured on Node 24:

  Symbol.asyncDispose in (async function*(){})()   true    every async generator
  Symbol.asyncDispose in new ReadableStream()      false   what a body actually is

So the structural predicate was wrong twice over. It adopted every
generator-shaped fact as a run resource, which meant a stage consuming such a key
had its source released the moment it handed up — a transducer silently producing
zero frames — and a fork threw on receiving one at a key nobody declared
consuming, which is every failed-over streaming request. Meanwhile it missed the
upstream body the whole rule exists for.

`own(value, release)` is what says the run is answerable for a value. That is the
same thing `consumes` on the response side already declares, so declaration was
always the mechanism; this is the runner reading it rather than guessing.

Found by the first family that streams, which is the only way it could have been
found: the reference example's bodies are plain objects with a disposer, so both
halves of the mismatch are invisible there. The two tests added here are the two
halves — a generator the language marks and the run does not, and a stream the
language does not mark and the run does.
Menci added 14 commits August 17, 2026 05:43
A stage had no way to begin work without reaching past the record for a
scheduler, and anything it started that way escaped the run's own accounting.
Deferral is now a property of the value: a helper marks a promise, the marked
value enters the record like any other, and teardown waits for what this run
began.

Declared rather than sniffed. Testing for a then method would make a fact that
happens to hold a promise indistinguishable from one the run owes work to, and
it would be quietly awaited instead of reported.

Per run and not per root, because a discarded branch's facts are private to it
and a root-level sweep could never see them. Teardown has a deadline and
exceeding it is an error a reader can act on, since a value that never settles
would otherwise hold teardown open forever.
…a document

The dump's log-line test named itself after structured fields and asserted every
part of the line except them; removing the fields from the encoder left it
green.

Two comments pointed at design documents that are not in the repository, so a
reader following them finds nothing.
…nerator

Two assertions read `Symbol.asyncDispose` off an async generator and required it
to be there. That is a fact about the host rather than about this runner, and it
is false on the host CI runs and `apps/platform-node` supports:

                                                   Node 24   Node 22
  Symbol.asyncDispose in (async function*(){})()   true      false
  Symbol.asyncDispose in new ReadableStream()      false     false

So the suite passed on a developer's Node 24 and failed on Node 22, where the
product behaves identically: ownership is claimed through `own()` and nothing is
ever sniffed.

The variance is worth writing down rather than deleting, because it is a second
argument for claiming. On Node 24 a structural predicate adopts every
generator-shaped fact; on Node 22 it adopts nothing; neither column is what a run
needs. A predicate whose answer depends on the host cannot be what decides which
resources a gateway closes.

What the tests assert now is the run's own answer, which is the same everywhere.
The four chat protocols were referred to by abbreviations that only work if
you already know which vendor owns which shape: `responses`, `messages`,
`chatCompletions` and `gemini`. `messages` in particular collides with the
plural of `message`, which is also a wire field on two of those protocols, so
reading a symbol was not enough to tell an endpoint from an array.

Every identifier, module path, directory and internal string that names a
protocol now carries its vendor and its endpoint:

  chatCompletions -> openaiChatCompletions / OpenAIChatCompletions / openai-chat-completions
  responses       -> openaiResponses       / OpenAIResponses       / openai-responses
  messages        -> anthropicMessages     / AnthropicMessages     / anthropic-messages
  gemini          -> geminiGenerateContent / GeminiGenerateContent / gemini-generate-content

Comments, docs and test names take the prose spelling of the same names:
"OpenAI Responses", "Anthropic Messages", "OpenAI Chat Completions".

Deliberately left alone, because the name is not ours to change:

- Public URL paths and route shapes (`/v1/responses`, `/v1/messages`,
  `/v1/messages/count_tokens`, `:generateContent`, `supported_endpoints`
  values, operator path overrides).
- Vendor and foreign-project strings: the OpenAI error code
  `responses_item_routing_unavailable`, Codex wire values such as
  `responses_compaction_v2`, `openresponses/openresponses` references, and
  every Anthropic/OpenAI/Google payload field.
- Flag ids in `OPTIONAL_FLAG_IDS`; operators have them in stored config.
- Database object names and persisted enum values: the `responses_items` and
  `responses_snapshots` tables, `api_keys.responses_retention_seconds`, their
  indices and triggers, the `expiration_sweeps.domain` and
  `spilled_files.owner_kind` values, the CHECK-constrained `source_api` and
  `target_api` values behind `ChatTargetApi`, and the `ModelEndpoints` keys
  stored in `upstreams.config_json`. Renaming a large table is not viable on
  the deployment target, so the schema keeps its current spelling and the code
  that reads it keeps the schema's words while the surrounding names become
  canonical.
- `responses` and `Messages` where they are the plural of `response` and
  `message` rather than the protocol.
The mechanical pass could not decide these from the token alone, because the
same spelling is a persisted value elsewhere:

- `PUBLIC_DATA_PLANE_ROUTES` route ids and the API-docs entries that name them.
- `PlaygroundApi` members, the records keyed by them, and their i18n labels.
- `CollectKind` members in the request inspector.
- The namespace aliases the OpenAI Responses event builder is imported under.
- The affinity carrier domains. These are AEAD associated data on values a
  client echoes back, so a rename costs one turn of affinity on the deploy
  that lands it and then self-heals: `unwrap` treats a domain mismatch as a
  foreign value rather than failing the request.

`ModelEndpoints` keys, `ChatTargetApi` members and the expiration-sweep domain
keep their old spelling: all three are stored, and two of them are pinned by a
CHECK constraint no migration is allowed to relax.
`openai-*`, `anthropic-*` and `gemini-generate-content` sort differently from
the names they replaced, so `eslint --fix` moved 145 import lines. No other
change.
`ModelEndpoints` was the last place in the tree that abbreviated a protocol:
`chatCompletions`, `responses` and `messages` keyed the map a model declares
its served endpoints with. `ChatTargetApi` mirrored those spellings, and both
now use the canonical `openaiChatCompletions` / `openaiResponses` /
`anthropicMessages`.

The map is authoritative operator configuration, stored per model in
`upstreams.config_json.models[].endpoints` and once more at
`config_json.endpoints` for custom upstreams, so `0083` rewrites the keys in
place. It follows `0024_structured_model_endpoints.sql`, which performed the
same class of rewrite on the same column when the path array became this map.
`MODEL_CATALOG_REVISION` goes to 6 so the derived catalog cache, which is not
authoritative, goes cold instead of being migrated.

The migration test asserts the capability rather than the JSON: it seeds a row
in the old shape, runs the migrations, and reads the endpoints back through the
production repository codec and the custom provider's config parser. Its
negative control runs the same read with `0083` skipped and requires it to
fail, so the test cannot pass for a reason other than the migration.

`ChatTargetApi` is safe to rename because nothing stores it. The
CHECK-constrained `source_api` and `target_api` columns behind it were dropped
when `0050_perf_ttft_tpot.sql` rebuilt the performance tables around
`operation`, so no live column carries those values.
The pass rewrote three classes of value it had no business renaming, each
caught by a test rather than by the typechecker:

- The legacy `pathOverrides` key `chat_completions` that
  `0044_custom_pathoverrides_slash_keys.sql` migrates from; it is stored data
  the migration matches on by name.
- The migration filename `0065_responses_state.sql`, split because the token
  scanner cannot begin a match on a digit and so resumed inside the name.
- Prose already qualified once, which the namespace-alias rename qualified
  again into "OpenAI OpenAIResponses".

The control-plane `modelEndpointsSchema` also still validated the old endpoint
keys, so every azure and custom upstream POST was a 400; test expectations that
read `Object.keys(endpoints).sort()` move with the new sort position.
The dashboard labelled the protocols "Responses", "Messages" and "Chat
Completions" in some places and "OpenAI Responses" / "Anthropic Messages" in
others, in the same locale file. An abbreviation only reads if you already know
which vendor owns which wire shape, and the operator looking at these labels is
exactly the person who does not.

Both locales move together so they stay structurally equivalent. The DeepSeek,
Qwen and Kimi flag descriptions keep "Chat Completions API" — there it names
those vendors' own API, not OpenAI's.

Four comments that still abbreviated mid-sentence beside canonical names are
rewritten to the prose form as well.
The gallery's sample rows named the protocol both ways in one control. It is
dev-only copy, so this is consistency rather than behavior.
The backfill tool's fixtures build an `upstreams` row by hand, so they carry the
endpoint keys directly.
An export taken before the protocols were spelled out in full carries the old
endpoint keys, and restoring one has to say so. It does — the runtime validator
names the key it did not understand — but nothing asserted it, and the property
is easy to lose: the request schema alone would strip an unknown key silently,
leaving a model that imports "successfully" and then serves nothing.

Verified against the mechanism rather than the shape: disabling the endpoint
check in `packages/provider/src/model-config.ts` fails this row and only this
row.
Three protocol names read in full everywhere and the fourth did not: comments
and test names still said "Gemini" and "Chat" where the sentence beside them
already said "OpenAI Responses" and "Anthropic Messages". A peer list reading
"Anthropic Messages/Gemini/Chat" asks the reader to expand two of its three
entries.

Prose about the protocol we serve and translate now names it in full — its
payloads, fields, streams, sources, targets, routes, interceptors and
envelopes. Prose about the vendor does not, because there the abbreviation is
the correct name:

- Google's model families (`Gemini 3 Flash`), its model catalog
  (`/v1beta/models`, "Gemini surfaces chat-kind models only"), its
  OpenAI-compat endpoint, its OAuth, its docs, and its `model:action` URL form.
- `Copilot Chat`, which is the VSCode extension's product name.
- `Chat` as the `ModelKind` in the dashboard's kind picker, and "Chat
  protocols" where it means the chat family rather than one member of it.
- The DeepSeek, Qwen and Kimi "Chat Completions API" descriptions, which name
  those vendors' own compatible endpoints — prefixing OpenAI there would assert
  something false.

Hyphenated compounds take the hyphenated form
(`Gemini-generateContent-via-OpenAI-Chat-Completions`), and articles move with
the vowel the qualified names now start with.

Also lands the data-transfer import test for a pre-rename backup, which failed
to compile: `UpstreamRecord.config` is a provider union rather than an open
record, so the export's serialized config is what the spread has to come from.
The sweep missed `packages/protocols`, whose test names and field comments
still said "Chat" for the OpenAI Chat Completions protocol, and one comment in
`common/models.ts` that says Gemini generateContent has no `ModelEndpoints` key
of its own — a statement about the protocol, not about Google.

`ChatModelInfo`'s "Chat capability metadata" stays: that describes the chat
kind, which all four protocols share.
@Menci
Menci changed the base branch from main to rename-protocols August 18, 2026 18:36
@Menci
Menci marked this pull request as draft August 18, 2026 18:36
Menci added 7 commits August 19, 2026 02:38
Every stage already had a logger whose lines land in that stage's record when a
dump is open, but the recorded event carried an ad-hoc `{ level, message,
fields }` and stuffed the stage's name into `fields.stage`. A reader holding the
log lines alone could not say which stage wrote one without the rest of the run
to resolve `stageId` against.

The event now carries a `LogEntry` — level, context, message, fields — which is
the shape `@guiiai/logg` writes, so a sink that speaks logg takes one unchanged.
`context` is the stage's name, where logg puts the same thing. The type is
declared here rather than imported: a foundation package states the shape it
needs and never the implementation.

The ordering a reader depends on holds by construction, and is now asserted: a
line names its stage by id, entering a stage is what mints that id, and the
entering event goes out before the stage body can log. So no line can arrive
before the event that says which stage it belongs to.
The four chat protocols already carry their vendor. The five non-chat ones did
not, and two of them collide with ordinary English: `completions` is also the
plural noun, and worse, a substring of `/v1/chat/completions`, so a symbol named
`completions` could be either endpoint; `embeddings` is what an embedding
response is full of.

  completions         -> openaiCompletions         / OpenAICompletions         / openai-completions
  embeddings          -> openaiEmbeddings          / OpenAIEmbeddings          / openai-embeddings
  imagesGenerations   -> openaiImagesGenerations   / OpenAIImagesGenerations
  imagesEdits         -> openaiImagesEdits         / OpenAIImagesEdits
  audioTranscriptions -> openaiAudioTranscriptions / OpenAIAudioTranscriptions

`rerank` keeps its spelling. It is a `ModelKind` that fans out to six vendor
wire protocols (`cohere-v1`, `cohere-v2`, `jina-v1`, `voyage-v1`, ...), so no
vendor owns it and a vendor prefix would assert something false.

The two module directories that group these endpoints, `images` and `audio`,
take the vendor as well. They are not model kinds — those are `image` and
`transcription` — but OpenAI's own API families, and leaving them bare would
have made them the only unqualified protocol directories standing beside
`openai-completions` and `openai-embeddings`. `chat/` stays: `chat` is the kind.

`ModelEndpoints` keys are authoritative operator configuration, so
`0083_canonical_endpoint_keys.sql` gains a `WHEN` branch per key rather than a
second migration over the same column — it has not shipped, and it already
rewrites the three chat keys in that map. Its test now seeds two rows, one per
family, because the validator throws on the first key it does not recognize and
a single row could only ever witness one of them; the negative control names a
chat key and a media key. `rerank` is seeded unrenamed as a guard.
`MODEL_CATALOG_REVISION` is already 6 on this branch and covers this too.

Deliberately left alone:

- Public URL paths and path fragments (`/v1/completions`, `/v1/embeddings`,
  `/v1/images/{generations,edits}`, `/v1/audio/transcriptions` and their
  unversioned forms), including `PassthroughServeApiName`, whose members are
  public URL fragments by design, `ENDPOINT_PATHS` values, `pathOverrides`
  keys, and the same set restated in nginx, Wrangler and the Vite proxy.
- `/v1/chat/completions` and every `chat/completions` spelling: that is the
  other protocol's path.
- Copilot wire values: `supported_endpoints` entries, `capabilities.type`, and
  the `completions` quota id with its `x-quota-snapshot-completions` header.
- Persisted, CHECK-constrained `operation` values (`text_completion`,
  `embeddings`, `image_generation`, `image_edit`, `audio_transcription`) and
  the `audio-transcription:` log tag that mirrors one of them.
- Shipped migrations, including `0016_custom_drop_embeddings_endpoint.sql` and
  the legacy underscore `pathOverrides` keys `0044` matches on by name.
- `image_generation` and everything named after it: that is the OpenAI
  Responses builtin tool, not the images endpoints.
- Ordinary English — "text completions", "alternative completions",
  "fast-path completions", "embedding catalogs", `return_embeddings` (Jina's
  rerank field), and the `text-embedding-*` model-id tokens the custom
  provider's kind heuristic matches on.

The gallery's multiselect sample also gets its option values back: the earlier
mechanical pass rewrote `defaultSelectedOptions` without rewriting the `value`
attributes it points at, so the sample rendered with no selection.
…d one

The last abbreviations in the tree. `messages-web-search-shim` becomes
`anthropic-messages-web-search-shim`, and the three `responses-*` shims become
`openai-responses-*`. The other ten ids name a vendor dialect, a rewrite or a
usage convention rather than a protocol, and keep their spellings — the rule is
about protocol names, not about lengthening every identifier.

The dashboard already labelled all four in full ("OpenAI Responses Web Search
Shim"), so this is the id catching up to the copy an operator actually reads.

A flag id is authoritative operator configuration in two columns — per-upstream
`flag_overrides` and per-model `config_json.models[].flagOverrides` — so `0084`
rewrites the keys in place, the way `0083` did for the endpoint capability map.
A stale key fails in two directions and neither is loud: the read path takes any
string, so the toggle survives and silently stops matching, and the write path
rejects unknown ids, so the next dashboard save of that upstream fails on a key
the operator never typed.

One difference from `0083` is worth naming, because getting it wrong is
production-breaking rather than cosmetic. An override's value is a boolean, and
`json_each` hands JSON `true` back as the integer 1, so rebuilding the object
with a bare `json(value)` writes `{"flag": 1}` — which `normalizeFlagOverrides`
throws on, taking every upstream with overrides down at load. The value is
rebuilt from `json_each.type` instead. Verified by mutation: with the naive form
the migration test fails on exactly that assertion.
`target_api` is this gateway's own debug lane, not a vendor field — it carries a
`ChatTargetApi`, so its value is `anthropicMessages` like every other statement
of that value. Two Copilot boundary fixtures still said `messages`.

Found by sweeping the field's values rather than the identifier: a string literal
in a test is exactly where a rename sweep aimed at declarations passes over.
Exercise focus and interactability through user-event so busy confirmation preserves the real activation target. Verify the retry remains accessible and uses the same redemption key.
Role disappearance can precede Fluent surface unmount and Tabster teardown. Observe the old surface leaving the DOM before activating a new invocation, then verify the new dialog owns focus and retains the redemption key.
Exercise reset-card state through the controlled DialogShell lifecycle, including its exit callback, while retaining the real confirmation actions. Verify portal focus, motion, busy state and retry behavior separately in production Chrome; Happy DOM supplies no layout for modal visibility.
Register initial resources and deferred work, share disposal and drain promises, and check declared needs at each boundary. Preserve original stage errors and their fact context, including cleanup failures.

Record deferred outcomes, native stream handles and complete error details. Normalize upload content before recording and mask Secret renderings fully. Add lifetime and codec regressions.
Allow an into stage to dispatch a narrower branch of its declared entry contract. Keep the response contract fixed and reject payloads incompatible with the chosen target.

Verify response-side errors retain the returned facts and original error identity.
Return the prior release callback when replacing a resource's release action. This lets outer stream observers settle around an upstream drain while sharing the same once-only ownership.
Preserve full byte and builtin content in the run object space. Decode cycles and shared references once for event consumers and identity-based inspection, and associate deferred settlement with its original handle.
Separate writer creation from reader attachment, renew one idle lease with writer heartbeats and reader progress, and interrupt expired handles. Store append-only segments, preserve portable RPC error outcomes, pull framed HTTP reads with backpressure, and install the store in both runtime bootstraps. Synchronize Cloudflare bindings, SQLite migration and first-deployment probe.
Keep prologue validation inside the run's cleanup boundary and preserve its original fact snapshot for outer diagnostics.
Object IDs need only survive while their objects remain reachable. Keep value interning for complete bytes and long strings unchanged.
A workerd probe confirmed WebSocket messages reach a paused reader without backpressure. Keep the hibernating socket for tail notifications and fetch one SQLite segment per iterator pull, so reader activity measures consumption and the Worker does not queue a whole backlog. Preserve explicit interruption on server socket failures.
Send native events directly to the recording sink so streamed runs do not retain every historical fact graph through teardown. Keep in-memory collections explicit in diagnostic fixtures and verify the sink observes entry before stage execution.
Accept a byte stream at the portable file boundary. Await filesystem writes into a same-directory staging file before rename, and consume unknown-length R2 streams through bounded multipart parts. Preserve previous objects on failure and retain error chains through cancellation and staging cleanup. A real workerd probe verifies generated-stream constraints and exact multipart publication.
Await stage boundaries, scoped logs and deferred settlement before advancing execution or completing drain. Keep external synchronous loggers supported and preserve original failures when recording also rejects. Event-local encoding stays synchronous; the sink owns serialized durable writes.
Keep the full multipart byte check while avoiding deep comparison of millions of typed-array properties under the parallel repository suite.
Record explicit failures beside folded successful exits. A shared rejection cannot overwrite another run's failure facts: foreign associations use a per-run cause wrapper with the original name and message, while recorded failure content remains the source exception. Preserve primary exceptions when failure recording also rejects.
Apply the same scoped error association when release or deferred teardown rejects, using the accepted return facts or the primary failure context.
Keep atomic staging on each target's filesystem in a reserved private namespace. Retention deletion collects exact unfinished uploads after process death without matching public filenames. Preserve shared directories while writers open their files, avoiding a reproduced concurrent cleanup race. Validate killed-child and controlled overlapping-writer scenarios alongside byte and publication checks.
Keep original fact graphs only for active folding scopes. Drop terminal stage bookkeeping after return or failure while preserving parent return comparison and identity encoding. A separate child with forced GC proves returned and failed payloads are collectible while the encoder remains live.
Exercise inside-out response unwinding, fresh frozen retry inputs and enclosing finally order with original errors. Describe logger fields as a frozen record of shared immutable descendants and document release ownership in current author terminology. Remove obsolete clone and redaction-policy wording; runtime behavior is unchanged.
Wait for an echoed warmup before aborting the established connection. Attach the peer close observer before the abort, assert destruction after that event, and always close the dialed socket. Replace the fixed sleep with the lifecycle state the test intends to verify.
Observe the fake transport's first write and verify the TLS handshake record and ClientHello type before firing cancellation. The post-ClientHello scenario now reaches its declared phase without a fixed sleep. Production TLS behavior is unchanged.
Associate primitive stream and deferred failures with a per-run Error whose cause preserves the exact reason. Keep accepted response facts available to settlement and preserve original source markers, including null and undefined. No global primitive registry is needed. Cover response-side failures and late deferred outcomes.
Reject ASCII case variants of the private staging segment in logical keys and normalized filesystem components. A real case-insensitive filesystem probe allowed an uppercase public key to write inside the private directory. Preserve normal published paths and existing key hashing; cover private variants through all FileStore operations.
Keep this phase within the original pipeline and LogStream infrastructure scope. Restore FileStore contracts, Node/R2 implementations and file-storage tests exactly to main's completed-byte baseline. Remove streaming-only atomic staging and multipart fixtures, retaining their complete implementation on the local archive ref. LogStream contracts, adapters, framing and runtime boot remain intact.
Keep pipeline event encoding and identity semantics in the pipeline foundation. Move recording contracts, broker framing, stream helpers and the cold recorder into a gateway-independent package with explicit storage and publication ports. Gateway retains request policy, attribution and SQL adapters; deployment bootstraps consume the shared codec directly.
Reject forbidden package specifiers as well as resolved relative paths. Foundation packages remain independent of recording, and dump receives gateway and deployment services through ports.
# Conflicts:
#	.agents/skills/deploy-to-cloudflare/SKILL.md
#	.agents/skills/deploy-to-cloudflare/assets/binding-probe.js
#	apps/platform-cloudflare/src/bootstrap.ts
#	apps/platform-node/src/bootstrap.ts
#	apps/web/__tests__/components/upstream-editor/codex-reset-cards_test.tsx
Package-level READMEs have no precedent in the repository and were not
requested; the contracts they described live in the source and AGENTS.md.
The socket-dial and userspace TLS test edits were flake fixes unrelated to
the pipeline core, so those files return to main's versions.
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