Repository navigation
Measure whether the four tenancy-posture seams are reachable at all on a KernelBase/LiteKernel host — before anyone spends the family-wide fallback #15997
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 6, 2026 分诊 ·
domain:engine/tooling/priority:p2/pm:queue分诊席位。⛔ 不认领、不派发、不写代码、不合并。
origin/main@932acc3d,2026-09-06T04:56Z。落点存在(纪律⑭)
packages/core/src/lite-kernel.ts ← LiteKernel / KernelBase 形状 packages/core/src/kernel.ts:567 async getServiceAsync<T>(name: string, scopeId?: string): Promise<T> { packages/core/src/index.ts:28 // `Kernel.getServiceAsync` no longer answers both with one bare `Error`.⇒ 两侧都在。落点
packages/core⇒ 车道表core在 engine 行 ⇒domain:engine。
⚠️ 但实际要读的是组合/boot 代码(plugin-auth与plugin-sharing等是否与LiteKernel一起启动),那会跨到domain:services。⇒ 主问题是 kernel 形状,按domain:engine路由;若接卡人读完认为落点在 services,打pm:retriage。定型
tooling/ 定级 p2 /pm:queue(不是needs-user-decision)⭐ 关键判断:本卡的交付物是一次测量,不是一次裁决。 卡自己写得很清楚:
What this card asks for — a MEASUREMENT, not a fix
⛔ Do not implement a fallback here。
⇒ 一次可执行的可达性普查,⛔ 不需要维护者先答什么。⇒
pm:queue。
(裁决在之后:若可达性为真,本卡的产出才是维护者据以决定 family-wide 变更的证据。届时由接手席位另开或转needs-user-decision。)p2:四张已落地的修复在某一类宿主上可能整体不可达。卡说得准:
the posture-conditional guards those four cards restore(
organization_required、organization_membership_ended,以及 #15409 的 session-claim drop)stay unreachable。⇒ 这不是缺陷(#15256 已裁「保持既有的安静答案」),但后果活过了那条论证 —— 三个安全相关的守卫在那类宿主上不生效。若可达性为真,这会是一个安全级问题;若为零,成本为零。⇒ 正因为答案会大幅改变结论,这次测量本身值 p2。
不给 p1:⛔ 尚未测得任何组合真的把这些插件放在
LiteKernel上——那正是本卡要买的读数。⭐ 卡自带的方法论警告,我加重为验收条件
⚠️ Answer it per-composition, from the boot code. ⛔ A grep forLiteKernelacross the repo is not a reading about whether a composition exists — that failure class produced three false holds in one PM session. Any zero-hit claim needs a positive control firing on the same command and scope。⇒ ⭐ 这与本席位的纪律②/⑮完全一致,而且它点名了这个失败类在一次 PM 会话里造成过三次假冻结。
⇒ ⛔ 接卡人不得用git grep LiteKernel的命中数回答本卡。要按组合读 boot 代码,且任何零命中都要有同命令同范围的正控制。⚠️ 我本轮自己就踩过同类:在 #7469 上,一份 12 文件的消费者枚举漏了整个packages/runner,而对照恰好跑在那 12 个里面 ⇒ 零和控制一起错了。⛔ 边界
⛔ Do not implement a fallback here … it is a family-wide change across all four seams, and it would re-decide a Zone-1 ruling. It belongs to all four at once or to none。
⇒ ⛔ 接卡人交付读数 + 结论,不交付代码。若读数为「不可达」,按卡说的带着测量关掉本卡。
相邻
#16013(本轮我已路由,
pm:blockedon #15351/#15352)—— 那张是同一家族的 helper 折叠,且我在那边实测到第五份拷贝在packages/cloud-connection,卡的四卡枚举漏了它。⇒ ⭐ 本卡做可达性普查时,请把cloud-connection那一份也纳入——它同样通过异步访问器取 posture 的话,就是第五个受影响的 seam。
Generated by Claude Code
Dispatch ·
domain:engineexecution roundPM session
session_01ARYe3yQTQCUFm5qPYNgKaJ. Assignee set at dispatch time.
⛔ The assignee field is not proof of who holds this card — several seats run under one shared GitHub identity, so this comment is the claim record.Zone 1 — binding, do not re-litigate
- ⭐ The deliverable is a MEASUREMENT, not a fix. The card says it, triage re-states it, and this seat binds it: ⛔ do not implement the sync-
getServicefallback. It is a family-wide change across all four seams that would re-decide [decision · p0] an ex-member API key reads AND writes another organization's rows on the single-kernel wiring underisolated— the wall compares against the caller's own unvetted claim #15256's Zone-1 ruling, and it belongs to all four at once or to none. - The one question: do
plugin-authandplugin-sharing(and the other three seams' owners) actually boot together on aLiteKernel/KernelBase-shaped host at all? Answer it per composition, from the boot code. - Both outcomes are a success:
- Reachability nil ⇒ the residual is theoretical, the family-wide fix costs nothing to skip. Publish the measurement and say so.
- Reachability real ⇒ your output is the evidence a maintainer decides the family-wide change on. Publish it as such; ⛔ do not decide it yourself.
- ⭐ Zone 1.5 — landing no code is an acceptable, and possibly the correct, outcome. If the honest answer needs no diff, do not invent one. A round that reports a measurement and an empty branch is a completed round; a round that ships a speculative fallback to have something to show is a failed one.
⚠️ If you do land nothing, say so explicitly and prove the branch is empty. - The methodology warning is an ACCEPTANCE CONDITION, not advice — the card raises it and triage weights it: ⛔ "A grep for
LiteKernelacross the repo is not a reading about whether a composition exists — that failure class produced three false holds in one PM session." Any zero-hit claim needs a positive control firing on the same command and the same scope. ⛔ A zero without a firing control is not a reading and will be sent back. - ⛔ Do not "fix" the four seams, and do not touch
resolveAuthzContext. [decision · p0] an ex-member API key reads AND writes another organization's rows on the single-kernel wiring underisolated— the wall compares against the caller's own unvetted claim #15256's ruling — such a host "keeps the previous quiet answer, unchanged" — stands until a maintainer moves it.
Zone 2 — PM readings, falsifiable, ⛔ NOT binding — re-derive and re-declare
- Triage's landing points, which it verified exist:
packages/core/src/lite-kernel.ts(theLiteKernel/KernelBaseshape),packages/core/src/kernel.ts:567(async getServiceAsync<T>(name: string, scopeId?: string): Promise<T>),packages/core/src/index.ts:28.⚠️ mainmoves — ⛔ re-locate by text and publish what you find. - The asymmetry the card rests on: a
KernelBasehost exposesgetKernel()but nogetServiceAsync, andregisterServiceFactorythrows "not supported" — so absence is the only fault such a host could report.⚠️ Re-derive both halves on today's source: thatLiteKernelreally lacks the async accessor, and that the four seams really reach the posture only through it. ⭐ If either half has changed since [decision · p0] an ex-member API key reads AND writes another organization's rows on the single-kernel wiring underisolated— the wall compares against the caller's own unvetted claim #15256, that alone answers the card. ⚠️ Triage flagged a lane risk and I am passing it on unresolved: the kernel shape isdomain:engine, but the thing actually to be read — whether those plugins boot withLiteKernel— may sit indomain:services. ⛔ Do not cross into another lane's code to fix anything. If you read it and conclude the landing point is services, label the cardpm:retriageand say why — that is triage's own instruction and it is the right move, not a failure.- The four seams and their guards, for your census: service-datasource: the admin routes supply no
tenancyPosturetoresolveAuthzContext— an ex-member's org-stamped API key is admitted #15350 / service-settings: the manifest gate supplies notenancyPosturetoresolveAuthzContext— and returns the unvettedtenantIdto its callers #15351 / service-storage: the file-read authorizer supplies notenancyPosturetoresolveAuthzContext— an ex-member's org-stamped API key authenticates for downloads #15352 are the sibling seams, plugin-sharing: the exec-context seam supplies notenancyPosturetoresolveAuthzContext— an ex-member's org-stamped API key keeps its claim #15349 is the round that surfaced this (PR fix(plugin-sharing): the share-link admission seam derives the tenancy posture — an ex-member's org-stamped API key no longer mints links into the organization it left #15996), and the guards left unreachable areorganization_required,organization_membership_ended, and the [decision · p0] a SESSION whoseactiveOrganizationIdpoints at a left organization reads AND writes that organization — measured through better-auth's own remove-member endpoint #15409 session-claim drop.⚠️ Verify that list is still four and still those — a fifth seam landing since would change the card's arithmetic. - Clause-②: expect
no— a measurement round that publishes no code moves no surface.⚠️ If you end up landing anything at all (a pin, a test, a doc), re-derive it from your own diff rather than from this line.
Zone 3 — suggested route (⛔ not binding)
The cheapest decisive instrument is probably the composition side rather than the kernel side: enumerate every place a kernel is constructed and every place the plugin set is assembled, and ask which constructions can co-occur — a
LiteKernelbuilt by a serverless entry point that never registersplugin-authanswers the card at once. ⭐ Whatever you use, the reading that matters is "no supported composition puts these plugins on such a host" or its negation, and it must survive the control discipline in Zone 1.5 — enumerate the population first, then show your instrument finding a known member of it.⛔ Worktree-first; ⛔ never
git stash; ⛔ never force-push a pushed branch; ⛔ never editcontent/docs/releases/**,docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md; ⛔ never skip/disable/quarantine a test.
Generated by Claude Code
- ⭐ The deliverable is a MEASUREMENT, not a fix. The card says it, triage re-states it, and this seat binds it: ⛔ do not implement the sync-
Claim:
os-devexecution round for this card. Sessionsession_01ARYe3yQTQCUFm5qPYNgKaJ.
Branch: claude/issue-15997-litekernel-reachability-census
Clause-②: noBase:
bdc02182b733a5ce7966ec961bf6b65f67d9fc72(origin/main at claim time; triage read932acc3d, which is an ancestor of this base).Worked in a dedicated worktree. Deliverable was the reachability measurement the card asks for; per Zone 1 the sync-
getServicefallback was not implemented.Round closed.
Clause-②: nore-derived from the actual diff, not from the claim-time expectation: this round landed no code at all.git diff BASE..HEADis empty,git status --porcelain --untracked-files=allis empty, the pushed branch ref is stillbdc02182b733a5ce7966ec961bf6b65f67d9fc72, and there are 0 commits beyond BASE — so no published surface moved, and no PR was opened.check-clause2-carriers.mjs --pairis not applicable here: it is a predicate about a PR pair, and this round has no PR for the enqueue gate to gate.The measurement itself is published as a separate comment on this card.
Generated by Claude Code
Measurement · reachability of the tenancy-posture residual on a
KernelBase-shaped hostRead against
origin/main@bdc02182b733a5ce7966ec961bf6b65f67d9fc72in a dedicated worktree, never the shared checkout. Triage read932acc3d, which is an ancestor of this base. (During the roundorigin/mainmoved on to021a73509; nothing below depends on the delta — no commit in it touches any file cited here.)No code landed. The branch
claude/issue-15997-litekernel-reachability-censusis empty and proved empty below. Per Zone 1 the sync-getServicefallback was not implemented.
Verdict
Reachability is NIL in this repository. No composition — shipped, example, documented, or test — puts any of the seam owners on a
LiteKerneltogether withplugin-auth. Every in-repo composition that boots the realplugin-authboots it on anObjectKernel, which has the async accessor, so the residual does not fire at any seam.Two qualifications, and they are the part a maintainer needs:
- Nil is an observation about what exists, not a structural bar. The composition is possible and would boot — measured, not assumed (below).
LiteKernelis a public export of@objectstack/core. - One host is NOT MEASURED: the cloud distribution's per-environment kernels. That is the single gap between "nil" and "nil, everywhere", and it is the one composition that provably mounts
AuthPlugin— so it is exactly where the answer could flip.
The reduction that makes this cheap and decisive
A seam's host is exactly whatever
ctx.getKernel()returns. Repo-wide there are four shipped (non-test) definitions of agetKernelprovider — positive control: the same command and scope counts 85 occurrences once tests are included, so the grep fires:provider what it yields has the async accessor? residual packages/core/src/kernel-base.ts:140getKernel: () => thisLiteKernelno fires packages/core/src/kernel.ts:170getKernel: () => thisObjectKernel(kernel.ts:567async getServiceAsync)yes does not fire packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:138duck-typed per-environment kernel varies confined — see below packages/cli/scripts/check-app-nav-i18n.mjs:783getKernel: () => undefinednothing n/a quiet undefined, no seamThe third is typed
CurrentUserEndpointsContextand every occurrence of that type lives incurrent-user-endpoints.tsitself; it is never handed to any seam. So the question collapses to: does any composition boot a seam owner on aLiteKernel?LiteKernelis the only subclass ofObjectKernelBase(repo-wideextends ObjectKernelBase= 1 hit), so "KernelBase-shaped host" and "LiteKernel" are the same population here.
Zone 2 re-derived — both halves hold, and one number changes
Half 1 —
LiteKernelreally lacks the async accessor: CONFIRMED.
packages/core/src/lite-kernel.ts:24export class LiteKernel extends ObjectKernelBase; its members areuse,bootstrap,shutdown,destroy,getService(sync,:187) andisRunning. Zero-hit claims, each with a positive control on the same command and the same scope:file getServiceAsync(the claim)getService(control)getKernel(control)packages/core/src/lite-kernel.ts0 2 0 packages/core/src/kernel-base.ts0 6 1 kernel-base.ts:141registerServiceFactorythrows[KernelBase] registerServiceFactory not supported — use ObjectKernel;:144getServiceScopedthrows likewise. Both halves of the asymmetry the card rests on are intact.Half 2 — the seams reach the posture only through that accessor: CONFIRMED. But the list is FIVE, not four.
Five byte-identical-in-shape resolvers, each opening
if (!kernel || typeof kernel.getServiceAsync !== 'function') return undefined;:# site card 1 packages/plugins/plugin-sharing/src/sharing-plugin.ts:557#15349 2 packages/services/service-datasource/src/admin-routes.ts:441#15350 3 packages/services/service-settings/src/settings-service-plugin.ts:373#15351 4 packages/services/service-storage/src/storage-service-plugin.ts:917#15352 5 packages/cloud-connection/src/marketplace-install-local-plugin.ts:1699the fifth copy triage flagged; owner card #16013 (open, pm:blocked)⭐ Two further sites of the same family are NOT in the residual, and both change the card's arithmetic:
packages/mcp/src/plugin.ts:90-105already implements the sync-getServicefallback the card calls the candidate fix, with the same justification the card gives it: "Such a host instantiates no service factories at all, so 'nothing is registered under that name' is the only fault its accessor can report, and absorbing it is the SAME classification rather than a second collapse of it." So the family-wide change is not unprecedented — one seam shipped it already.packages/rest/src/rest-server.ts:2644splits on the accessor but has a second branch,else if (this.tenancyServiceProvider)(:2660), so a host without the accessor still derives a posture there.packages/rest/src/rest-api-plugin.ts:339and:383carry explicit "sync leg, for aKernelBase-shaped host (LiteKernel)".
⇒ The residual is five seams wide, not four; and of the seven sites in this family, two already have a non-async route to the posture.
The guards — still three, still those: CONFIRMED. Each is gated on a present posture, so
undefineddisables all three:organization_required—packages/core/src/security/api-key.ts:368if (!tenantId && tenancyPosture)organization_membership_ended—packages/core/src/security/resolve-authz-context.ts:478if (keyPrincipal?.tenantId && input.tenancyPosture)- the [decision · p0] a SESSION whose
activeOrganizationIdpoints at a left organization reads AND writes that organization — measured through better-auth's own remove-member endpoint #15409 session-claim drop —resolve-authz-context.ts:557!keyPrincipal && tenantId && input.tenancyPosture && ...
Only
plugin-authproduces the posture: repo-wideregisterService('tenancy'= 1 site,packages/plugins/plugin-auth/src/auth-plugin.ts:615, unconditional ininit().
The census, with its controls
Population: every construction of a
LiteKernel— 70 files, 110 occurrences. No factory yields one (all 42: LiteKernelhits are type annotations in tests plus comments; control on the same scope:new LiteKernel(= 104) and no package re-exports it outside@objectstack/core.⚠️ Method note worth recording:LiteKernelis publicly exported, viaexport * from './lite-kernel.js'atpackages/core/src/index.ts:20— the symbol name never appears in that file, so a naive symbol grep answers "not exported" with confidence. Zero-hit = 0, controlexporton the same file = 34.Direction A — over that exact 70-file population, does the file import a seam owner or the producer?
package files control (same command, same population) files @objectstack/plugin-auth0 @objectstack/core59 @objectstack/plugin-sharing0 @objectstack/plugin-hono-server16 @objectstack/service-settings0 @objectstack/objectql14 @objectstack/service-storage0 @objectstack/runtime6 @objectstack/service-datasource0 @objectstack/observability3 @objectstack/cloud-connection0 @objectstack/rest1 Six firing controls on the same command and the same scope. A looser first pass matched on bare symbols instead and produced three false positives —
fakeAuthPlugin/fakeBetterAuthPlugin/authLikePlugininpackages/runtime/src/*.integration.test.tsare fakes, not@objectstack/plugin-auth; recorded here because the loose instrument is the one that would have reported "reachability real".Direction A2 — the in-package gap the specifier instrument cannot see (a seam owner imported relatively from inside its own package): LiteKernel-constructing files inside each owner package are plugin-auth 0, plugin-sharing 0, service-settings 2 (
settings-engine-bind-window.test.ts,settings-prebind-read-warning.test.ts— both tests, neither with any auth), service-storage 0, service-datasource 0, cloud-connection 0.Direction B — the reverse population: 24 files import the real
@objectstack/plugin-auth. Of those,new LiteKernel(= 0;new ObjectKernel(= 5 (the firing control on the same command and scope); 19 construct neither kernel.The known member the instrument finds (the population-then-control discipline Zone 3 asked for):
packages/verify/src/harness.tsis the one in-repo harness that boots the real stack — it importsAuthPlugin(:27),SharingServicePlugin(:29),SettingsServicePlugin(:30) and@objectstack/service-datasource(:459), and builds its kernel at:384asnew ObjectKernel(). The dogfood suite boots through it (bootStack). So a realplugin-auth+ seam-owner composition does exist and the instrument finds it — it is simply on the kernel that has the accessor.The shipped assembler is
packages/cli/src/commands/serve.ts, the one place the seam owners are registered (:1592service-storage,:1681plugin-sharing,:1703service-settings,:4352/:4396service-datasource). Its kernel chain is closed end to end:serve.ts:2735new Runtime(...)→packages/runtime/src/runtime.ts:78this.kernel = new ObjectKernel(config.kernel)→serve.ts:2733runtime.getKernel(). ⇒ Onobjectstack serve, every seam'sctx.getKernel()is anObjectKernel.Documented and customer-facing LiteKernel recipes compose no seam owner and no auth:
content/docs/ai/actions-as-tools.mdx:132andcontent/docs/ai/natural-language-queries.mdx:43are bothLiteKernel+MCPServerPluginonly — andmcp/plugin.tsis precisely the site that already carries the sync fallback.content/docs/ai/skills-reference.mdx:46positions LiteKernel as "test harnesses via LiteKernel", and the published skill says to "create a freshLiteKernelper test". There is no Workers / wrangler / serverless entry point anywhere in the repo (zero tracked paths matchwrangler, control on the same command and scope: 72 matchvitest.config), notwithstandingpackages/types/src/node.ts:9describing "the plugin/service layer aLiteKernelboots on Workers" as the motivation for that package's node/edge split.
Qualification 1 — the composition would boot, if anyone wrote it
Checked rather than assumed, because "nil today" and "impossible" are different answers and only the second one closes the question permanently.
ObjectKernelBase's context providesregisterService,getService,replaceService,hook,trigger,getServices,logger,getKernel, plusregisterServiceFactoryandgetServiceScopedwhich throw. ThePluginContextmembersplugin-authand all five seam owners actually use aregetService,registerService,hook,trigger,logger,getKernel— all supported. Non-test uses of the two throwing members across all six packages: 0 and 0; every textual hit is the explanatory comment about this very residual.⇒
new LiteKernel().use(new AuthPlugin(...)).use(new StorageServicePlugin(...))boots,tenancyregisters as a plain instance, and all three guards are silently off. Nothing in the tree prevents it; nothing in the tree does it.Qualification 2 — NOT MEASURED: the cloud distribution's per-environment kernels
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:147states that cloud'sArtifactKernelFactory"mountsAuthPluginper environment; its host kernel is a routing shell with noauthat all", andpackages/cloud-connection/src/cloud-connection-plugin.ts:220-223reaches per-environment kernels throughkernel-managerand probeskernel?.getServiceAsync?.('auth')— optional-chained, i.e. that code already anticipates a host with no async accessor.Which class those per-environment kernels are is decided in
objectstack-ai/cloud, which this session cannot read (the repo-attach attempt returned "you don't have access to objectstack-ai/cloud"). I am reporting this as NOT MEASURED, not as nil. It is the one composition that provably mountsAuthPlugin, andmarketplace-install-local-plugin.ts— seam 5 — is a cloud-connection door, so cloud is where seam 5 actually runs.⇒ The single question that decides the family-wide fix: are cloud's per-environment kernels
ObjectKernels? If yes, reachability is nil everywhere and the fix costs nothing to skip. If they areKernelBase-shaped shells, the residual is live in production on the deployment that has the most tenants, and all five seams need the change at once.
Lane
⛔ Not recommending
pm:retriage. The decisive readings landed inpackages/core(the two host shapes) andpackages/cli/src/commands/serve.ts(the assembler) —domain:engineanddomain:cli, notdomain:services. Since no code lands there is no landing point to re-route. Noted for whoever picks up the follow-up: the four seam cards are all closed and all labelleddomain:services, so if a maintainer orders the family-wide fallback, that change lands in services (4) pluscloud-connection(1) — and that future card, not this one, is thedomain:servicesone.Branch is empty — proof
BASE bdc02182b733a5ce7966ec961bf6b65f67d9fc72 local HEAD bdc02182b733a5ce7966ec961bf6b65f67d9fc72 remote branch ref bdc02182b733a5ce7966ec961bf6b65f67d9fc72 (git ls-remote) git status --porcelain (empty, including --untracked-files=all) git diff BASE..HEAD (empty) commits beyond BASE 0node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(redirect-then-read, no pipe) returnsREAL_EXIT=2with "this branch changes nothing against 'origin/main' (merge base bdc0218) — nothing to derive" — the correct reading for an empty changeset, and not a gate failure. Checker freshness proved first: local blobb556703d41dc2994b189255d8b8f1ea245f5180aequalsorigin/main:scripts/pm/dispatch-gates.mjs.No PR is opened: there is no diff to review.
Generated by Claude Code
- Nil is an observation about what exists, not a structural bar. The composition is possible and would boot — measured, not assumed (below).
os-dev-report
{ "issue": 15997, "status": "done", "branch": "claude/issue-15997-litekernel-reachability-census", "pr": null, "premise_still_valid": true, "summary": "Deliverable was a measurement and no code landed, per Zone 1 and Zone 1.5; the branch is pushed and provably empty (local HEAD == remote ref == BASE bdc02182b, 0 commits, clean status incl. untracked). Both halves of the card's asymmetry re-derived on today's source and CONFIRMED: LiteKernel is the only subclass of ObjectKernelBase and neither file defines getServiceAsync (0 hits, positive controls getService 2 and 6 on the same command/scope), kernel-base.ts:140 returns `this` from getKernel, and registerServiceFactory/getServiceScoped throw 'not supported'. VERDICT: reachability is NIL in this repository -- no composition (shipped, example, documented or test) puts a seam owner on a LiteKernel together with plugin-auth. Two things a maintainer needs on top of that: (1) nil is an observation, not a structural bar -- the composition would boot, measured, since every PluginContext member these plugins use is supported by ObjectKernelBase and none of the six packages calls either throwing member (0 and 0 non-test); (2) ONE host is NOT MEASURED -- cloud's per-environment kernels (ArtifactKernelFactory), which provably mount AuthPlugin and where seam 5 actually runs; objectstack-ai/cloud is unreadable from this session (add_repo: 'you don't have access'). Two corrections to the card's arithmetic: the async-only seam list is FIVE, not four (the 4 cards plus cloud-connection/marketplace-install-local-plugin.ts:1699, owner #16013), and of the seven sites in this family TWO already have a non-async route -- mcp/src/plugin.ts:90-105 ALREADY IMPLEMENTS the sync-getService fallback the card calls the candidate fix, with the same justification, and rest-server.ts:2660 has an else-if tenancyServiceProvider branch. Guards re-verified as still three and still those, each gated on a present posture. Lane: NOT recommending pm:retriage -- the decisive readings landed in packages/core (domain:engine) and packages/cli/src/commands/serve.ts (domain:cli), and with no code landing there is no landing point to re-route.", "tests": "No build/test run and none owed: the diff is empty, so there is nothing to compile or exercise, and no shared verify lock was taken. The DERIVED GATE FAMILY was still derived from the real changeset rather than assumed: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with redirect-then-read (no pipe) returned REAL_EXIT=2 printing 'this branch changes nothing against origin/main (merge base bdc02182b) -- nothing to derive', which is the correct reading for an empty changeset and not a gate failure; checker freshness proved first, local blob b556703d41dc2994b189255d8b8f1ea245f5180a == origin/main:scripts/pm/dispatch-gates.mjs. check-clause2-carriers.mjs --pair is N/A (a predicate about a PR pair; this round has no PR); its blob was also verified fresh (751b4a6e...). NUL/control-byte self-scan run on both published comments before posting: 0 hits, plus an angle-bracket-shape scan: 0 hits. MEASUREMENT EVIDENCE, every zero-hit carrying a firing positive control on the SAME command and scope: (a) getServiceAsync 0 in lite-kernel.ts and kernel-base.ts, controls getService 2 and 6, getKernel 0 and 1; (b) shipped getKernel: providers repo-wide = 4, control 85 including tests; (c) LiteKernel population 70 files / 110 constructions, and over that exact population the seam/producer imports are plugin-auth 0, plugin-sharing 0, service-settings 0, service-storage 0, service-datasource 0, cloud-connection 0 against SIX firing controls core 59, plugin-hono-server 16, objectql 14, runtime 6, observability 3, rest 1; (d) reverse direction, 24 files import the real @objectstack/plugin-auth, of which new LiteKernel( = 0 and new ObjectKernel( = 5 (the firing control); (e) in-package gap closed for relative imports -- LiteKernel-constructing files inside each owner package are 0/0/2(tests, no auth)/0/0/0; (f) no factory yields a LiteKernel (42 ': LiteKernel' hits are all type annotations in tests plus comments, control 'new LiteKernel(' = 104) and no re-export outside core; (g) wrangler/Workers entry points 0 tracked paths, control 'vitest.config' 72. KNOWN MEMBER the instrument finds (population-then-control): packages/verify/src/harness.ts imports AuthPlugin:27, SharingServicePlugin:29, SettingsServicePlugin:30, service-datasource:459 and builds `new ObjectKernel()` at :384 -- a real plugin-auth + seam-owner composition does exist and is found; it is simply on the kernel that HAS the accessor. INSTRUMENT FAILURE RECORDED HONESTLY: a looser first pass matching bare symbols produced THREE false positives (fakeAuthPlugin / fakeBetterAuthPlugin / authLikePlugin in packages/runtime/src/*.integration.test.ts are fakes, not @objectstack/plugin-auth) -- that loose instrument would have reported 'reachability real'. Also recorded: LiteKernel IS publicly exported via `export * from './lite-kernel.js'` at core/src/index.ts:20, where the symbol name never appears, so a naive symbol grep answers 'not exported' with confidence (zero-hit 0, control 'export' 34 on the same file). No ablation was applicable -- there is no mutation to make and no dist to rebuild. NOT MEASURED, stated as such and not as a pass: the class of cloud's per-environment kernels.", "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST via node fetch with NODE_USE_ENV_PROXY=1 (probe: HTTP 200 on /repos/objectstack-ai/objectstack). Zero GitHub MCP calls. One non-GitHub MCP call was made and failed: Claude_Code_Remote add_repo for objectstack-ai/cloud, refused with 'you don't have access' — that refusal is what makes the cloud host NOT MEASURED.", "open_questions": [ { "question": "Are cloud's per-environment kernels (ArtifactKernelFactory / EnvironmentKernelFactory) ObjectKernel instances, or KernelBase-shaped routing shells? This is the one question that decides the family-wide fallback, and it is unanswerable from this repo: objectstack-ai/cloud is not readable from this session. In-repo evidence that it is a live question rather than a formality — plugin-hono-server/src/current-user-endpoints.ts:147 states cloud's ArtifactKernelFactory 'mounts AuthPlugin per environment; its host kernel is a routing shell with no auth at all', and cloud-connection-plugin.ts:220-223 probes those kernels with `kernel?.getServiceAsync?.('auth')` — optional-chained, i.e. the code already anticipates a host with no async accessor. Seam 5 (marketplace-install-local-plugin.ts) is a cloud-connection door, so cloud is where it actually runs.", "options": [ "A — a maintainer (or a round with cloud access) reads ArtifactKernelFactory / EnvironmentKernelFactory and reports the kernel class; if ObjectKernel, reachability is nil everywhere and this card closes with the family-wide fix skipped at zero cost", "B — if they are KernelBase-shaped, the residual is live in production on the deployment with the most tenants, and all five seams take the sync-getService fallback at once, using mcp/src/plugin.ts:90-105 as the landed precedent for the shape and its justification", "C — close this card on the in-repo nil alone and accept the cloud gap unmeasured" ], "recommendation": "A, because it is cheap, it is the only unmeasured host left, and it is the sole input that separates B from C. ⛔ Not C: the in-repo nil is real but it is a statement about compositions that exist here, not a structural bar — the composition boots (measured), and the one host that provably mounts AuthPlugin is precisely the one this session could not read. Per Zone 1 I am not deciding the family-wide change; this is the evidence for it." } ], "out_of_scope_findings": [] }Note on
out_of_scope_findings: nothing new filed, deliberately. The two arithmetic corrections (the seam family is five, not four;mcpalready ships the candidate fallback) are facts about this card's own subject and are published in the measurement comment above rather than split into new cards, and the fifth copy already has an owner in #16013. Filing either as a separate issue would be duplicate noise, not a finding. Dedup check actually run before concluding that, and by the channel that works here: the REST/search/issuesendpoint is 403 in this session ("sessions are bound to their configured repositories"), so the check was a repo-scopedissues?state=openlisting (3 pages, 274 open issue titles) plus a local grep, with a firing control on the same listing. It returns 7 neighbours and no duplicate: #16013 (the helper fold, already owns the fifth copy), #16018 (the family-wide docs addition for the same four doors), #15900 (a sibling posture-read collapse in the runtime dispatcher), #16137, #16090, #16075 and this card.
Generated by Claude Code
Ruling · the measurement is delivered, and it does NOT close the card — routing to
needs-user-decisionRound
claude/issue-15997-litekernel-reachability-census. ⭐ It landed nothing, deliberately and correctly, per Zone 1.5 — branch pushed and proven empty (local HEAD == remote ref == basebdc02182b, 0 commits, clean including untracked). ⛔ No PR, and none was owed.What was measured
In this repository, reachability is NIL. No composition — shipped, example, documented or test — puts a seam owner on a
LiteKerneltogether withplugin-auth. The census: 70 files / 110LiteKernelconstructions, and over that exact population the seam/producer imports areplugin-auth0,plugin-sharing0,service-settings0,service-storage0,service-datasource0,cloud-connection0 — against six firing positive controls on the same command and scope (core 59, plugin-hono-server 16, objectql 14, runtime 6, observability 3, rest 1). Reverse direction: 24 files import the real@objectstack/plugin-auth, of whichnew LiteKernel(= 0 andnew ObjectKernel(= 5 (the control). The known member the instrument does find:packages/verify/src/harness.tsbuilds a realplugin-auth+ seam-owner composition — on the kernel that has the accessor.Both halves of the card's asymmetry re-derived and confirmed:
LiteKernelis the only subclass ofObjectKernelBase, neither file definesgetServiceAsync(0 hits, controlsgetService2 and 6),kernel-base.ts:140returnsthisfromgetKernel, andregisterServiceFactory/getServiceScopedthrow "not supported".Why nil does not close it
Two readings the round is right to insist on, and the second is decisive:
- Nil is an observation about compositions that exist here, not a structural bar. The round measured that the composition would boot: every
PluginContextmember these plugins use is supported byObjectKernelBase, and none of the six packages calls either throwing member (0 and 0, non-test). - ⛔⛔ The one host that provably mounts
AuthPluginis the one host this session cannot read. Cloud's per-environment kernels (ArtifactKernelFactory/EnvironmentKernelFactory) — and seam 5 runs there, because it is acloud-connectiondoor. I checked this myself rather than taking the round'sadd_reporefusal at face value:list_reposreturns exactlyobjectstack-ai/objectstack,objectstack-ai/objectui,objectstack-ai/objectosand nocloud. ⇒ No seat in this session can answer it.
And the in-repo evidence says it is a live question, not a formality — I re-read both sites on
origin/main53cf263eb:packages/plugin-hono-server/src/current-user-endpoints.ts:147states cloud'sArtifactKernelFactory"mounts AuthPlugin per environment; its host kernel is a routing shell with no auth at all".packages/cloud-connection/src/cloud-connection-plugin.ts:223reads
try { authSvc = await kernel?.getServiceAsync?.('auth'); } catch { /* ignore */ }— optional-chained, i.e. that code already anticipates a host with no async accessor.
⭐⭐ The finding that changes the cost of the decision
The card calls the sync-
getServicefallback "the candidate fix". It is not a candidate — it is already landed, with the same justification. Verified by me atpackages/mcp/src/plugin.ts:90-105,resolveStdioTenancyPosture: the async arm is gated ontypeof kernel.getServiceAsync === 'function', and when that is false it falls through toctx.getService<TenancyPostureSource>('tenancy').rest-server.ts:2660has a second non-async route via itstenancyServiceProviderbranch.⇒ Of the seven sites in this family, two already have a non-async route. The family-wide change is therefore not "invent a fallback across the seams" but "extend a landed, argued pattern to the seams that lack it" — a materially cheaper and lower-risk decision than the card assumed. ⛔ I am not taking it; Zone 1 reserved it and the deciding input is missing. This is the evidence for it.
Corrections to the card's arithmetic
- The async-only seam list is FIVE, not four. The four cards plus
packages/cloud-connection/src/marketplace-install-local-plugin.ts:1699, owner After all four tenancy-posture seams land: fold their local resolveAdmissionTenancyPosture copies onto one helper — the CLASSIFICATION only, not the resolution #16013. The card, triage and this seat's dispatch all said four. - The three guards are still three and still those, each gated on a present posture. ✓
⭐ Method note, recorded because it nearly inverted the answer
A looser first pass matching bare symbols produced three false positives —
fakeAuthPlugin/fakeBetterAuthPlugin/authLikePlugininpackages/runtime/src/*.integration.test.tsare fakes, not@objectstack/plugin-auth. That instrument would have reported "reachability real". The round caught it and reported it rather than quietly tightening. Also recorded:LiteKernelis publicly exported viaexport * from './lite-kernel.js'atcore/src/index.ts:20, where the symbol name never appears — so a naive symbol grep answers "not exported" with confidence (zero-hit 0 against a firing control of 34 on the same file).Disposition
⛔ Not closed on the in-repo nil (the round's option C, and it is right to refuse it): the residual's only plausible live host is exactly the unmeasured one.
⛔ Not decided here (option B): that is the family-wide change Zone 1 reserved.⇒
needs-user-decision, on one question and one only:Are cloud's per-environment kernels (
ArtifactKernelFactory/EnvironmentKernelFactory)ObjectKernelinstances, orKernelBase-shaped routing shells?ObjectKernel⇒ reachability is nil everywhere, the family-wide fix is skipped at zero cost, and this card closes.KernelBase-shaped ⇒ the residual is live in production on the deployment with the most tenants, and all five seams take the fallback at once — usingmcp/src/plugin.ts:90-105as the landed precedent for both the shape and the justification.
It needs a maintainer or a seat with
objectstack-ai/cloudaccess; ⛔ neither exists in this session.PM dispatch seat · session
session_01ARYe3yQTQCUFm5qPYNgKaJ
Generated by Claude Code
- Nil is an observation about compositions that exist here, not a structural bar. The round measured that the composition would boot: every
Ruling recorded — option A, close with the measurement (director seat, decision batch #59, 2026-09-06)
Maintainer reply, verbatim: 「16063 c, 其他同意」 (this card: adopted as recommended).
The one unmeasured host, read by the director seat against
objectstack-ai/cloudorigin/main(fetched 2026-09-06 14:4x UTC):reading result packages/objectos-runtime/src/artifact-kernel-factory.ts:494const kernel = new ObjectKernel(this.kernelConfig);— the per-environment kernel is anObjectKernel, which hasgetServiceAsyncimplements EnvironmentKernelFactory, whole repo, non-test1 — ArtifactKernelFactory(:330); no other factory shapenew LiteKernel(, whole repo, non-test.ts1 hit, and it is a doc-comment example ( packages/service-ai/src/plugin.ts:228* const kernel = new LiteKernel();) — not a constructioncontrol, same command and scope: new ObjectKernel(non-test3 — the factory above, apps/cloud/server/index.ts:45,apps/objectos/server/index.ts:66⇒ Qualification 2 of the measurement (comment 5557138519) resolves to nil. Combined with the in-repo census, no shipped, example, documented, test, or cloud composition puts a seam owner on a
KernelBase-shaped host together withplugin-auth. Reachability is nil everywhere.Ruling. The residual stays theoretical; the family-wide sync-
getServicefallback across the five seams is not spent, and #15256's Zone-1 ruling ("keeps the previous quiet answer, unchanged") stands. This card closes as completed with its measurement. Two facts recorded for whoever reopens this: the composition would boot if written (Qualification 1), andpackages/mcp/src/plugin.ts:90-105already carries the fallback shape as a precedent if a realLiteKernel+plugin-authcomposition ever appears.Labels:
needs-user-decisionremoved; closed (completed). Ledger on #12708 (batch #59).
Generated by Claude Code
Filed by the PM dispatch loop as option C of the ruling on #15349 (comment
5553763390). ⛔ Unassigned and ungraded —domain:*, type and priority are triage's.The residual
The four
resolveAuthzContextposture repairs censused under #15256 (item 5) all derive the tenancy posture through an async service accessor. AKernelBase-shaped host —LiteKernel, i.e. serverless / edge — exposesgetKernel()but nogetServiceAsync. On such a host all four repaired seams therefore resolve no posture at all, even whenplugin-authhas registeredtenancyas a plain instance.That is the ruled behaviour, not a defect in any of the four cards: #15256's landed comment states such a host "keeps the previous quiet answer, unchanged", and
rest-server.tsdocuments why — aKernelBasehost has no service factories (registerServiceFactorythrows "not supported"), so absence is the only fault it could report anyway, and dereferencing a missinggetServiceAsyncwould turn "this host shape has no async registry" into an outage.But the consequence survives the justification: on such a host, the posture-conditional guards those four cards restore (
organization_required,organization_membership_ended, and the #15409 session-claim drop) stay unreachable.What this card asks for — a MEASUREMENT, not a fix
⛔ Do not implement a fallback here. The candidate fix (fall back to the sync
ctx.getServiceprobe when no async accessor exists, justified by the same "no factories, so a throw can only mean absence" fact) is a family-wide change across all four seams, and it would re-decide a Zone-1 ruling. It belongs to all four at once or to none.The question to answer first, cheaply:
Do
plugin-authandplugin-sharing(and the other three seams' owners) actually boot together on aLiteKernel/KernelBasehost at all?LiteKernelacross the repo is not a reading about whether a composition exists — that failure class produced three false holds in one PM session. Any zero-hit claim needs a positive control firing on the same command and scope.Refs
#15349 (the card whose round surfaced this, and its PR #15996) · #15350 / #15351 / #15352 (the three sibling seams, same residual) · #15256 (the census and item-5 ruling) · #13906 (decision 1 option A, the classification the seams carry) · #15409 (the session-claim drop, one of the guards left unreachable)
Generated by Claude Code