Skip to content

th-6fdd1c: bump core to 1.13.2 — the corpus now has zero knownDivergences - #523

Merged
brentrager merged 1 commit into
mainfrom
fix/bump-core-rune-fix
Aug 20, 2026
Merged

th-6fdd1c: bump core to 1.13.2 — the corpus now has zero knownDivergences#523
brentrager merged 1 commit into
mainfrom
fix/bump-core-rune-fix

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Consumes smooth-operator-core#188 (released as v1.13.2), which fixes the mock provider's streamed-text chunker: Go split on byte boundaries, TypeScript on UTF-16 code unit boundaries. Both cut multi-byte characters in half, and each fragment becomes U+FFFD once serialized — irreversibly.

That was the last defect keeping the shared conformance corpus off full parity. interaction-choices-park-resume streams "Pro it is — pulling that quote up."; at 36 bytes a 3-way split lands mid-em-dash, so the Go server accumulated "Pro it is ��� pulling that quote up.".

The finish line

spec/conformance/scenarios/ now carries no knownDivergences marker at all. All 18 scenarios pass on all five servers — Rust, Go, TypeScript, Python, .NET.

The marker did precisely what it was built to do. When the ordering fix (#520/#521) made Go pass four of the five interaction scenarios, the fifth was re-pointed at the real remaining defect rather than deleted — and CI expired it on its own the moment that defect shipped:

remove go from knownDivergences in interaction-choices-park-resume.json — it now passes

That failure is what told me to remove it. No one had to remember.

Changes

from to
go/server/go.mod core/go v1.8.9 v1.13.2
typescript/server ^1.8.4 (locked 1.8.6) ^1.13.2 (locked 1.13.2)

Python and .NET are untouched — their chunkers were checked and never had the defect (Python slices str by code point; .NET delegates to ToChatResponseUpdates() and doesn't split at all).

Also refreshes the corpus README, which documented the Go marker as live.

Verification

  • Go go/server: full suite green, including all 18 scenarios with the marker removed. The Go dep jumped five minor versions (1.8.9 → 1.13.2); go build ./... is clean and no test regressed.
  • TypeScript typescript/server: 387 passed across 41 files, tsc --noEmit clean.

…arker

Core 1.13.2 fixes the mock provider's streamed-text chunker, which split on
byte boundaries in Go and UTF-16 code-unit boundaries in TypeScript. That was
the one defect still keeping the conformance corpus off full parity: the
choices scenario's 36-byte reply put a 3-way boundary mid-em-dash, so Go
accumulated three U+FFFD.

The corpus now carries NO knownDivergences marker at all — all 18 scenarios
pass on all five servers. CI expired the marker itself, exactly as designed.
@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1089283

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@smooai/smooth-operator Patch
@smooai/smooth-operator-web-chat-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager
brentrager merged commit 36d321b into main Aug 20, 2026
8 checks passed
@brentrager
brentrager deleted the fix/bump-core-rune-fix branch August 20, 2026 01:26
brentrager added a commit that referenced this pull request Aug 20, 2026
… in the shared place

The <remarks> claimed 'the DATA lives in the shared place under the shared keys
either way'. Only the key names are shared: this store writes
conversation_sessions.metadata, the other four write conversations.metadata_json
(rust/adapters/postgres/src/lib.rs:618). A note that asserts the divergence away
is worse than no note, since it is exactly what someone would rely on before
pointing both at one database.

States the real divergence, that nothing gates it (knownDivergences was retired
in #523, and a test pinning the current table would lock the drift in), and that
moving ONE key would split this store's three across two tables — strictly worse.
Unification tracked as th-52becd.
brentrager added a commit that referenced this pull request Aug 21, 2026
* th-13df6d: keep Rich Interactions alive across a reconnect

`supports` — the client render-capability list that gates the entire Rich
Interactions framework — lived only on the session
(`Session.metadata.supports`, read by `AppState::session_capabilities`). A
reconnect IS a resume: the client re-opens the socket and re-issues
`create_conversation_session` with the same `conversationId`, which mints a NEW
session id. So unless the client re-declared `supports` every single time, the
server forgot it could render cards and every interaction kind quietly fell back
to conversational collection — no error, no event, nothing on the wire to
notice. Reconnects are routine (network blips, mobile backgrounding, deploys),
so a shipped feature was degrading in the field with no signal.

The session registry was already the wrong home, and this repo had said so once
before: th-c12df5 moved the workflow step pointer off it for exactly this reason
("this per-pod session map resets on reconnect/pod hop"). `supports` now rides
durable conversation metadata (`clientSupports`) the same way — same
read-modify-write shape as `persist_workflow_step`, same best-effort failure
mode — and a resume that omits the key inherits what the conversation last
declared.

A list the frame DOES declare always wins, including `[]`. That is now how a
text-only channel resuming a rich conversation opts out, so the spec's
`supports` description carries the rule and the generated TS/Go/Python/.NET
types are regenerated from it rather than restating it by hand. The inherit
direction is bounded anyway: a card a client cannot render times out
(`INTERACTION_TIMEOUT`) into the same conversational fallback the gate would
have chosen.

Verified across all five implementations first. The pearl's premise that the
four ports keep `supports` in a per-connection map does not hold — Go,
TypeScript, Python and .NET never parse `supports` at all and host no
interactions framework (`interaction_required` / `submit_interaction` /
`identity_form` appear only in their generated wire types), so there is nothing
there to persist yet. Rust is the only implementation with the behavior, so it
is the only one changed.

`reconnect_resuming_a_conversation_keeps_the_declared_capabilities` covers both
directions and fails on the pre-fix handler (verified by reverting).

* th-13df6d: move `supports` onto the conversation in all four ports

Mirrors the Rust reference in the preceding commit. Go, Python and .NET each
kept the declared render capabilities in a per-connection map on the dispatcher
(`FrameDispatcher.supports` / `_session_supports` / `_sessionSupports`), which a
reconnect wipes and which nothing ever pruned. TypeScript already routed the
value through its `SessionStore`, but stored it on the SESSION record — and a
resume mints a new session, so it started empty just the same.

Each port now persists it per CONVERSATION through the mechanism its own store
already used for conversation-scoped facts, rather than a fifth invented one:
Go adds `SetConversationSupports` beside `SetCurrentStep`, Python adds
`get/set_client_supports` beside the workflow-step pointer, TypeScript adds a
`convSupports` map beside `convOwner`/`convOrg`, .NET adds
`Get/SetClientSupportsAsync` beside `Get/SetWorkflowStepAsync`. Every
implementation of each store interface is updated, in-memory and Postgres.

The per-connection maps are DELETED rather than kept as caches, so there is one
source of truth and the leak goes with them.

Distinguishing an omitted `supports` from an explicit `[]` is load-bearing —
omitted inherits, `[]` replaces — and three of the four collapsed them. Go's
frame field becomes `*[]string` (json.Unmarshal gives nil for both), .NET's
`ParseSupports` returns `IReadOnlyList<string>?`, and the TS stores stopped
dropping an empty list on the floor.

Correction to the previous commit message: it claimed the four ports never parse
`supports` and host no interactions framework. That was true of the commit this
work branched from and is false on current main — #505/#509/#513/#520 landed the
framework in Go, TypeScript, Python and .NET in the meantime. The pearl's
original diagnosis was right; the rebase is what surfaced it.

Each port adds a reconnect test that drives a SECOND, FRESH dispatcher over the
same store — a single dispatcher would pass even with per-connection state — and
each was verified to fail against its own pre-fix code before being kept.

Verified: go build/vet/test + gofmt (go and go/server modules), ruff check +
format + pytest (389), tsc + vitest (390), dotnet build + test (642 across five
assemblies). Postgres-backed suites really ran; Docker was up.

* th-13df6d: don't float a reconnect to the top of the conversation sidebar

The TypeScript store's capability write also set conversations.updated_at.
That column is the sidebar's recency sort and is bumped when a MESSAGE lands
(appendMessage); a bare reconnect appends nothing, so this floated every
backgrounded tab to the top of the list on resume. The Go, Python and Rust
stores all leave it alone — a parity-relevant choice, now commented as one.

* th-13df6d: correct the .NET store's divergence note — the data is NOT in the shared place

The <remarks> claimed 'the DATA lives in the shared place under the shared keys
either way'. Only the key names are shared: this store writes
conversation_sessions.metadata, the other four write conversations.metadata_json
(rust/adapters/postgres/src/lib.rs:618). A note that asserts the divergence away
is worse than no note, since it is exactly what someone would rely on before
pointing both at one database.

States the real divergence, that nothing gates it (knownDivergences was retired
in #523, and a test pinning the current table would lock the drift in), and that
moving ONE key would split this store's three across two tables — strictly worse.
Unification tracked as th-52becd.
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