fix(gateway): unwrap receipt envelope in flow_* proxy (closes #37)#40
Merged
stackbilt-admin merged 2 commits intomainfrom Apr 17, 2026
Merged
fix(gateway): unwrap receipt envelope in flow_* proxy (closes #37)#40stackbilt-admin merged 2 commits intomainfrom
stackbilt-admin merged 2 commits intomainfrom
Conversation
`proxyRestToolCall` read receipt fields (`governance`, `createdAt`, facts)
directly off the tarotscript response root. The /run worker returns an
envelope: `{ verified, receipt: { hash, createdAt, governance, facts, ... } }`.
So every field read resolved to undefined and the flow_quality /
flow_governance / flow_status branches silently returned `{ hash }`-only
echoes.
Fix: unwrap once and read from `envelope.receipt`. flow_quality and
flow_governance now return real governance posture + quality signals
(confidence, shadow_density, position_count). flow_status surfaces
verified + createdAt. flow_summary returns the full envelope so callers
can see verification state alongside the receipt.
Regression tests cover the three read paths against a fixture that
mirrors the production envelope shape (engine#23 / tarotscript#199).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove references to internal tracking identifiers from a public-OSS artifact. Descriptive prose that documents the regression guard stays; the private-repo identifiers that named the specific tracking tickets do not. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stackbilt-admin
pushed a commit
that referenced
this pull request
Apr 17, 2026
stackbilt-admin
added a commit
that referenced
this pull request
Apr 17, 2026
#33) * fix(oauth): rescue legacy grants via grant.scope fallback in resolveAuth (#29) The C-1a remediation (commit 256ba06, 2026-04-10) removed the hardcoded ['generate','read'] path in resolveAuth and started enforcing the actual scopes threaded through props.scopes. PR #32 fixed the grant-creation side so empty-scope OAuth initiates get routed through the consent page and mint grants with populated scopes. This closes the last gap: the READ side. Any grant minted before C-1a carries empty props.scopes (the pre-C-1a path wrote the grant top-level scope but never populated props), so every authenticated request from that legacy cohort still hits a (none) scopes error at tool dispatch - exactly the UX symptom #29 and #30 describe. resolveAuth now calls a new resolveLegacyGrantScopes helper when oauthProps.scopes is empty. The helper extracts the bearer, calls the OAuth provider's unwrapToken (which denormalizes the top-level grant.scope onto the token record), and returns it as the effective scope set. Successful rescues are logged so we can measure the legacy cohort shrinking over time and know when to retire both this fallback and the future backfill script. What this does NOT rescue: - Grants minted between C-1a (2026-04-10) and #32 (2026-04-11 20:43Z) where grant.scope and props.scopes are both empty. These users must disconnect and reauth through the #32 consent page - nothing exists on the server to fall back to. Architecture: The @cloudflare/workers-oauth-provider library transitively imports cloudflare:* protocol modules that vitest's node ESM loader cannot resolve, so getOAuthApi is dynamically imported inside the fallback helper. Every other unit test that transitively imports gateway.ts continues to work without touching the library. OAuthProvider configuration was extracted to src/oauth-config.ts so gateway.ts can pass the same options to getOAuthApi without a circular import back through index.ts. In-flight safety: deploy this READ fallback first. The backfill script that rewrites stale KV grants comes in a separate PR and must run AFTER this deploy is live - a partial backfill against workers running old code will 500 in-flight sessions on mixed blob versions. Tests (6 new, 131/131 total): - props.scopes present -> uses them directly, never touches unwrapToken - props.scopes empty + grant.scope populated -> fallback rescues, session created with rescued scopes, tool call succeeds - both empty -> scopes stay empty, tool call rejects with (none) error, which is the UX signal pointing users to reauth - unwrapToken returns null -> graceful empty fallback - unwrapToken throws (KV outage) -> caught, logged, empty fallback - no bearer header -> fallback is inert Related: #28 (closed by #32), #29 (this PR), #30 (should resolve after this deploys + users reconnect). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ci: refresh for current main (post-#40) --------- Co-authored-by: Codebeast <codebeast@stackbilt.dev> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Kurt Overmier <kurt@stackbilt.dev>
This was referenced Apr 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
proxyRestToolCallwas reading receipt fields (governance,createdAt,facts) off the tarotscript response root, but the/runworker returns them nested underenvelope.receipt. Every read resolved toundefined, soflow_quality/flow_governance/flow_statussilently fell through to{ hash }-only echoes.envelope.receipt.flow_qualityandflow_governancenow return real governance + quality signals (determinism profile, capping, scaffold confidence, shadow density, position count).flow_statuscorrectly surfacesverified+createdAt.flow_summaryreturns the full envelope so callers see verification state alongside the receipt.Why this, not the alternatives
The triage comment on #37 framed this as "fix in tarotscript" vs "remove from gateway manifest." Neither was right — the receipt store is correct on the backend; it was a gateway-side unwrap bug. This PR closes #37 with a ~20-line gateway fix and no changes needed downstream.
Test plan
npm run typecheck— cleannpm test— 127/127 pass (including 2 new regression tests)flow_create→flow_governance/flow_qualityreturn populated data, not{ hash }aloneCloses #37
🤖 Generated with Claude Code