Uh oh!
There was an error while loading. Please reload this page.
fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843
Conversation
…ovider-seam-sync-throw
… helper shifted rest-server.ts by 62 lines
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c532ef8ae66f8e514060ba4b4ee726382a39f407 && git checkout c532ef8ae66f8e514060ba4b4ee726382a39f407
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 86e765a7704eff4de2eb070330ff66a0e211c3aa 689001179d5d865db799ac6fa88cfb497b61391c && git checkout -B drift-repro 86e765a7704eff4de2eb070330ff66a0e211c3aa && git merge --no-ff 689001179d5d865db799ac6fa88cfb497b61391c
node scripts/docs-audit/affected-docs.mjs --json 86e765a7704eff4de2eb070330ff66a0e211c3aa
|
Uh oh!
There was an error while loading. Please reload this page.
…main The page is routed to `merge=os-regen` in .gitattributes, so the merge took one side whole with zero conflict markers — here, this branch's side — silently dropping main's own re-anchor after #13843 grew packages/rest/src/rest-server.ts. Regenerated with the gate's own --fix, which recomputes every anchor from the actual source positions. Verified rather than assumed: 65 table rows in, 65 out; with line numbers normalised the page is identical to main's apart from the objectql/src/engine.ts anchors; and every one of those shifts matches this branch's two insertion hunks exactly (+66 for sites between them, +75 for sites after the destroy() change). No row deleted, none reworded. Part of #13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L
…ops draining /ready (objectstack-ai#13829) * fix(objectql,service-datasource): give the driver registry an eviction door, so a deleted datasource stops draining /ready (objectstack-ai#13578) The ObjectQL driver registry had a `registerDriver` door and no counterpart, so nothing could ever leave it. `DELETE /api/v1/datasources/:name` emptied the admin door while `GET /api/v1/ready` kept naming the deleted datasource's driver — the probe reports whatever `checkDriversHealth()` finds in that registry — leaving a process restart on every replica as the only recovery. `IObjectQLEngine` gains `unregisterDriver(name)`. The registry owns the invariant rather than each caller, because removal moves three pieces of private engine state that a caller can reach none of: the `drivers` map, the `defaultDriver` NAME (a stale one answers with a driver that is gone), and the datasource def, which has no removal door of its own. Wired into the three lifecycle paths that already funnel through teardown: datasource delete / pool teardown, failed-start rollback, and engine destroy. Eviction is per-replica, symmetric with how registration already works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * test(service-datasource): take the ConnectionEngineLike roster pin from seven members to eight `unregisterDriver` widens the seam the datasource connection service drives the engine through, and the roster pin exists so that widening is a decision written down rather than a side effect of editing the type. Restated deliberately, with a return-type pin: the eviction door answers `boolean` so an idempotent caller can tell a removal from a no-op. Part of objectstack-ai#13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * docs(permissions): re-anchor the system-context census after the engine.ts insertion Pure line rot: `unregisterDriver` lands above every cited elevation-read site in packages/objectql/src/engine.ts, shifting all 11 anchors by the method's length. Rewritten by the gate's own `--fix`; no census row's meaning changes. Part of objectstack-ai#13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * docs(permissions): re-anchor the system-context census after merging main The page is routed to `merge=os-regen` in .gitattributes, so the merge took one side whole with zero conflict markers — here, this branch's side — silently dropping main's own re-anchor after objectstack-ai#13843 grew packages/rest/src/rest-server.ts. Regenerated with the gate's own --fix, which recomputes every anchor from the actual source positions. Verified rather than assumed: 65 table rows in, 65 out; with line numbers normalised the page is identical to main's apart from the objectql/src/engine.ts anchors; and every one of those shifts matches this branch's two insertion hunks exactly (+66 for sites between them, +75 for sites after the destroy() change). No row deleted, none reworded. Part of objectstack-ai#13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * chore(changeset): grade @objectstack/spec as minor with a BREAKING banner `IObjectQLEngine.unregisterDriver` is a REQUIRED member on a published interface: additive for consumers, compile-breaking for any third-party implementer. Regraded from patch to minor to match this contract's own precedent — the three prior changes to it all took minor, including one that added five members that were ALL optional and so broke nobody by construction. A required member grading below that is inconsistent. Banner shape verified against objectstack-ai#13870 rather than assumed: that changeset does pair a `minor` bump with a `**BREAKING**` line citing the launch-window convention. A strict-semver reading would say `major`; that reading is recorded as an open question for the maintainer in the PR body rather than acted on here, since uniform in-repo precedent is the operative convention and overruling it is not this PR's call. Part of objectstack-ai#13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * chore(changeset): answer the ADR-0087 question the BREAKING banner now raises Adding the banner turned `check-adr-0087-registration` red, correctly: a declared-breaking changeset must state in writing whether the change needs a ledger entry. Answered with the category built for this exact shape — a published runtime TypeScript interface with no metadata surface behind it, so `objectstack migrate meta` has nothing to rewrite and the compiler is the channel that reaches consumers. Part of objectstack-ai#13578 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * docs(permissions): regenerate the isSystem census after merging main Discharges the `os-regen` merge-driver deferral recorded for `content/docs/permissions/system-context.mdx` by the preceding merge commit. The driver does not text-merge this page, and it kept the branch side whole. That side is correct for this branch's `engine.ts` insertions but stale for everything main landed since the branch was cut, and it silently dropped main's own contribution to the page: an 18-line block explaining what the enforced-declarations row counts, and that row's value (21 -> 22). So the page is rebased on main's version and re-anchored by the gate's own repair (`node scripts/check-system-context-census.mjs --fix`), which rewrote 11 anchors, all of them `objectql/src/engine.ts` line shifts caused by this branch. No census row was added, deleted or re-worded; the totals are unchanged from main's own green run. check-system-context-census: OK - 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L * docs(permissions): re-anchor the isSystem census after the second main merge Discharges the `os-regen` deferral recorded by the preceding merge commit. Main's side of the page carried no prose or count change this time — its whole delta was line anchors moved by objectstack-ai#13910 in `packages/rest`. So the gate's own repair re-derives them: 10 anchors rewritten, every one a `rest-server.ts` shift. No census row added, deleted or re-worded. check-system-context-census: OK - 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3jdziLbAPGeceVNmSox5L --------- Co-authored-by: zhuangjianguo <zhuangjianguo@steedos.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Fixes#13280
At
RestServer.computeExecCtx, a host-wired provider that throws synchronously discarded the entire execution context — identity included — while the same provider failing as a rejected promise was absorbed and the caller was served. One fault, two wire answers, chosen by whether the host happened to spell its providerasync.The mechanism
The seams read
provider(environmentId).catch(() => undefined). That handler is attached to the promise the call returns, so it can only ever see a rejection. A provider that throws before returning a promise — an ordinary non-asyncfunction, which the seam's own declared type (taking an optional environment id and returning a promise) cannot stop a host from wiring — throws while the expression is still being evaluated. There is no promise to attach to, the.catchis never reached, and the throw lands incomputeExecCtx's outercatch, which returns no context at all.Measured, before and after
Real
RestServer, realregisterPackageRouteswired the wayrest-api-plugin.tswires it. Both callers hold a valid session and identical grants; the rows differ only in how the provider fails. The "before" column was re-measured by ablating this change on disk at this same commit, not quoted from the card.settingsServiceProvidersettingsServiceProviderobjectQLProviderobjectQLProviderauthServiceProviderauthServiceProviderTwo findings beyond the card:
objectQLProviderdiverged too (403 vs 401) and was not in the card's table; andauthServiceProvider, though mechanically asymmetric, was never observably so — an absorbed auth provider yields nothing and the next line already returns no context.What was normalised, and what deliberately was not
Normalised — the six seams inside
computeExecCtxthat call-then-.catch, now routed through oneseamOrUndefinedhelper that invokes the provider inside atry: bothkernel.getServiceAsync('auth')sites,authServiceProvider,kernel.getServiceAsync('objectql'),objectQLProvider,settingsServiceProvider.Deliberately NOT touched:
kernelManager.getOrCreate()andauthService.getApi()— these carry no seam.catchat all, so they lose the context in both directions. They are already symmetric; giving them a swallow would be a new policy, not a normalisation.computeExecCtx's outercatch. Whether a post-identity fault should discard identity is the second of the two directions the finding recorded — a behaviour change on a public door, of the class [finding] resolveExecCtx 的.catch(() => undefined)把执行上下文解析失败静默降级为「无上下文」— 该行为在包管理门上可达什么错误状态,未测 #13255 deferred, and still unruled. This change is decision-independent: under any answer to that question, one fault yielding 200 or 401 depending on how the host spelled its provider is a defect.rest-server.ts— other doors, out of this card's scope.The branded permission-store outage is unaffected and pinned as such:
AuthzStoreUnavailableErrorhas exactly one construction site (tryFind, inresolve-authz-context.ts), reached fromresolveAuthzContext— downstream of every seam here — so no normalised seam sits on its path.Tests
packages/rest/src/package-door-execctx-fault-reachability.test.tssection 7 is inverted in place, not re-baselined: it asserted the divergence, and the superseded text is quoted beside the new assertions. It now pins agreement and the agreed value per seam, plus a guard that the three seams do not agree with each other (200 / 403 / 401) — so a future blanket-swallow regression, where every seam degrades to a served 200, fails this section instead of passing it.SETTINGS_PROVIDER_SYNC_THROWis consequently gone from the section-2 class table — it is no longer a context-lost class — with a block in its place explaining why re-adding it would assert that a repaired seam is still broken.pnpm --filter @objectstack/rest test— 164 files, 2761 tests passed, at the final head689001179d.pnpm --filter @objectstack/rest typecheck— green;check:test-typecheckOK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).rest-server.tsto its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4tob58d2056) and by theseamOrUndefinedcount going 7 to 0; restore proven by hash equality, emptygit diff HEADand cleangit status --porcelain.PACKAGE_DELETE_FAILEDfrom its stub), measured identically before and after this change — not something this change introduced.Census
packages/rest/src/execctx-consumer-census.test.tsdid not move: its counts (75 sites, 95 mentions, 16/22/53) are unchanged and the file passes untouched.A different census did move —
content/docs/permissions/system-context.mdx, the generated anchor table. The helper added a net +62 lines torest-server.ts, rotting 10 line anchors. Regenerated withcheck-system-context-census.mjs --fix, never hand-edited: flagged 16, rewrote 10 anchors across 4 lines. Rewritten is fewer than flagged, so the over-rewrite mode #13646 measured did not occur. Control: with all digits stripped, the file is byte-identical before and after, and every one of the 10 anchors moved by exactly +62 — matching the net line delta. Row count unchanged at 387.Clause-②: no
Measured, not asserted:
@objectstack/restwas built twice at the same commit, with and without this change.dist/index.d.tsis byte-identical (md5ecb74c8a...both ways);dist/index.jsdiffers (md5279edd63vsa563b087), which is the control proving the rebuild picked the change up.seamOrUndefinedappears 0 times in the.d.tsand 6 in the.js— it is module-private and never published. No schema changes;packages/specis untouched.This is a runtime behaviour change at a permission door with no published-contract-surface change, which the maintainer ruling of 2026-08-28 places outside Clause ② by its negative boundary.
Changeset
patch, for@objectstack/rest. A defect repair with no surface change: nothing exported is added, removed or renamed, no authorable key or schema moves, and the published type surface is byte-identical. Notminor(no new capability); not breaking (nothing removed or renamed, and no host can reasonably have depended on a settings outage revoking its callers' identity). The changeset states the wire-behaviour change explicitly so it reaches the release notes.Gate reconciliation
Union derived by
scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsafter mergingorigin/mainand after the last edit, harvested at head689001179d. Exit codes captured before any pipe.union named 59, ran 59, unreconciled 0 — from
comm -23on the sorted lists, exact comparison.54 green. Four are NOT MEASURED, in each gate's own words — none is a red, and none is caused by this diff:
check-test-completeness(exit 3) — "PREREQUISITE NOT MET — this gate grades a savedturbo run testlog, and no log was named… running the family locally, record this gate as NOT MEASURED."check:dual-build-cjs-loads(exit 3) — "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/… ⛔ This is NOT a pass: nothing was measured."check:type-check-debt(exit 1) — refuses rather than measures: "--re-measurecannot run: 25 workspace dependencies of the ledgered packages have no built type entry point on disk… measuring now would not fail, it would silently measure a DIFFERENT WORLD." Narrowing declared, with the positive control that the substance for the one package this diff touches was measured:@objectstack/rest's owntypecheckandcheck:test-typecheckare green with the ledger entry unchanged, andcheck:type-check-coverage(the non-re-measure half) is green. No TEST_DEBT entry was raised; no ledger, debt or baseline file is touched.check-system-context-census(exit 1) — the anchor rot above; now green after regeneration, re-run on the final head.check:skill-examplesinitially refused for an unbuiltclient-reactdist ("a FALSE GREEN on exactly the change it exists to catch"); the closure was built and it is now genuinely green — 260 prose examples type-check across 3 surfaces.Ratchet family re-run on the final head after the last commit:
check:type-check-coverage,check:published-files,check:test-source-alias,check-system-context-census, and rest'stypecheck— all green.CI convergence is left to the reviewing PM per the dispatch contract; this PR is reported at draft time.
Generated by Claude Code
Generated by Claude Code