Repository navigation
runtime: on a kernel-resolver host (cloud), every record-bound POST /actions answers 404 RECORD_NOT_FOUND — resolveService asks the host kernel first, and its singleton protocol has none of the environment's objects #22944
Description
Filing gate ①: a reproduced product defect with a named landing site. It surfaced as objectstack-ai/hotcrm#2065: Convert Lead on the hosted environment jack answers "Record not found in crm_lead". It was reproduced and root-caused on a local hosted-shape rig whose pins equal cloud's production release v1.36.0. Reader: the seat that owns @objectstack/runtime (triage routes it). The consumer, hotcrm#2065, unlocks when cloud's framework pin carries the fix.
Dedupe: repo:objectstack-ai/objectstack is:issue resolveService defaultKernel → 2 hits, all states (this card and #5155, the request-kernel parameter, which is a different seam). … kernel-resolver actions RECORD_NOT_FOUND → 3 (this card; #22938 and #6015 are unrelated). Nearest: #10988 (REST double kernel waiter), which is a different defect.
Defect
- Landing site:
packages/runtime/src/http-dispatcher.ts,HttpDispatcher.resolveService(line 2386 onmaintoday). Given ascopeId, it asksthis.defaultKernel.getServiceAsync(name, scopeId)first and returns any non-null answer. Only after that does it consult the request's own kernel (context.kernel). - On a
kernel-resolverhost (cloud's objectos), that order picks the wrong kernel. The request is routed to its environment kernel. The host kernel, however, holds its own singletonprotocol/metadata/objectql, and a singleton answersgetServiceAsync(name, scopeId)whatever the scope. So every dispatcher-served door that passescontext.environmentIdas the scope gets the host's data plane. - What the user sees:
POST /api/v1/actions/:object/:actionwith arecordId.loadActionSubjectRecord→callData('get')→ the host protocol'sgetData→assertObjectRegisteredthrowsOBJECT_NOT_FOUND: Object 'crm_lead' not found.- The catch in
loadActionSubjectRecordswallows it and setsrecordLoadDenied. refuseDeniedSubjectLoad(fix(runtime): consume the caller-scope load verdict at the flow and script action doors #16854) answers 404RECORD_NOT_FOUND"Record not found in ", for a row the caller can read.- Why
GET /api/v1/data/:object/:idstill works on the same host: that route is REST-owned.RestServer.resolveProtocoltakeskernel.getServiceAsync('protocol')from the environment's own kernel (rest-server.ts~2209). - A standalone deployment never sees this.
context.kernelis absent, sorequestKernel()falls back todefaultKernel, and both legs read one kernel. This is why hotcrm#2065 "does not reproduce on 17.7.0 locally" (the hotcrm seat measured a standalone stack). - Script actions: every record-bound script action on a hosted environment has answered 404 since cloud's pin carried fix(runtime): consume the caller-scope load verdict at the flow and script action doors #16854 (2026-09-08).
- Flow actions: flow buttons joined them when objectui#12046 moved the console's flow launch onto the action door. That console reached production with cloud v1.36.0 (deployed 2026-10-11).
- HotCRM's exposure: 4.0.1 declares 30 record-bound actions (23 script, 7 flow).
- Inferred, not measured separately:
callData's API-exposure gate readsmetadata.getObjectfrom the same wrong kernel. On this door it therefore finds no definition and falls open to schema defaults. - Services whose answer flipped, logged once per name:
objectql,metadata,protocol. Nothing else. - Convert Lead, end to end in the console: the action door answers 200, the screen opens, Submit resumes with 200, and the lead reads
convertedwith its account, contact and opportunity (amount 50000) created. - Script
mark_article_helpfulandmark_primaryanswer 200. Flowsschedule_followup,claim_caseandgenerate_quoteanswer 200 and pause. log_callwithout its required params answers 400VALIDATION_ERROR, unchanged.- A nonexistent
recordIdstill answers 404RECORD_NOT_FOUND. The fix(runtime): consume the caller-scope load verdict at the flow and script action doors #16854 refusal still holds. - On a kernel-resolver host whose host kernel holds a singleton
protocolwithout the object,POST /api/v1/actions/<object>/<action>with a readablerecordIdloads the row from the request's environment kernel. Pin it with a test on that shape (two kernels,context.kernel≠defaultKernel). - The same test proves a nonexistent id still answers 404
RECORD_NOT_FOUND. - A host with no
kernel-resolver(standalone, and SharedProjectPlugin scoped factories) resolves exactly as before. Pin it with a test. - Downstream, owned by cloud and not this card: cloud bumps its framework pin past the fix and re-measures Convert Lead on a hosted environment (hotcrm#2065).
Measured
Rig: cloud
mainf8ed1efd (two-processrun-stack.sh --prod-like --with-ui), framework56bf27affb, objectuid758f2f20d. These are cloud v1.36.0's pins, the release now serving production. HotCRM 3.1.0 was installed from the local marketplace and then upgraded in place to 4.0.1, the pathjacktook.Instrumented swallowed error (a probe in the rig's
runtime/distcatch):[probe] subject load threw crm_lead GTS2jcGfJc2tHdxh OBJECT_NOT_FOUND 404 Object 'crm_lead' not found at assertObjectRegistered (metadata-protocol) ← getData ← callData ← loadActionSubjectRecord ← handleActionsRequest ← HttpDispatcher.dispatch [probe] protocol via defaultKernel scoped <envId> reqKernelIsDefault=false hasCrmLead=false reqKernelProtoHasCrmLead=trueReach, same session, same lead:
request answer GET /api/v1/data/crm_lead/GTS2jcGfJc2tHdxh200 POST /api/v1/actions/crm_lead/convert_lead(console click, flow)404 RECORD_NOT_FOUND POST /api/v1/actions/crm_lead/schedule_followup(flow)404 RECORD_NOT_FOUND POST /api/v1/actions/crm_knowledge_article/mark_article_helpful(script)404 RECORD_NOT_FOUND POST /api/v1/automation/lead_conversion/trigger(old console door)200, paused at screen_1Timeline:
Prototype fix, verified on the same rig
The change was patched into the rig's
runtime/dist: when the request carries its own kernel (kernel !== this.defaultKernel), resolve on that kernel first (kernel.getServiceAsync(name, scopeId)). The host kernel's scoped lookup is the fallback. A standalone host (kernel === defaultKernel) keeps today's order exactly.resolveServiceOrLoud/classifyService(thetenancyread of the identity step, and the/keysand activation gates) keep the same scoped-first order. Whether they should follow is a separate question, because it changes which kernel'stenancyanswers identity on a hosted request. This card does not ask for it, and the prototype did not change it.Acceptance
🤖 Generated with Claude Code
Activity
hotlong commentedon Oct 12, 2026
Claim: Claude Code desktop session 29ba8341 (filed this card and opened the fix)
Branch:
claude/issue-22944-request-kernel-firstPR: #22945
objectstack-fleet commentedon Oct 12, 2026
Triage: grade completed. The filer's
bug·priority:p1·domain:cli·repo:cloudstand;area:workflow,securityandpm:dispatchedadded (claim6117825965, PR #22945)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T04:54Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read:6117825965.pm:dispatched. Triage does not touch assignees.security, kept abstract: the card's inferred row says that on this door an access check reads its definitions from the host kernel, not the request's kernel. A check that reads the wrong kernel can fall open. The fix's kernel order covers it, becausemetadatais one of the three services it flips. The PR pins that check on the request's kernel beside the 200s, so the inferred row becomes a measured one.main, which is the v18 line. objectstack-ai/cloud's framework pin stays at56bf27affb(v17) until C7 (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211), per the maintainer's 「等 c7」. So hosted environments keep answering 404 on every record-bound action, and Convert Lead on the hosted 4.0.1 environmentjackfails with "Record <id> not found in crm_lead"; it does not reproduce on 17.7.0 locally hotcrm#2065 stays open, until the maintainer moves that pin. This is posted on decision [Decision] 17.7.0 的两个跨租户写入(W、W2):维持 #15193「不做 17.x 发布线」,只发安全公告加规避说明(A);一次性 17.7.1(B);还是恢复 17.x 维护分支(C) #22938, the 17.x question, where it belongs.