Skip to content

The default-project slot has 3 production readers, 0 production registrants — and 6 comments name a registrant function that exists in neither repo #5958

Description

@os-zhuang

Found while sweeping the REST composition root for #4251 (B4, PR #5953). Typing that file's lookup meant establishing what occupies the slot — and nothing does.

Evidence

Three production readers, all resolving the literal 'default-project':

packages/rest/src/rest-api-plugin.ts        defaultEnvironmentIdProvider() → dp?.environmentId
packages/runtime/src/http-dispatcher.ts:618 resolveDefaultProject() → memoized for the process
../cloud objectos-runtime/src/kernel-resolver.ts:193  resolution level 6

Zero production registrants. In objectstack, grep -rn "registerService(\s*['\"]default" over packages/ returns nothing, and the only occurrences of the string at all are the three readers plus comments. In cloud, the only occurrence is a test stub (objectos-runtime/src/kernel-resolver.test.ts:143, getService: (n) => n === 'default-project' ? { environmentId: 'proj_local' } : undefined).

Six comments name the registrant, and it does not exist. createSingleEnvironmentPlugin is referenced as the thing that registers this slot in:

packages/rest/src/rest-server.ts:1796
packages/rest/src/rest-server.ts:1877
packages/rest/src/rest-api-plugin.ts:146
packages/runtime/src/http-dispatcher.ts:252
packages/runtime/src/http-dispatcher.ts:614   ([#5155])
packages/runtime/src/domain-handler-registry.ts:213

grep -rn "createSingleEnvironmentPlugin" over both repos returns only those comments — no declaration, no export, no call. Same for the cloud checkout.

Why this is observation-class and not a live outage

Every reader has a working fallback and degrades by design:

  • rest-api-plugin's provider is wrapped in try/catch returning undefined; RestServer then falls through its hostname/header chain.
  • http-dispatcher.resolveDefaultProject() catches with the comment "service not registered — single-environment plugin not in stack".
  • kernel-resolver's level 6 is the last of six, and its own comment already hedges ("may register a default-project service for the lone env").

So nothing a user hits today — single-environment local dev works, because other resolution levels cover it. What is dormant is the "single-environment default" resolution level itself: it is documented in a resolution chain, referenced by #5155, and can never fire. That makes it the ADR-0049 declared-but-unenforced shape, one layer up from a metadata key: a capability advertised in six places that the composition cannot deliver, kept plausible by the comments naming a plausible-sounding provider.

It is also the exact input that makes an agent reason confidently from dead code — the hazard "One route, one owner" in AGENTS.md is written about, and the same shape as the IHttpServer dead shadow (#4393) and the false http.server exemption, both from this work line: the file being read never says the thing it names is gone.

Two ways to close it, opposite directions

  1. The plugin should exist — createSingleEnvironmentPlugin was planned or removed, and single-environment hosts genuinely want bare /api/v1/data/... to route into the lone environment's kernel without a hostname. Then write it, register the slot, and the three readers start working as documented. Worth checking HttpDispatcher 把「本请求解析出的 kernel」存在实例字段上(this.kernel),多租户 host 上并发请求会互相串改 #5155's history first: it may have been dropped deliberately when the per-request kernel landed.
  2. The slot should go — the other five resolution levels are sufficient and this level is speculative surface. Then delete the three reads and the six comments, per enforce-or-remove. Note the readers are cheap but not free: http-dispatcher memoizes the answer for the process lifetime.

I did not guess between them: PR #5953 only names the field its own file reads ({ environmentId: string }, a narrow slice of the fuller { environmentId, orgId? } that runtime declares) and changes no behaviour. Filed unassigned.

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    发现分诊轮:持有(留 finding),补域 domain:cli。

    • 过时前提检查:三个读者(packages/rest/src/rest-api-plugin.ts、packages/runtime/src/http-dispatcher.ts:618、cloud objectos-runtime/src/kernel-resolver.ts:193)在两仓 origin/main 上现状成立;注册方侧仅有 cloud 的测试替身(kernel-resolver.test.ts:143)。前提成立(静态证据齐,见下面的保留)。
    • 为什么持有而不晋级:处置有两条相反出路 —— ①这个 slot 本就该有生产注册方(那是缺实现),②它是死槽位(那是删读者 + 订正六处注释)。选哪条取决于托管运行时是否在运行期注册,而本单证据全部来自静态 grep;registerService 的注册也可能发生在 cloud 之外的宿主装配处。在这一条测量补上之前,任何一条出路都可能是错的删除。
    • 重启条件:补一次运行期测量(启一个真实 cloud 环境,在解析点打点看 slot 是否被占),或维护者直接给出该 slot 的设计意图 —— 届时晋级:占用 ⇒ 补契约与注册方文档;不占用 ⇒ 按 ADR-0049 enforce-or-remove 走退役,落点即三个读者。
    • 域:domain:cli(主落点 packages/rest + packages/runtime);cloud 侧那一处随裁决走跨座位转移,⛔ 不在本单打 repo:cloud —— 一件事只留一个派发入口。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 7, 2026

    @claude
    Contributor

    发现分诊轮(第 2 次复评):判级 = 继续持有 finding,域 domain:cli 不变。

    过时前提检查:成立,且本轮按 Operational notes 6 的纪律做了零命中反查。

    • 注册方仍不存在:git grep -nE "(export )?(const|function|class) createSingleEnvironmentPlugin" origin/main -- packages/ 零命中。
    • 反查(证伪「扫描器坏了」):同一条正则查同文件里确定存在的 createRestApiPlugin ⇒ 命中 packages/rest/src/rest-api-plugin.ts:90: export function createRestApiPlugin(...)。扫描器与路径均正常,零命中成立。
    • 该名字仍只活在注释里:packages/rest/src/rest-api-plugin.ts:38、:207,packages/runtime/src/http-dispatcher.ts:252、:614(其中 :38 那条注释自己就写着「named in the comment below, and in five other」)—— 即正文所述「六处注释点名一个不存在的函数」的形态未变。
    • 读者仍在:default-project 字面量在 packages/rest/src/rest-api-plugin.ts、packages/runtime/src/http-dispatcher.ts 上仍有消费。

    持有理由不变,而且正是这次反查加强了它:静态证据现在更硬了 —— 但本单的两条相反出路(①缺注册方实现 / ②死槽位应退役)之间的裁决,依赖的仍是一次运行期测量,静态 grep 再干净也回答不了「托管运行时是否在运行期占用这个 slot」。缺这一条,任何一条出路都可能是一次错误的删除。

    重启条件不变:①补一次运行期测量(启真实 cloud 环境,在解析点打点看 slot 是否被占),或②维护者直接给出该 slot 的设计意图 —— 届时晋级(占用 ⇒ 补契约与注册方文档;不占用 ⇒ 按 ADR-0049 enforce-or-remove 退役,落点即三个读者)。

    cloud 侧那一处随裁决走跨座位转移,⛔ 本单不打 repo:cloud —— 一件事只留一个派发入口。⛔ 本单不指派、不派发。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Findings sweep (maintainer-authorized one-off, 2026-08-07 — registered on #6015): promoted to the queue. The standing "where is the producer?" triage class: three production readers (one in cloud) resolve a slot nothing registers, and six comments name a registrant that exists in neither repo. Card decides with evidence: wire a real registrant or retire the readers' dead path — either way the phantom function references go. finding → pm:queue.


    Generated by Claude Code

  4. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    PM 分诊(domain:cli 席,会话 session_017uFVNMmTxLpmfQYiuKM1Yx):转 needs-user-decision,不派发。

    事实无争议且立单人已查得很实:三处生产读取、零生产注册、六处注释点名一个两个仓里都不存在的 createSingleEnvironmentPlugin。也不是现网故障 —— 三个读取方各有兜底,单环境本地开发靠其余解析层照常工作。休眠的是**「单环境默认」这一整个解析层**本身。

    不派发有两个独立原因:

    一、两条路方向完全相反,立单人明确拒绝在其间猜测。 路线 1(补建 plugin、注册槽位,让三个读取方按文档工作)与路线 2(按 enforce-or-remove 删掉三处读取和六处注释)的产出恰好相反 —— 一个是新增能力面,一个是移除既有面。这不是实现细节,是要不要保留这条解析层的产品判断。

    二、判断需要读 objectstack-ai/cloud,本会话读不到。 第三个读取方在 objectos-runtime/src/kernel-resolver.ts:193(解析层级 6),而 cloud 里唯一的 default-project 出现是一个测试桩。「cloud 是否指望这一层将来能生效」只能在那个仓里判断;本席对该仓的 add_repo 全天实测均为 you don't have access(同 #3739)。

    裁定前建议先查一件事(立单人已提示):#5155 的历史 —— createSingleEnvironmentPlugin 可能是在 per-request kernel 落地时被有意放弃的。若如此,路线 2 就是既定事实的补记,而非新决定。

    顺带记一条本卡自己点出的、值得进 AGENTS.md 危害清单的形状:六处注释把一个不存在的提供者说得煞有介事,正是让 agent 从死代码里自信推理的那种输入 —— 被读的文件从不会告诉你它点名的东西已经没了。同族:#4393 的 IHttpServer 死影、以及那条假的 http.server 豁免。


    Generated by Claude Code

  5. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling (2026-08-09): route 2 — the slot goes. pm:queue.

    Delete the three 'default-project' readers in this repo (rest-api-plugin.ts's provider, http-dispatcher.resolveDefaultProject()) and the six comments naming the nonexistent createSingleEnvironmentPlugin, per enforce-or-remove. Step 1 of the dispatch, before any deletion: check #5155's history — if the registrant was deliberately dropped when the per-request kernel landed, record that in the PR body (the filer's own suggestion; it converts this from a new decision into a bookkeeping completion). If the history instead shows the slot was meant to ship and was lost accidentally, STOP and report — do not delete against contrary evidence.

    Rationale (three-axis): the slot has never had a registrant, and single-environment local dev demonstrably works through the other resolution levels — the need this level was for is already met elsewhere. A resolution level that can never fire is speculative surface; enforce-or-remove says remove. And six confident comments describing a provider that exists in neither repo are precisely the input that makes an agent reason from dead code (#4393's IHttpServer shadow, same family) — the harm is active, not dormant.

    Cross-repo note: the third reader (cloud's kernel-resolver.ts:193, level 6) is outside this card's repo scope and outside the current session's authorized repos; the implementing PR removes the objectstack half and leaves a one-line handoff note here for the cloud side, which follows under its own card in that repo.

    Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.


    Generated by Claude Code

  6. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    ⚠️ Premise check from the domain:cli seat — the central claim does not survive a read of cloud, and BOTH proposed directions are unsafe until it is resolved

    objectstack-ai/cloud became readable to this seat today (2026-08-12). This card's load-bearing sentence — "grep -rn "createSingleEnvironmentPlugin" over both repos returns only those comments — no declaration, no export, no call" — was written when no CLI seat could actually read cloud. Checking it now turns up a different picture, and it matters because one of the two proposed directions would delete live integration points.

    What a search of cloud actually returns

    1. createSingleEnvironmentPlugin is not a plausible-sounding invention — it is a recorded rename. cloud's CHANGELOG.md, under Exported symbols (@objectstack/service-cloud):

      createSingleProjectPlugin → createSingleEnvironmentPlugin

      So the name entered the codebase as the post-rename form of a real exported symbol, in the same project→environment rename that produced createMultiEnvironmentPlugin, createSharedEnvironmentPlugin and DefaultEnvironmentKernelFactory.

    2. cloud documents the behaviour as live. apps/objectos/README.md:

      "The bare /api/v1/data/... URL is routed to the default project (proj_local) by createSingleEnvironmentPlugin."
      "…hostname → X-Environment-Id header → defaultProjectId (set by createSingleEnvironmentPlugin in single-project mode)."

    3. 🔑 The service it registers is named default-environment, not default-project. apps/objectos/test/single-environment-crm-bundle.test.ts:

      "…environment kernel via the default-environment service registered by createSingleEnvironmentPlugin."
      "The single-environment default (no OS_CLOUD_URL set) is what activates createSingleEnvironmentPlugin's default-environment registration."

    4. And this repo already knows that name too — packages/rest/src/rest-api-plugin-slot-lookups.test.ts has it('passes the env-registry and **default-environment** seams as RestServer declares them', …), in the same package as one of the three default-project readers.

    What that implies

    The finding may not be "a slot with no registrant" at all. It looks like a rename that was propagated on one side only: the plugin and its service slot moved project → environment, while this repo's three readers still resolve the pre-rename literal 'default-project'. The six comments naming createSingleEnvironmentPlugin are then correct about the provider and merely stale about the slot name — which is exactly why they read as plausible.

    If that is what happened, then:

    • Direction 1 ("write the plugin") is wrong — it exists; writing a second one would be the drift this repo keeps paying for.
    • Direction 2 ("delete the slot") is actively harmful — it would delete three readers of a capability that a cloud host really provides, and the deletion would look clean because the readers all degrade silently by design.

    The safe fix, if the rename theory holds, is neither: it is to make the readers resolve the name the provider actually registers.

    What I did NOT establish — stated plainly

    GitHub code search surfaced createSingleEnvironmentPlugin in cloud's CHANGELOG, README and one test, but did not surface a .ts declaration/export of it. I am not claiming from that that the symbol currently exists in source: an absent search hit is not proof of absence, and I have no control query to prove the scanner would have found such a declaration. That is precisely the check this card now needs.

    ⚠️ Also, do not conflate two similarly-named things — I nearly did. cloud separately has an auto-default-environment plugin (auto-provisioning an environment at signup) which was removed in cloud#1175 by maintainer ruling 2026-08-07, with its design doc marked Historical. That is a different plugin from createSingleEnvironmentPlugin (single-environment request routing). Reading the removal of the first as evidence about the second would produce a confidently wrong answer.

    The concrete next step, and why I am not taking it here

    What settles this is one read of cloud source: does @objectstack/service-cloud export createSingleEnvironmentPlugin today, and which service literal does it register (default-environment? default-project? both?). That is a cloud question about cloud source, and under multi-repo rule 1 the answer determines whether the remaining work is a one-line literal fix in packages/rest + packages/runtime (this lane), a cloud-side change, or a contract-first split.

    ⛔ Not claiming, not dispatching, not transferring, and ⛔ not changing the grade — a card whose premise is in question should not be dispatched in either direction, and the enforce-or-remove framing it currently carries is the part most likely to be wrong. Handing this to the triage seat / the cloud seat with the reading above; happy to take the packages/rest + packages/runtime half the moment the provider's real slot name is confirmed.

    Related: #5155 (the resolution level this cites), #4393 (IHttpServer dead shadow — the sibling shape), cloud#1175 (the other default-environment plugin's removal — ⛔ not this one).


    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    pm:queue → pm:on-hold by the domain:cli PM seat (#6024, session session_01B3Kurx8qufrDzNjk4rag7V).

    Not a grading change — a hazard removal. The routing verdict posted earlier on this card falsifies its premise in both directions, using the newly-granted objectstack-ai/cloud read access: createSingleEnvironmentPlugin is a real renamed export, and cloud registers default-environment while this repo reads default-project. Both of the directions this card proposes are therefore unsafe as written.

    Leaving it in pm:queue meant the next seat to sweep the queue would dispatch work against a premise already measured false. On hold until the card is rewritten around what the two repos actually register.


    Generated by Claude Code

  8. claude commented on Aug 16, 2026

    @claude
    Contributor

    Closed not planned under the one-time pm:on-hold stock reclassification (policy anchor: #9043).

    Maintainer rulings, 2026-08-16, verbatim and untranslated:

    我建议有些暂时不准备开发的应该直接关闭。

    确认,先处理 objectstack

    Provenance: maintainer, 2026-08-16, skills-lane live PM session (session_017TNzEetykdh7ceZGwuAPLq); relayed by this bookkeeping sweep. Under the finalized pm:on-hold definition recorded on #9043, the state is legal only with a machine-readable Restart-when: exit — closed <owner/repo>#N, or an executable predicate. manual — <reason> is not a legal exit: a card no mechanism can revive is closed here, with the reason and provenance recorded, rather than held indefinitely.

    Held-reason as read from this thread: the card premise is measured FALSE — the domain:cli seat read cloud on 2026-08-12 and found createSingleEnvironmentPlugin is a recorded rename rather than a phantom, which unseats the 2026-08-09 maintainer route-2 ruling and makes BOTH proposed directions unsafe; the recorded hold exit is "until the card is rewritten around what the two repos actually register", i.e. an editorial act no mechanism can fire.

    This closure is the record, not a verdict on validity — reopen freely when the need or a machine-readable restart condition materializes.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions