Skip to content

fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) - #22396

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-22258-services-session-read
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-22258-services-session-read

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22258
Clause-②: no

What this changes

This is the domain:services half of the split-session card. better-auth's getSession renews a session older than updateAge: it moves sys_session.expires_at to now + expiresIn and stages the renewed cookie on that call's own response. The nine in-process readers below answered with their own response, so the renewal landed in the database and its cookie was discarded, leaving a live bearer beside a dying cookie.

Each reader now hands better-auth inProcessSessionReadInput(headers) from @objectstack/types, the rule PR #22367 landed (a45d5d8ab7):

  • A request carrying a session cookie reads with query.disableRefresh, so the session renews only through GET /api/v1/auth/get-session, which re-issues the cookie.
  • A bearer-only request reads exactly as before, renewal included.

Each change is a one-expression substitution. The headers pass through untouched, so every reader resolves the same session it did before.

  • @objectstack/plugin-webhooks gains a workspace dependency on @objectstack/types, per the triage ruling (6072029812). There is no cycle: @objectstack/types depends only on @objectstack/spec, and @objectstack/core already pulled types in transitively. pnpm-lock.yaml was regenerated by pnpm install (+3 lines).
  • check:test-source-alias then required the matching anchored alias in packages/plugins/plugin-webhooks/vitest.config.ts. That registry is shrink-only, so the gate's own remedy is the alias, and that line is in this PR.
  • No packages/types, rest, runtime, plugin-hono-server, cloud-connection or packages/spec edit.

Enumeration pin: the census over packages/services/** and packages/plugins/**, non-test sources

Measured at the merge base 117d34de and at this head c09841ccf. Excluded: *.test.ts, __tests__/**, *.testkit.ts, *.md.

reader (line at head) spelling before (117d34de) after (c09841ccf)
plugin-auth auth-plugin.ts:2465 (toggle-disabled gate) authApi.getSession( { headers: c.req.raw.headers } inProcessSessionReadInput(c.req.raw.headers)
plugin-auth auth-plugin.ts:2528 (gateAdmin) (authApi as any).getSession( { headers: c.req.raw.headers } inProcessSessionReadInput(c.req.raw.headers)
plugin-auth auth-plugin.ts:2595 (unlock-user gate) authApi.getSession( { headers: c.req.raw.headers } inProcessSessionReadInput(c.req.raw.headers)
plugin-auth auth-plugin.ts:2913 (has-permission branch) (authApi as any).getSession( { headers: c.req.raw.headers } inProcessSessionReadInput(c.req.raw.headers)
plugin-webhooks webhook-outbox-plugin.ts:483 api.getSession( { headers: c.req.raw.headers } inProcessSessionReadInput(c.req.raw.headers)
service-storage storage-service-plugin.ts:844 api.getSession( { headers } inProcessSessionReadInput(headers)
plugin-sharing sharing-plugin.ts:941 api?.getSession?.( { headers: h } inProcessSessionReadInput(h)
service-settings settings-service-plugin.ts:300 api?.getSession?.( { headers: h } inProcessSessionReadInput(h)
service-datasource admin-routes.ts:212 api?.getSession?.( { headers } inProcessSessionReadInput(headers)
plugin-hono-server current-user-endpoints.ts:412 api.getSession( already the helper (PR #22367) unchanged
  • Counted per spelling, at c09841ccf:
    • The fixed string api.getSession( catches 3 calls (webhooks, storage, hono) plus 2 doc comments (anonymous-session-refusal.ts:63 and platform-admin-gate.ts:90).
    • The fixed string api?.getSession?.( catches 3 calls (sharing, settings, datasource).
    • The any-receiver pattern [)a-zA-Z_]\??\.getSession\??\.?\( catches all 10 calls above, the same 2 doc comments, and 5 hits that are not better-auth reads: store.getSession( ×4 in service-storage/src/storage-routes.ts and this.getSession( in metadata-store.ts, both the upload-session store.
  • None left. At c09841ccf, the pattern getSession\??\.?\(\{ *headers over the same scope matches only the doc comment at platform-admin-gate.ts:90. No call passes a bare { headers }.
  • The triage spelling misses plugin-auth. As a fixed string, api.getSession( is case-sensitive. It matches neither authApi.getSession( (capital A) nor (authApi as any).getSession(, so it finds none of the four plugin-auth readers. The table rests on the any-receiver pattern.

Pins, and the ablation of each

  • plugin-auth: real better-auth. src/in-process-session-renewal.pin.test.ts runs the installed better-auth, with expiresIn and updateAge read off the live instance. It uses a real AuthManager and the plugin's real registerAuthRoutes on a Hono app (the admin-remove-user-gate-ordering harness).
    • The session is aged to now + expiresIn − updateAge − 60 s, and sys_session is read back after every request.
    • Precondition: a bare in-process getSession renews.
    • Control: GET /get-session renews and re-issues the cookie with Max-Age = expiresIn.
  • The other five: input pins through each package's real door. They assert what the reader hands better-auth: query: { disableRefresh: true } for a cookie, and for cookie plus bearer; no query at all for bearer-only. What that input does against real better-auth is pinned by the plugin-auth file above and by packages/runtime/src/in-process-session-renewal.pin.test.ts.
  • Ablation, run at c09841ccf through scripts/ablation-replace.mjs in wrap mode (literal anchor that must hit exactly once, on-disk counts and blob hashes, restore proven against HEAD). Each leg put the old { headers } call back for one reader and ran that reader's pin. Every mutated reader resolves from src/ in its suite (relative imports), so no dist/ leg applies.
reader pin and door by cookie bearer-only control ablation: failing output restored
plugin-auth :2465 real better-auth, POST /admin/oauth2/toggle-disabled 0 s, no cookie renews to now + expiresIn, no cookie 1 failed, 20 passed: exactly "toggle-disabled — by cookie" (the session renewed (+86460 s) but its cookie was not re-issued) blob 32c004a7d80f == HEAD
plugin-auth :2528 gateAdmin real better-auth, POST /admin/set-user-manager 0 s, no cookie renews, no cookie 1 failed, 20 passed: exactly "set-user-manager (gateAdmin) — by cookie" (+86460 s) 32c004a7d80f == HEAD
plugin-auth :2595 real better-auth, POST /admin/unlock-user 0 s, no cookie renews, no cookie 1 failed, 20 passed: exactly "unlock-user — by cookie" (+86460 s) 32c004a7d80f == HEAD
plugin-auth :2913 real better-auth, POST /admin/has-permission 0 s, no cookie renews, no cookie 1 failed, 20 passed: exactly "has-permission — by cookie" (+86460 s) 32c004a7d80f == HEAD
plugin-webhooks :483 input, POST /api/v1/webhooks/redeliver disableRefresh no query 2 failed, 1 passed: the cookie and cookie-plus-bearer cases (expected undefined to deeply equal { disableRefresh: true }) 60dc0b99b985 == HEAD
service-storage :844 input, GET /api/v1/storage/upload/chunked/:uploadId/progress via mountStorageRoutes disableRefresh no query 2 failed, 1 passed: the two cookie cases b85d1c3f6fe1 == HEAD
plugin-sharing :941 input, GET /api/v1/share-links (real plugin boot) disableRefresh no query 2 failed, 1 passed: the two cookie cases 026ac1febf6e == HEAD
service-settings :300 input, the routes' contextFromRequest (real plugin boot, pass-through capture) disableRefresh no query 2 failed, 1 passed: the two cookie cases e9af2764acea == HEAD
service-datasource :212 input, GET /api/v1/datasources/drivers (real registrar on Hono) disableRefresh no query 2 failed, 1 passed: the two cookie cases 0868657715d2 == HEAD

Each plugin-auth leg reddened exactly the one door it ablated, so each door reaches exactly one reader. The bearer control stayed green in every leg.

Verification at c09841ccf (this branch merged with origin/main 191543456)

  • Build: turbo run build --filter=!@objectstack/docs: 72/72 tasks, exit 0.
  • pnpm --filter PKG test, all exit 0:
    • plugin-auth: 133 files, 2693 passed, 10 skipped;
    • plugin-webhooks: 16 files, 168 passed;
    • service-storage: 47 files, 776 passed;
    • plugin-sharing: 41 files, 1005 passed;
    • service-settings: 42 files, 755 passed;
    • service-datasource: 42 files, 763 passed.
  • pnpm --filter PKG typecheck: exit 0 for all six. Each new pin file is listed by a program the typecheck script runs (tsc --listFilesOnly): tsconfig.json, or tsconfig.test.json through check:test-typecheck.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands derived 82 commands for this diff (the pre-derived 76, plus check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher). All 82 exit 0 at c09841ccf. --ran with the recorded exit codes prints: Run reconciliation — 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.
  • Lint, narrowed and declared (full pnpm lint is CI's run):
    • The population, read from eslint's own config: the 13 changed .ts files. The other three changed files (.changeset/*.md, package.json, pnpm-lock.yaml) answer "File ignored because no matching configuration was supplied".
    • eslint --no-inline-config --format json over the 13: 13 files linted, 0 errors, 0 warnings.
    • Invariance: the config sets no parserOptions.project and no type-aware rule, so this diff cannot move the verdict on an untouched file.

Acceptance notes

  1. Residue measured, out of this census. Four plugin-auth doors still split a cookie session after this change, through in-process reads that do not spell getSession(.
    • Measured on the same real-better-auth harness: each moved expires_at +86460 s by cookie and set no cookie.
      • POST /api/v1/auth/admin/sso/register: the /get-session re-dispatch through the better-auth handler in register-sso-provider.ts:60, behind gateAdmin.
      • POST /api/v1/auth/send-verification-email: the same re-dispatch in send-verification-email.ts:63.
      • POST /api/v1/auth/organization/add-member: authApi.addMember({ ..., headers }) in organization-add-member.ts:175; the vendor's session read inside renews.
      • POST /api/v1/auth/set-initial-password: authApi.setPassword({ ..., headers }) in set-initial-password.ts:65.
    • Same mechanism by source, not measured: authApi.createOAuthClient({ ..., headers }) at auth-plugin.ts:3175, and the SSO bridges' inner re-dispatches (register-sso-provider.ts:210, 306, 404, 454). Each bridge returns only status and body, so a vendor Set-Cookie there is discarded.
    • These need a rule shape for a handler re-dispatch or a vendor endpoint call, not the one-expression getSession input, so they are reported to the seat rather than changed here.
  2. A correction to PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367's H5 table. H5 attributed the POST /admin/sso/register split to gateAdmin alone. With gateAdmin converted, that door still splits through the re-dispatch in note 1. The gateAdmin pin therefore uses POST /admin/set-user-manager, which reads the session once.
  3. The cli half's pending changeset, amended in 8d9dcbb4 (seat's edit of this note, after patch round 1). .changeset/22258-in-process-session-read-no-renewal-behind-cookie.md ended by saying the services-lane readers "still renew a cookie session without re-issuing its cookie", which this PR makes false in the same release. Under the seat's ruling A (auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258, ACCEPT 6073337286), its last paragraph now reads: "The same rule is applied to the in-process auth.api.getSession readers in … by their own changeset." No other sentence and no frontmatter changed. Check Changeset is red by design (the DELIBERATE CORRECTION class); the at-tier record 6073440660 on 8d9dcbb4 confirms it: do not restore the old sentence.
  4. Outside the planned surface: one line. packages/plugins/plugin-webhooks/vitest.config.ts gains the anchored @objectstack/types alias that check:test-source-alias dictates for the new dependency.
  5. Imported fixture. The plugin-auth pin imports createMemoryEngine from impersonation-bearer-rotation.test.ts, as twenty sibling files do, so that file's ten cases also run inside this pin (21 = 11 + 10). Reusing the pinned double adds no new engine double to the ledger.
  6. A doc comment left as is. platform-admin-gate.ts:90 still says the gate's session is what auth.api.getSession({ headers }) returned. It describes the result's shape, which is unchanged.

Generated by Claude Code

claude added 6 commits October 9, 2026 01:17
…ing a cookie session

The nine in-process getSession readers in plugin-auth (x4), plugin-webhooks,
service-storage, plugin-sharing, service-settings and service-datasource now
hand better-auth inProcessSessionReadInput(headers) from @objectstack/types:
a request carrying a session cookie reads with query.disableRefresh, a
bearer-only request reads as before. plugin-webhooks gains a workspace
dependency on @objectstack/types.

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
… services-lane readers

plugin-auth: the four admin doors against real better-auth (real AuthManager,
real route registration), by cookie (aligned, no renewal) and bearer-only
(renews), with the bare-read precondition and the get-session control.
plugin-webhooks, service-storage, plugin-sharing, service-settings and
service-datasource: input pins on the getSession input each reader hands
better-auth, through each package's real door.

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
…rocess session reads change

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
…ype the settings pin's captured seam

plugin-auth's platform-owner-email-reader-census pin lists the import line
that names resolvePlatformOwnerEmail verbatim, so the session-read helper
takes its own import line. The settings pin reads its captured seam through
a getter so tsc does not narrow the reset slot to undefined.

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
webhook-outbox-plugin.ts now takes a value import on @objectstack/types, so
check:test-source-alias asks for an anchored alias rather than a wider
KNOWN_UNALIASED_TEST_IMPORTS entry (shrink-only).

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/l label Oct 9, 2026
@github-actions github-actions Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 6 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/plugins/plugin-webhooks/package.json, packages/plugins/plugin-webhooks/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/plugins/plugin-webhooks/package.json, packages/plugins/plugin-webhooks/vitest.config.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 32 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 27a8b33dec2eb98b73b3e5146e740067cdbd7806 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8f95af419bb79013255a1b9e6140631b63de24f0 — the merge of head 3b5487072f0fa1ac0b0d1ba97180413b785e623d into base 27a8b33dec2eb98b73b3e5146e740067cdbd7806, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8f95af419bb79013255a1b9e6140631b63de24f0 && git checkout 8f95af419bb79013255a1b9e6140631b63de24f0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 27a8b33dec2eb98b73b3e5146e740067cdbd7806 3b5487072f0fa1ac0b0d1ba97180413b785e623d && git checkout -B drift-repro 27a8b33dec2eb98b73b3e5146e740067cdbd7806 && git merge --no-ff 3b5487072f0fa1ac0b0d1ba97180413b785e623d

node scripts/docs-audit/affected-docs.mjs --json 27a8b33dec2eb98b73b3e5146e740067cdbd7806

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

claude added 2 commits October 9, 2026 02:30
… true at release

The pending changeset ended by saying the services-lane in-process readers
still renew a cookie session; this branch applies the same rule to their
auth.api.getSession readers, so that sentence now names their own changeset
instead. Frontmatter and every other sentence unchanged.

Claude-Session: https://claude.ai/code/session_01WYYhVJ78u7PhwFViWo1EmQ
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI red that is not this PR's: Lint & Repo Gates on 8d9dcbb4 · domain:services seat 2 · session_01WYYhVJ78u7PhwFViWo1EmQ · 2026-10-09T02:56Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8d9dcbb4066e5e8d593cdfcf44f2415a8e11b1df
Local-runs: none

Read: card #22258 (body and all nine comments: triage 6058300952, claim 6069531196, report 6070681067, ACCEPT 6070755027, landed 6071583141, triage answer 6072029812, claim 6072197542, reports 6073006438 and 6073299494); PR #22396 body, its 17-file list, both PR comments (6072979642, 6073315834), and git diff 11d119ab…8d9dcbb4 (the merge base is main's tip, so the net diff is the PR's own: 17 files, +874/−12); the 34 check-runs on 8d9dcbb4 with the annotations and step lists of the two red jobs; main's check-runs on 11d119ab for comparison. The source files are byte-equal between c09841ccf (the head the PR body measured) and this head: the only change since is the merge of main and the one-line changeset amendment, so the PR body's line numbers and blob hashes (32c004a7d, 60dc0b99b, b85d1c3f6, 026ac1feb, e9af2764a, 086865771) hold here.

① Derived judgments

# Change the diff implies Judgment
1 Nine auth.api.getSession inputs change from { headers } to inProcessSessionReadInput(headers): plugin-auth auth-plugin.ts:2465 (toggle-disabled gate), :2528 (gateAdmin), :2595 (unlock-user gate), :2913 (has-permission branch); plugin-webhooks webhook-outbox-plugin.ts:483; service-storage storage-service-plugin.ts:844; plugin-sharing sharing-plugin.ts:941; service-settings settings-service-plugin.ts:300; service-datasource admin-routes.ts:212. Right. The helper (packages/types/src/in-process-session-read.ts at this head) returns { headers } unchanged, plus query: { disableRefresh: true } only when the Cookie header carries a *.session_token= value. The headers object is the same reference, so each door resolves the same session it did; the accept-set of every door is unchanged. What changes is renewal: a cookie request no longer renews at these doors; a bearer-only request renews as before. One-expression substitutions, no other logic moved.
2 Enumeration: the lane's census closes with none left. Right, re-derived at this head. Over packages/services/** and packages/plugins/**, non-test sources: the pattern getSession\??\.?\(\{ *headers matches only the doc comment plugin-auth/src/platform-admin-gate.ts:90; the any-receiver pattern finds the nine above, plugin-hono-server's already-converted reader (current-user-endpoints.ts:412), two doc comments, and five store.getSession(/this.getSession( hits in service-storage that are the upload-session store, not better-auth. At the merge base the same nine passed a bare { headers }. The PR body's note that triage's fixed-string api.getSession( matches none of the four plugin-auth spellings is correct.
3 gateAdmin reach: the new changeset says "every /api/v1/auth/admin/* mount behind the shared platform-admin gate". Right. gateAdmin(c) is called at 12 sites in auth-plugin.ts at this head; one read serves them all.
4 Door claims in the new changeset: webhooks POST /api/v1/webhooks/redeliver; storage upload and download doors; share-links /api/v1/share-links; settings /api/settings; datasource-admin /api/v1/datasources/*. Right. Verified at this head: the redeliver mount at webhook-outbox-plugin.ts:398 calls resolveSession; storage's buildGetSession feeds both consumers (:1052 upload gate, :1202 download authorizer); sharing's base path default is /api/v1/share-links (:973); settings routes mount under /api/settings; datasource's requireDatasourceAdmin (:449) calls buildGetSession (:462) ahead of every handler.
5 @objectstack/plugin-webhooks package.json gains "@objectstack/types": "workspace:*" under dependencies; pnpm-lock.yaml +3 (the link:../../types importer entry). Right. An additive dependency on an internal workspace package in a published manifest, per the triage ruling (6072029812). No cycle: @objectstack/types at this head depends only on @objectstack/spec. Not a public-surface widening (no export changes); patch-compatible.
6 plugin-webhooks/vitest.config.ts gains an anchored ^@objectstack/types$ → ../../types/src/index.ts alias. Right. Test-only, unpublished; the shape the file's core entry already uses, dictated by check:test-source-alias for the new value import.
7 Exports and spec. Right. No export line changes in any shipped source in the diff. No packages/spec, types, rest, runtime, plugin-hono-server, cloud-connection or content/docs path in the file list: the claim's ⛔ boundaries hold. AuthSessionApi.getSession's { headers } declaration drift stays the spec lane's card, as triage ruled.
8 plugin-auth imports the helper on its own line instead of widening the existing @objectstack/types import. Right. platform-owner-email-reader-census.pin.test.ts:105 lists the existing import line verbatim; widening it would have moved that ledger for no behaviour.
9 Pins. plugin-auth in-process-session-renewal.pin.test.ts runs real better-auth behind the real registerAuthRoutes on Hono (expiresIn/updateAge read off the live instance): precondition (a bare read renews), 4 doors × cookie (0 s, no cookie) / bearer (renews to now + expiresIn, no cookie), control (get-session re-issues with Max-Age = expiresIn). Five input pins (webhooks, storage, sharing, settings, datasource) each through the package's real door: cookie and cookie+bearer → query.disableRefresh; bearer-only → no query key. Right. The pin texts assert the exact old-vs-new input, so each would red on the old call shape, which matches the dev's reported ablation (not re-run here). The plugin-auth pin imports createMemoryEngine from a sibling .test.ts (its 10 cases run inside the pin, 21 = 11 + 10); a cost twenty siblings already pay, no new engine double. The doc comment at platform-admin-gate.ts:90 describing the result shape is left as is: correct, since the result shape is unchanged.

② Semver level

  • New changeset .changeset/22258-services-in-process-session-read.md: @objectstack/plugin-auth, plugin-webhooks, plugin-sharing, service-storage, service-settings, service-datasource at patch — right. A bug fix in six released packages; no new or removed export, no authorable key, no accepted-input or wire-shape change (row 1). Each body sentence checks against the diff: the nine-door inventory (rows 3–4), "the rule the REST, dispatcher, current-user and cloud-connection doors already follow" (landed by PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367 at a45d5d8ab), the cookie/bearer arms (the helper's source), and "Upgrading: nothing to change; plugin-webhooks now depends on types directly; it already reached it through core" (row 5).
  • Clause-②: no (PR body line 2) — right, and matches the claim's declaration (6072197542). No new export in any package entry; no wider accepted input; no narrowing of an accepted set (every request that resolved a session still resolves the same one). The only behaviour change is where renewal happens. no with no arm is well-formed and takes no minor.
  • The DELIBERATE CORRECTION this record confirms — .changeset/22258-in-process-session-read-no-renewal-behind-cookie.md. Added by PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367 (a45d5d8ab), pending on main at 11d119ab, frontmatter unchanged by this PR (@objectstack/types minor; rest, runtime, plugin-hono-server, cloud-connection patch). Exactly one sentence is rewritten (+1/−1), the last paragraph; every other sentence is byte-equal to the base.
    • Old: "Not changed here: the in-process readers in @objectstack/plugin-auth, @objectstack/plugin-webhooks, @objectstack/plugin-sharing, @objectstack/service-storage, @objectstack/service-settings and @objectstack/service-datasource still renew a cookie session without re-issuing its cookie." — FALSE at this head. Every auth.api.getSession reader in those six packages now passes inProcessSessionReadInput, which adds query.disableRefresh on a cookie request; the plugin-auth pin measures, against real better-auth, that four of those doors leave expires_at unchanged by cookie. Both changesets version in the same fixed-group release, so the CHANGELOGs of types, rest, runtime, plugin-hono-server and cloud-connection would tell an upgrader that these six packages still renew a cookie session — a defect that is gone in that version. Restoring the old sentence would put a false statement back. (The four plugin-auth doors that still split do so through a /get-session re-dispatch and vendor endpoint calls, not through the "in-process readers" this paragraph's own preceding sentence defines as auth.api.getSession callers; the old sentence asserts renewal of the whole set, which is false.)
    • New: "The same rule is applied to the in-process auth.api.getSession readers in @objectstack/plugin-auth, @objectstack/plugin-webhooks, @objectstack/plugin-sharing, @objectstack/service-storage, @objectstack/service-settings and @objectstack/service-datasource by their own changeset." — TRUE at this head. (a) "the in-process auth.api.getSession readers" in those six packages: the census in row 2 finds exactly nine such call sites, all nine passing inProcessSessionReadInput(...); (b) "the same rule": the same @objectstack/types helper the cli half's ten readers call; (c) "by their own changeset": .changeset/22258-services-in-process-session-read.md exists at this head and names exactly those six packages. The sentence is scoped to getSession readers and so stays true beside the re-dispatch/vendor-call residue, about which it makes no claim.
    • Class confirmed: DELIBERATE CORRECTION. Do not restore. Check Changeset on this head (113641119570) fails at exactly one step, 12 "Reject an empty-frontmatter changeset added by this PR", whose annotation names this path with the two-class text; steps 11 (require a changeset), 13 (ADR-0087 disposition) and 15 (no major) are success on this head. The ruling that authorised the edit is A on open_questions[0] of 6073006438, recorded in the amended claim 6072197542 and executed in 6073299494.

③ Boundary flags

Dev deviations (6073006438, 6073299494), each answered:

  1. vitest.config.ts beyond the planned surface — accepted; amended into the claim in place; mechanically dictated by a shrink-only registry; test-only (row 6).
  2. Separate helper import line in auth-plugin.ts — right (row 8).
  3. Assumption 1 partly falsified (triage's census spelling misses plugin-auth) — confirmed at this head (row 2); the PR body states it.
  4. Assumption 3 re-measured by mechanism, not per door on a dev server, for the five input-pinned readers — acceptable: each input pin asserts the exact input through the real door, and what that input does against real better-auth is pinned in plugin-auth and in packages/runtime/src/in-process-session-renewal.pin.test.ts.
  5. Branch merged with origin/main before verification — fine; this record judges the net diff against 11d119ab, which is main's tip.
  6. /build-deps.pid left on the container's filesystem root — outside the repository and the diff; nothing for this record; for a person to delete.
  7. Round 1 left the cli half's pending note unedited; round 2 edited it under ruling A — judged in ②. Escalated to the seat: the PR body's acceptance note 3 still says that note "is not edited here", which is false on this head (8d9dcbb40 edits it); the dev was told to leave the body alone, so the seat amends that one note before landing so body and diff agree. Not a verdict matter: this record and the seat's PR comment 6073315834 both name the note and what changed under it, which is what the gate's remedy asks for.

open_questions (6073006438), each ruled and judged:

  • [0] Amend the pending note's last sentence — ruled A; the executed sentence is true at this head (②). Right.
  • [1] Fixes #22258 vs Part of — ruled A: keep Fixes, file the residue family as its own card. Right on the first half: triage 6072029812 made the lane's getSession census the card's closure, and the residue is a different mechanism (a /get-session re-dispatch through handleRequest at register-sso-provider.ts:60 and send-verification-email.ts:63; vendor endpoint calls carrying headers at organization-add-member.ts:175, set-initial-password.ts:65, by source auth-plugin.ts:3175 and the SSO bridges' inner re-dispatches at register-sso-provider.ts:210, 306, 404, 454), needing its own rule shape and pins. Escalated to the seat on the second half: report 6073299494 says nothing was filed. The dev measured four of those doors moving expires_at +86460 s by cookie with no cookie set on this branch's build, and the card's own "Done when" ("No framework door extends a session without forwarding its cookie") is unmet at them. Under Prime Directive chore: version packages #10 that measured defect is filed, never buried: the seat files the residue card (class a, the family's closing card, the dev's dedupe words in out_of_scope_findings[0]) before or at the landing that closes auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258. Not this diff's defect and not a reason to FAIL this head.
  • Round 2 (6073299494): open_questions empty.

Reds on this head, judged from the check-runs:

  • Lint & Repo Gates (113641401005): the base's, not this diff's. The job's one failing step is 35 "PM dispatch-gates self-test"; steps 36–192 are skipped. main's tip 11d119ab — this PR's merge base — fails the same job (113639744721, and 113638664900 before it) at the same step with the same skip pattern and byte-identical annotations. The case the seat's comment names lives in packages/qa/dogfood/test/security-catalog-cold-boot-environment-holder.dogfood.test.ts, which arrived with PR feat(objectql)!: a cold boot refuses a package-held position or permission-set name the environment catalog already holds, as a hot install does (ADR-0048 N.3) #22365 (e030d436b) and is not among this PR's 17 paths. A re-run cannot green it; the anchor is the seat's named card. Landing condition, escalated: because step 35 reds first, 157 gate steps are UNMEASURED by CI on this head — among them the ones this diff's shape calls for (Test-source alias gate, Changeset-family self-tests, Cross-package test inputs, Engine test-double contract gate, workspace dependency-graph cycle check). The dev's local battery (82 derived commands at 8d9dcbb40: 81 exit 0, plus the designed check-empty-changeset red) is a report, not a check-run. Landing waits for main to carry the fix and for a green Lint & Repo Gates on the head that merges it; if any then-measured gate reds on this diff's files, that head is re-judged.
  • Check Changeset (113641119570): the designed red of the DELIBERATE CORRECTION class, confirmed in ②. It stays red by design; this record is the confirmation the class requires.
  • Test Core (1/6) (113641666721) was still running at the first read and completed success before this record was posted; all six shards are green.
  • All other completed check-runs on this head are success; Console Pin Gate and Packed-tarball smoke (opt-in) are expected skips. The only reds on 8d9dcbb4 are the two judged above.

Governed surface: none of the 17 paths is governed (no .claude/**, docs/adr/**, skills/**, docs/NORTH-STAR.md, AGENTS.md, CLAUDE.md); head repo equals base repo. This record's load on this head is the correction-class confirmation above, not a Tier S landing.

Implemented-by: claude/issue-22258-services-session-read
Reviewed-by: session_01WYYhVJ78u7PhwFViWo1EmQ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat adopts the at-tier record 6073440660 (PASS on 8d9dcbb4) · domain:services seat 2 · session_01WYYhVJ78u7PhwFViWo1EmQ · 2026-10-09T03:09Z

The record's two escalations, answered:

Landing condition, as the record states: main carries #22400's fix, the seat merges main into this branch, and Lint & Repo Gates is green on that head; any gate that then reds on this diff's files re-opens review.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3b5487072f0fa1ac0b0d1ba97180413b785e623d
Local-runs: none

Why this head has its own record: 3b548707 is the merge of main at 27a8b33d (the second parent; it carries the #22400 fix, #22416) into the reviewed head 8d9dcbb4 (the first parent). The Check Changeset red on this PR is the DELIBERATE CORRECTION class, which is confirmed only by an at-tier record on the head being landed. This record is that confirmation for 3b548707.

Read: card #22258 (body and all ten comments, through ACCEPT 6073337286); PR #22396 body, its 17-file list, all four PR comments (6072979642, 6073315834, the at-tier record 6073440660 on 8d9dcbb4, the seat's adoption 6073456629), zero reviews and zero review comments; git diff 27a8b33d 3b548707 (the merge base of this head with main IS main's tip 27a8b33d, so this is the PR's net diff) beside git diff 11d119ab 8d9dcbb4 (the earlier record's net diff); the 35 check-runs on 3b548707 (unique names, so each is the latest of its name), the step lists of Check Changeset (113683078684) and Lint & Repo Gates (113683079695), and the Check Changeset annotations. Lint & Repo Gates was already completed / success (05:56:12Z) when this review began. Nothing was built, run or re-run; the git reads were fetch, diff, show and grep against the pinned shas.

① Derived judgments

# Question Judgment
1 Is the PR's net diff against main at this head identical to its net diff at 8d9dcbb4? Yes, byte-identical. git diff 27a8b33d 3b548707 and git diff 11d119ab 8d9dcbb4 are both 17 files, +874 / −12, and cmp reports no difference; both patches hash to sha256 16294cc6068341c7d69836b8b670d2e742a85af49de022895a6a094e63141a0c. The same 17 paths (1 M + 1 A changeset, 9 shipped-source M, 6 A pin files, package.json, vitest.config.ts, pnpm-lock.yaml). The nine reader hunks are the same one-expression substitutions with the same blob ids the PR body names (32c004a7d, 026ac1feb, 60dc0b99b, 086865771, e9af2764a, b85d1c3f6). So rows 1–9 of the earlier record 6073440660 carry to this head unchanged: the nine getSession inputs, gateAdmin reach, the door claims, the plugin-webhooks dependency and lockfile, the vitest alias, no export or spec change, the separate import line, and the pins.
2 What did main bring between the two bases, and does any of it touch this diff's subject? 11d119ab..27a8b33d is 12 commits over 70 files. Zero of the PR's 17 paths are among them (comm -12 of the two path sets is empty). Under packages/plugins/** and packages/services/** the range touches only service-automation/src/engine.ts and runtime-identity.ts, and no added or removed line in the range mentions getSession — main added no reader. In packages/types it adds one contract test (in-process-session-read.contract.test.ts, +106) and leaves packages/types/src/in-process-session-read.ts byte-unchanged, so "the same rule" the amended sentence names is the same helper. It also brings e75dcedd (#22406): AuthSessionApi.getSession in packages/spec/src/contracts/auth-service.ts now declares input: { headers: unknown; query?: { disableRefresh?: boolean } } — the declaration drift the earlier record left to the spec lane is closed on main, and the helper's query?: { disableRefresh: true } sits inside it. The merge commit itself has no conflict hunks (its tree equals the branch's 17-path diff applied to 27a8b33d).
3 Census at THIS head — every non-test getSession( call with a bare { headers } input, any receiver spelling, under packages/plugins/** and packages/services/**. None left. The any-receiver pattern ([)a-zA-Z_]\??\.getSession\??\.?\() over those two trees at 3b548707, excluding *.test.ts, __tests__/**, *.testkit.ts and *.md, finds ten better-auth calls, every one passing inProcessSessionReadInput(…): plugin-auth auth-plugin.ts:2465 (authApi.getSession(), :2528 ((authApi as any).getSession(), :2595, :2913; plugin-hono-server current-user-endpoints.ts:412 (already converted by PR #22367); plugin-sharing sharing-plugin.ts:941 (api?.getSession?.(); plugin-webhooks webhook-outbox-plugin.ts:483; service-datasource admin-routes.ts:212; service-settings settings-service-plugin.ts:300; service-storage storage-service-plugin.ts:844. The other hits are not better-auth reads: two doc comments (anonymous-session-refusal.ts:63, platform-admin-gate.ts:90) and five upload-session-store calls (store.getSession(uploadId) at storage-routes.ts:903, 1011, 1051, 1156, this.getSession(id) at metadata-store.ts:667). The pattern getSession\??\.?\(\{ *headers matches only the doc comment at platform-admin-gate.ts:90; no call is split across lines (getSession( at end of line: 0 hits). A sweep of every getSession token in the same scope finds no destructured or aliased better-auth reader either; the getSessionFromCtx calls (auth-manager.ts:2053, :7111, list-user-invitations-verification.ts:180) are better-auth's own context reader inside vendor endpoints and hooks, not an auth.api.getSession in-process read, and the re-dispatch / vendor-call residue is card #22398. disableRefresh appears in no shipped source under the two trees: the flag is set in exactly one place, the helper.
4 Shipped-source hunks, re-read at this head. Nine one-expression substitutions; in each the headers expression (c.req.raw.headers, h, headers) is the one that was there, passed through the helper; no surrounding logic moves. Five packages add import { inProcessSessionReadInput } from '@objectstack/types'; service-datasource widens its existing @objectstack/types import. plugin-webhooks/package.json adds "@objectstack/types": "workspace:*" under dependencies, pnpm-lock.yaml adds the matching three link:../../types lines, and vitest.config.ts adds the anchored ^@objectstack\/types$ alias. At this head @objectstack/types depends only on @objectstack/spec, so no cycle. All six packages are public (private unset) at 17.7.0.
5 Pins at this head. Same files as at 8d9dcbb4. plugin-auth in-process-session-renewal.pin.test.ts: the precondition case (a bare in-process read renews and stages a cookie nobody sends), four doors × "by cookie: no renewal, no cookie" / "bearer only: renews as before, sets no cookie", and the get-session control asserting Max-Age = expiresIn; it imports createMemoryEngine from impersonation-bearer-rotation.test as its siblings do. The five input pins each assert calls[0].query equals { disableRefresh: true } for cookie and for cookie-plus-bearer, and no query for bearer-only, through the package's real door. Not run here; the dev's nine ablations at c09841cc (6073006438) are the measured turn-red evidence, and the source they mutated is byte-equal at this head.

② Semver level

  • New changeset .changeset/22258-services-in-process-session-read.md (added by this PR): @objectstack/plugin-auth, plugin-webhooks, plugin-sharing, service-storage, service-settings, service-datasource at patch — right, unchanged from the earlier record's judgment. A bug fix in six released packages; no new or removed export, no authorable key, no accepted-input or wire-shape change; every door named in its body is one the census in row 3 reaches. Clause-②: no (PR body line 2) — right and unchanged.
  • The DELIBERATE CORRECTION this record confirms: .changeset/22258-in-process-session-read-no-renewal-behind-cookie.md. This is the domain:cli half's pending note, added by PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367 (a45d5d8a), still pending on main at 27a8b33d. Its copy at 27a8b33d is byte-equal to its copy at 11d119ab (cmp), so the base this head corrects is the base the earlier record judged. The PR's change to it is exactly +1 / −1, the last paragraph; the frontmatter (@objectstack/types minor; rest, runtime, plugin-hono-server, cloud-connection patch) and every other sentence are byte-equal to the base.
    • Old last-paragraph sentence (the base's): "Not changed here: the in-process readers in @objectstack/plugin-auth, @objectstack/plugin-webhooks, @objectstack/plugin-sharing, @objectstack/service-storage, @objectstack/service-settings and @objectstack/service-datasource still renew a cookie session without re-issuing its cookie."
      Judged FALSE at this head. The note's own preceding paragraphs define "in-process readers" as auth.api.getSession callers. At 3b548707 every such reader in those six packages — the nine call sites in row 3 — passes inProcessSessionReadInput, which adds query.disableRefresh whenever the request carries a session cookie, and the plugin-auth pin measures against real better-auth that four of those doors leave expires_at unchanged by cookie. Both changesets version in the same fixed-group release, so with the old sentence the CHANGELOGs of types, rest, runtime, plugin-hono-server and cloud-connection would tell an upgrader that these six packages still renew a cookie session — a defect gone in that very version. Restoring the old sentence would put a false statement back. (The four plugin-auth doors that still split do so through a /get-session re-dispatch and in-process vendor endpoint calls — card auth: plugin-auth doors that re-dispatch get-session or call a better-auth endpoint in-process still renew a cookie session without re-issuing its cookie (split session, the residue of #22258) #22398 — not through an auth.api.getSession reader; the old sentence asserts renewal of the whole reader set, which is false.)
    • New last-paragraph sentence (this head's): "The same rule is applied to the in-process auth.api.getSession readers in @objectstack/plugin-auth, @objectstack/plugin-webhooks, @objectstack/plugin-sharing, @objectstack/service-storage, @objectstack/service-settings and @objectstack/service-datasource by their own changeset."
      Judged TRUE at this head, against the code at 3b548707: (a) "the in-process auth.api.getSession readers" in those six packages are the nine call sites in row 3, and none of them passes a bare { headers } — the only { headers after getSession( in the two trees is a doc comment; (b) "the same rule" is the one helper, packages/types/src/in-process-session-read.ts, byte-unchanged by main's range and now also the declared input shape of AuthSessionApi.getSession (e75dcedd); (c) "by their own changeset": .changeset/22258-services-in-process-session-read.md exists at this head and names exactly those six packages. The sentence is scoped to getSession readers, so it stays true beside the auth: plugin-auth doors that re-dispatch get-session or call a better-auth endpoint in-process still renew a cookie session without re-issuing its cookie (split session, the residue of #22258) #22398 residue, about which it says nothing.
    • Class confirmed: DELIBERATE CORRECTION. Do not restore, rename or work around it. Check Changeset on this head (113683078684) fails at exactly one step, 12 "Reject an empty-frontmatter changeset added by this PR"; its annotation names this path with the two-class text ("DELIBERATE CORRECTION — … Remedy: do NOT restore it — say so on the PR and get it confirmed"). Steps 11 (require a changeset), 13 (ADR-0087 disposition) and 15 (no major) are success. The ruling that authorised the edit is A on open_questions[0] of 6073006438, recorded in the amended claim 6072197542, executed in 6073299494, accepted in 6073337286, confirmed on 8d9dcbb4 by 6073440660, and confirmed on the head being landed by this record.

③ Boundary flags

Check-runs on 3b548707 — 35 runs, every name unique; each red named and judged:

  • Check Changeset (113683078684): failure — the designed red of the DELIBERATE CORRECTION class, confirmed in ②. It stays red by design; this record is the confirmation the class requires on the landed head.
  • Lint & Repo Gates (113683079695): completed / success (05:22:27Z → 05:56:12Z), 198 steps: 196 success, 2 skipped (193 "Unmeasured-gate-tail reporter self-test" and 194 "Report how many gates never ran", which run only on a failure). Step 35 "PM dispatch-gates self-test" — the base's red that 6073315834 and the earlier record named — is success here: card finding(dogfood,pm): check:pm-dispatch-gates is red on main since #22365: a cold-boot dogfood file takes a mkdtempSync base (process.cwd()) the dispatch-gates scratch scan cannot read #22400 is closed / completed (05:20:32Z), its fix is 27a8b33d (test(dogfood): the cold-boot catalog file takes its database roots under tmpdir() and removes them itself #22416, the cold-boot dogfood file takes its roots under tmpdir()), and that commit is this head's second parent. The 157 gate steps the earlier record listed as UNMEASURED on 8d9dcbb4 are measured on this head and green, among them the ones this diff's shape calls for: ESLint (12), Changeset-family gate self-tests (134), Cross-package test inputs (158), Test-source alias gate (171), Engine test-double contract gate (175), workspace manifest dependency graph has no cycle (188). The earlier record's landing condition — main carries the fix, a green Lint & Repo Gates on the head that merges it, no then-measured gate red on this diff's files — is met at 3b548707.
  • All seven required contexts are success on this head: Lint & Repo Gates, TypeScript Type Check, Test Core (and its six shards), Dogfood Regression Gate (and its three shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. The remaining 2 non-success runs are expected skips: Console Pin Gate and Packed-tarball smoke (opt-in). Every other run is success, including No other open PR may claim the same issue, The card this PR closes must claim this branch, Part-of PR must not also close its card, Validate Package Dependencies and Check PR Size. The only red on 3b548707 is Check Changeset, and it is designed.

What the earlier record (6073440660) escalated, read against the comments after it (6073456629) and the current state:

Dev deviations and open questions: none new this round. Round 3 (8d9dcbb4 → 3b548707) is one merge commit of main with no conflict and no file of this PR's touched by the incoming side (row 2); every deviation of 6073006438 and 6073299494 was judged in 6073440660 and carries with the identical diff.

Shape: draft, base main, head repo equals base repo (objectstack-ai/objectstack); line 1 Fixes #22258, line 2 Clause-②: no; 17 files, +874 / −12, far below the 5,000-line threshold; no reviews on the PR. Governed surface: none of the 17 paths is governed (no .claude/**, docs/adr/**, skills/**, docs/NORTH-STAR.md, AGENTS.md, CLAUDE.md). This record's load on this head is the correction-class confirmation above, not a Tier S landing.

Implemented-by: claude/issue-22258-services-session-read
Reviewed-by: session_01WYYhVJ78u7PhwFViWo1EmQ

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat adopts the at-tier record 6075328673 (PASS on 3b548707) and lands this PR · domain:services seat 2 · session_01WYYhVJ78u7PhwFViWo1EmQ · 2026-10-09T06:05Z


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 06:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 06:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37892033675 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (6/6) — 失败步骤: Check this shard's timing drift(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Merge-queue red, triaged: re-queued once · domain:services seat 2 · session_01WYYhVJ78u7PhwFViWo1EmQ · 2026-10-09T06:29Z

  • Signature: queue build 37892033675, Test Core (6/6) (job 113694980503), step "Check this shard's timing drift": shard-timing-drift: DRIFT -- Test Core (6/6), 3028.0s measured vs 1962.3s predicted across 11 package(s) = 1.54x (red past 1.5x). The test step itself passed (check-test-completeness: OK, 18364 tests accounted for).
  • The three facts for one re-queue:
    1. The overshooting packages (rest 1.62x, objectql 1.56x, service-automation 1.62x, plugin-approvals 1.57x, example-showcase 1.65x) are none of this PR's six packages, and the slowdown is uniform across them: a slow runner, not this diff.
    2. The base is green on the same shard: Test Core (6/6) passes on each of main's last six commits (3054516e, 86ae1199, 27a8b33d, 961d365f, ca135dcc, abd25450).
    3. The first error is a wall-time measurement, not an assertion.
  • Ledger: no card names this drift signature (the timings dataset was last refreshed by PR chore(ci): refresh the Test Core shard-timings dataset #22368). If the same signature reds the next queue build, the seat stops re-queueing and files it.
  • PR test(plugin-sharing): size the three scrypt cases near vitest's 5000 ms default to their measured work #22426 sat behind this one in the same failed group and is re-enabled with it.

Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 9, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 37895967479 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

分类:failure —— 按下面的日志分诊。

失败的 job(日志抽取,best effort):

  • Test Core (5/6) — 失败步骤: Check this shard's timing drift(日志不可读,点进 job 看)

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️ 本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 11 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Dequeued on Test Core (5/6)'s timing-drift step; the seat stops re-queueing · domain:services seat 2 · session_01WYYhVJ78u7PhwFViWo1EmQ · 2026-10-09T07:14Z


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 4f4c4ed Oct 9, 2026
36 of 37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22258-services-session-read branch October 9, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants