fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context - #13843

Merged
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw
Aug 31, 2026
Merged

fix(rest): a provider seam that throws synchronously no longer discards the whole execution context#13843
os-steve merged 5 commits into
mainfrom
claude/issue-13280-provider-seam-sync-throw

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

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 provider async.

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-async function, 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 .catch is never reached, and the throw lands in computeExecCtx's outer catch, which returns no context at all.

Measured, before and after

Real RestServer, real registerPackageRoutes wired the way rest-api-plugin.ts wires 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.

seamfails asbeforeafter
settingsServiceProviderrejecting promise200200
settingsServiceProvidersynchronous throw401 UNAUTHENTICATED200
objectQLProviderrejecting promise403403
objectQLProvidersynchronous throw401 UNAUTHENTICATED403
authServiceProviderrejecting promise401401
authServiceProvidersynchronous throw401401

Two findings beyond the card: objectQLProvider diverged too (403 vs 401) and was not in the card's table; and authServiceProvider, 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 computeExecCtx that call-then-.catch, now routed through one seamOrUndefined helper that invokes the provider inside a try: both kernel.getServiceAsync('auth') sites, authServiceProvider, kernel.getServiceAsync('objectql'), objectQLProvider, settingsServiceProvider.

Deliberately NOT touched:

The branded permission-store outage is unaffected and pinned as such: AuthzStoreUnavailableError has exactly one construction site (tryFind, in resolve-authz-context.ts), reached from resolveAuthzContext — downstream of every seam here — so no normalised seam sits on its path.

Tests

packages/rest/src/package-door-execctx-fault-reachability.test.ts section 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_THROW is 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 test164 files, 2761 tests passed, at the final head 689001179d.
  • pnpm --filter @objectstack/rest typecheck — green; check:test-typecheck OK, test-layer debt unchanged at 1 file / 2 errors / 1 pinned signature (shrink-only).
  • Ablation, direction predicted in writing first: reverting only rest-server.ts to its pre-change bytes turns exactly the 5 predicted pins red and nothing else. Mutation proven on disk by blob hash (c600acc4 to b58d2056) and by the seamOrUndefined count going 7 to 0; restore proven by hash equality, empty git diff HEAD and clean git status --porcelain.
  • The 500 that appears in the write cohort once identity is restored is the harness's own healthy answer (PACKAGE_DELETE_FAILED from its stub), measured identically before and after this change — not something this change introduced.

Census

packages/rest/src/execctx-consumer-census.test.ts did 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 to rest-server.ts, rotting 10 line anchors. Regenerated with check-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/rest was built twice at the same commit, with and without this change. dist/index.d.ts is byte-identical (md5 ecb74c8a... both ways); dist/index.jsdiffers (md5 279edd63 vs a563b087), which is the control proving the rebuild picked the change up. seamOrUndefined appears 0 times in the .d.ts and 6 in the .js — it is module-private and never published. No schema changes; packages/spec is 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. Not minor (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 --commands after merging origin/main and after the last edit, harvested at head 689001179d. Exit codes captured before any pipe.

union named 59, ran 59, unreconciled 0 — from comm -23 on 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 saved turbo run test log, 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-measure cannot 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 own typecheck and check:test-typecheck are green with the ledger entry unchanged, and check: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-examples initially refused for an unbuilt client-react dist ("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's typecheck — 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

@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 4 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/api/environment-routing.mdx(via packages.list (sdk, the route ledger binds it to GET /api/v1/packages))
  • content/docs/kernel/contracts/metadata-service.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/kernel/services-checklist.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/permissions/permission-sets.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx(via /api/v1/packages (route, a path literal on a changed line))
  • content/docs/ui/apps.mdx(via /api/v1/packages (route, a path literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via /api/v1/packages (route, a path literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 13 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 86e765a7704eff4de2eb070330ff66a0e211c3aapackageMentionDocs.

Which tree this was computed on

This run read content/docs from c532ef8ae66f8e514060ba4b4ee726382a39f407 — the merge of head 689001179d5d865db799ac6fa88cfb497b61391c into base 86e765a7704eff4de2eb070330ff66a0e211c3aa, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 86e765a7704eff4de2eb070330ff66a0e211c3aa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-steve
os-steve marked this pull request as ready for review August 31, 2026 15:06
@os-steve
os-steve enabled auto-merge August 31, 2026 15:06
@os-steve
os-steve added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit add6a1bAug 31, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-13280-provider-seam-sync-throw branch August 31, 2026 15:30
zhuangjianguo added a commit that referenced this pull request Aug 31, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 1, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-steve@claude