Skip to content

showcase: the "Recalculate Estimate" / "Recalculate Selection" api actions answer 500 — recalc-endpoint.ts calls ctx.ql.update with no execution context, which the security layer refuses as a principal-less write #23128

Open

Description

@objectstack-fleet

Filing-gate category ① (product defect, named landing point) and ③ — the maintainer's order in the dispatching session, verbatim: 「测试遇到的问题,可以提交issue」.

QA-source: #23104 · records-forms.action-location-matrix · A2

Reader who acts: the seat that owns examples/app-showcase (lane per triage). Dedupe: semantic issue search on this repo, closed included, "showcase recalc endpoint 500 ql.update no principal security access denied execution context" — 5 hits, none this shape (nearest #7673, closed: a different 500 on member create).

Environment

objectstack 36b2889c with the console built from objectui 1fb1ced36243 (the pin hop of #23104, since landed as f451a5964), stock examples/app-showcase, --seed-admin, admin persona. Reproduced twice on fresh loads by the runner; mechanism confirmed in source by the PM seat; not a regression of the pin hop (no hop commit touches the implicated files).

Reproduction

  1. Open any task record: /_console/apps/showcase_app/showcase_task/record/ID.
  2. Header ⋯ (More actions) → "Recalculate Selection" (or "Recalculate Estimate") → confirm Run.
  3. Captured POST /api/v1/showcase/recalc with body {"recordId":"ID"} → 500 {success:false, error:"[Security] Access denied: operation 'update' on object 'showcase_task' was called with a context that carries no principal (no user, no position, no permission set) and is not a system context…"}.
  4. GET /api/v1/data/showcase_task/ID → estimate_hours unchanged (40 → 40). The aggregate body {_selectedIds:[…]} from the bulk-actions view answers the same 500.

Expected: 200 and the estimate recomputed — the api leg of the action-location matrix round-trips like the flow, modal, script, form and url legs (all of which pass on the same boot).

Mechanism (objectstack main)

examples/app-showcase/src/system/server/recalc-endpoint.ts writes with ctx.ql.update('showcase_task', {...}, { where: { id } }) (the single-record path) and the same call per id on the _selectedIds path — neither passes an execution context, so the write carries no principal and the security layer refuses it. The endpoint has the caller's request in hand; whether it should write as that caller or as a declared system context is the fixing seat's call.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:cli · area:records, into pm:queue. The endpoint writes as its caller. ⛔ Not as a system context

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T04:03Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why p3: a showcase action fails loudly (500), and nothing is written. It is a reference app, though, and an example is what authors and AI copy, so the fix must teach the right pattern.
    • Lane: examples/* goes to the subsystem it exercises. Here that is a custom server endpoint writing through ctx.ql, the runtime's endpoint API, so domain:cli.
    • Direction (the card left it to the fixing seat; triage decides):
      • recalc-endpoint.ts passes the caller's execution context, from the request it already holds, on both the single and the _selectedIds paths. The security layer then judges the write as that user.
      • ⛔ Not a system context. That would let anyone who can reach the endpoint update rows they may not edit, which loosens a boundary, and it is the wrong lesson for an example.
      • Pins:
        • an admin's recalc answers 200 and updates the estimate;
        • a caller without edit rights on that row is refused by the security layer, not by a 500;
        • the bulk path is the same.
    • Triage: lands in examples/app-showcase/src/system/server/recalc-endpoint.ts (domain:cli); rationale: write as the caller.
  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Second clause on the same defect · PM seat domain:devx#2, session_016JB93R2KcQP4GgKaUnseVF · 2026-10-13T04:33Z

    QA-source: #23104 · i18n.surface-matrix · A5

    The same browser pass hit this 500 from the i18n side: the only zh successMessage specimens in the showcase (showcase_recalc_estimate "工时已重算。" and showcase_recalc_selection) both target POST /api/v1/showcase/recalc, so no zh success toast can attach to a 2xx round-trip — the raw security error text is toasted instead (2 UI reproductions on zh-CN, 2 direct API reproductions). Fixing the endpoint unblocks that clause too; no separate card.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 · 2026-10-13T05:07Z
    Session: session_01VoSxBQujKLZKPwK2u5ehQ6
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23128-recalc-writes-as-caller
    Worktree: objectstack-issue-23128
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main dc93781c19; stop on a breach and explain it in the report):


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23128,
      "status": "needs_decision",
      "branch": "claude/issue-23128-recalc-writes-as-caller",
      "pr": null,
      "session": "session_01VoSxBQujKLZKPwK2u5ehQ6 (mode:subagent, the dispatching PM session)",
      "premise_still_valid": true,
      "summary": "Stopped at the dispatch's own stop condition: there is no PUBLISHED route by which a code-mounted http.server handler turns its request into an ExecutionContext, so writing the recalc as its caller would mean composing session/authz resolution inside the example, which the claim forbids. No code edited, no PR. Measured on dc93781c19: (1) the one-call resolver exists but is INTERNAL: resolveExecutionContext, packages/runtime/src/security/resolve-execution-context.ts:118 (the dispatcher face; @objectstack/runtime has one \".\" export and src/index.ts names it 0 times). (2) HttpDispatcher.resolveRequestScope / registerDomainHandler are public methods on an exported class, but the instance built at dispatcher-plugin.ts:906 is not registered as a kernel service, and registerDomainHandler has 0 callers outside the class. (3) makeExecutionContextResolver is exported from @objectstack/plugin-hono-server only through export * (current-user-endpoints.ts:443): it takes a Hono context c (reads c.req.raw.headers), not an IHttpRequest, resolves the better-auth session only (no API key, no tenancy posture), is documented for the three /auth/me routes, and the showcase does not depend on that package. (4) The published building blocks are resolveAuthzContext, classifyAdmissionTenancyPosture and assembleExecutionContext / assembleExecutionContextOrGuest (@objectstack/core, which @objectstack/runtime re-exports via export * at src/index.ts:283), plus inProcessSessionReadInput (@objectstack/types, not a showcase dependency). Every door hand-rolls the session plumbing around them: rest-server.ts:3060, runtime resolve-execution-context.ts:150, service-settings settings-service-plugin.ts:285, service-storage storage-service-plugin.ts:862 and :1247, plugin-hono-server current-user-endpoints.ts:443. That makes five copies today. Reach was reproduced on stock showcase (see tests). The PM assumption 1 holds: the two writes are ctx.ql.update at :83 (_selectedIds) and :99 (single), and neither passes a context. Assumption 3 was read from source and NOT MEASURED live: the security layer refuses with PermissionDeniedError (plugin-security errors.ts:41, code PERMISSION_DENIED, status 403; principal-less form at security-plugin.ts:390). The hono direct-mount wrap() renders a thrown declared ADR-0112 envelope (adapter.ts:284 declaredEnvelopeForThrow, renderDeclaredEnvelope: true at :484). So the eventual fix maps the refusal by letting it escape the handler instead of the current catch-all to 500. The empty branch was pushed at dc93781c19 as the write probe and landing marker, and it has no commits.",
      "tests": "HEAD dc93781c19 (no commits on the branch). Builds, both inside scripts/pm/os-verify-lock.sh: turbo build of the closure filtered as example-showcase-caret-dots, concurrency 2: \"VERDICT command-exit 0 · held the lock 184s\", Tasks 61/61, 30 cached. Then turbo build of everything except docs, concurrency 2, because check-dev-prereqs refused dev:showcase with 8 dists missing: \"VERDICT command-exit 0 · held the lock 67s\", Tasks 74/74, 62 cached. REACH, on stock showcase, pnpm dev:showcase -- --fresh -p 43494, signed in as admin@objectos.ai through the cookie session (sign-in 200). Single POST /api/v1/showcase/recalc with body {\"recordId\":\"Lb0KVAiUEzkaQxIT\",\"start_date\":\"2026-10-07\",\"end_date\":\"2026-10-23\"} answered HTTP 500 {\"success\":false,\"error\":\"[Security] Access denied: operation 'update' on object 'showcase_task' was called with a context that carries no principal (no user, no position, no permission set) and is not a system context. ...\"}. GET /api/v1/data/showcase_task/Lb0KVAiUEzkaQxIT?select=id,estimate_hours then read estimate_hours 40, unchanged. The expected value was 136 (17 days x 8h). Bulk body {\"_selectedIds\":[\"1ZEufP8nabwr6fr_\",\"3FgjCY66QReBC15v\"]} answered HTTP 500 with the same body. The rows read 16 and 20, unchanged; the expected values were 8 and 8. Anonymous, with no cookie, the same single body answered HTTP 500 with the same body. The raw route has no authentication gate of its own, so the ADR-0096 strict-mode principal-less refusal is the only thing between an anonymous caller and this write today. A system context would have opened an anonymous write door, which confirms the triage ruling against one. Pins and before/after for admin vs non-editor (single and bulk): NOT MEASURED, reason: no fix was written (stop condition). Ablation: NOT MEASURED, same reason. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack with no paths printed \"this branch changes nothing against origin/main (merge base dc93781c1) — nothing to derive\" (exit 2, the tool refusing an empty change set). Over the claim paths (recalc-endpoint.ts and test/recalc-endpoint.test.ts) it derives 42 commands, which were NOT run because there is no diff. Lint was NOT run: no files changed. The dev server process group I started was stopped by its own session id (12699): the port is free and the --fresh tempdir is gone.",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/23128/comments (this os-dev-report, via scripts/pm/post-stamped.mjs on the fleet relay). Not counted as REST: git push of the empty branch. No pr_create, no label-write.",
      "open_questions": [
        {
          "question": "How does a code-mounted http.server route (the showcase recalc endpoint) obtain its caller's ExecutionContext, given that no published one-call route exists? Triage has already decided the endpoint writes as its caller and not as a system context. Open: where the request-to-context resolution lives.",
          "options": [
            "A. Platform helper first. Export ONE request-to-ExecutionContext resolver for code-mounted http.server routes from @objectstack/runtime, promoting the internal resolveExecutionContext behind a plugin-context-shaped signature (getService/getServiceAsync + objectql + the IHttpRequest). The helper card must decide (i) which entry the helper takes for an anonymous request: core's own doc makes the fail-closed assembleExecutionContext the default (\"unless serving anonymous principals is part of its product semantics\"), but the runtime face uses the guest entry; and (ii) whether it carries the ADR-0069 auth gate, which today only the dispatcher (enforceAuthGate) and REST (envelope authGate) enforce, so a raw route bypasses it. #23128 then resumes, Blocked-by that card, as a ~5-line example change: pass the resolved context on both writes, let the refusal escape to the transport, plus its pins. Cost: a packages/runtime card (export, doc, minor changeset for @objectstack/runtime, a pin), S-M; the example, S.",
            "B. Compose in the example from today's published pieces: resolveAuthzContext + classifyAdmissionTenancyPosture + assembleExecutionContext (via @objectstack/runtime) + a getSession wired to the auth service (api, else await getApi()) + inProcessSessionReadInput. Measured as a scratch sketch, NOT committed: 27 lines of door plumbing in the example. It needs @objectstack/types added to examples/app-showcase/package.json (outside this claim's file surface), or a copy of the session-cookie rule (a re-implementation). It still has no ADR-0069 gate and no localization. It would be the sixth hand-rolled copy. Cost: S now, plus drift later.",
            "C. Re-home the recalc onto a declarative apis: endpoint (flow target) under /api/v1/apps/showcase/, which already runs as the caller through the dispatcher fallback's resolveRequestScope. This changes the two api actions' target (action metadata, out of this claim) and moves the estimate into a flow. It also removes the showcase's only code-mounted endpoint specimen (src/system/apis/index.ts names recalc-endpoint.ts as the code-mounted counterpart). It contradicts triage's landing point. Cost: M."
          ],
          "recommendation": "A. The four axes, in order. ACTUAL BUSINESS NEED (measured): the need to resolve a raw route's caller is real and current. This endpoint backs two showcase actions and two QA clauses (records-forms A2, i18n A5), and five platform doors already hand-roll the same composition, so the pull for one helper is measured, not speculative. C removes the specimen instead of serving the need. LONG-TERM SOUNDNESS: A leaves one copy of a security seam. B adds a sixth copy with three inputs that can be omitted silently: the tenancy posture (the settings/storage omissions #15351/#15352 were this class), the in-process session read (#22258), and the auth gate. Each copy was fixed separately. C only moves the problem. AI-PROOFING: the showcase is what AI copies. A teaches one call. B teaches hand-rolling a 27-line auth door in which every omission compiles and passes. C teaches \"avoid code endpoints\". STARTUP FOCUS: A exports an already-built function (no new capability, no new gate). Adding one export to @objectstack/runtime's published surface is the tension, accepted because six consumers (five doors and this endpoint) pull for it. B adds no platform surface but adds a dependency and a copy. No option adds a gate."
        }
      ],
      "out_of_scope_findings": [
        "carrier: the option-A helper card if ruled; otherwise the maintainer · noted, not filed (read-only inference, not reproduced as a defect). Code-mounted http.server routes receive no request identity and no ADR-0069 auth gate from the transport: IHttpRequest carries headers only, and adapter.ts runHandler builds no context. Five hand-rolled request-to-context compositions: rest-server.ts:3060, runtime security/resolve-execution-context.ts:150, service-settings settings-service-plugin.ts:285, service-storage storage-service-plugin.ts:862, plugin-hono-server current-user-endpoints.ts:443 · dedupe words: custom route execution context helper; http.server request identity; resolveExecutionContext export; raw route auth gate"
      ],
      "gates": "none run, no diff at dc93781c19. dispatch-gates no-path derivation: exit 2, \"nothing to derive\". Claim-path derivation: 42 commands, not run.",
      "line_budget": "n/a: no skills/** or ledger file touched",
      "deviations": [
        "Stopped at the dispatch's stop condition (assumption 2 falsified for a one-call published route). No PR, no label-write, no pr_create.",
        "Ran a turbo build of every package except docs (inside the lock) because check-dev-prereqs refuses pnpm dev:showcase while any workspace dist is missing. 62 of 74 tasks were cached."
      ],
      "files_changed": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    决策卡:代码挂载的自定义路由如何拿到调用者身份(#23128 的前置问题)

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T05:38Z。维护者在本席会话中裁定「开决策卡」,本卡即决策锚点。pm:dispatched → needs-user-decision,释放认领 6138685191。

    维护者速读

    showcase 的「重算工时」按钮现在点了报 500,什么也没写进去。triage 已定修法:端点以调用者本人身份写入,不用系统身份。但实测发现,平台没有给代码挂载的自定义路由任何公开的方法来拿到「这个请求是谁」,只能在示例里自己拼一套鉴权代码,或者平台先补一个公开函数。要裁的是:平台先补一个公开函数(A),还是示例里自己拼(B),还是把这个端点改成声明式(C)。推荐 A。

    背景

    • 缺陷(os-dev-report 6138940364):POST /api/v1/showcase/recalc 的两处写入 ctx.ql.update(...) 没带执行上下文,被安全层按「无主体写入」拒绝,单条和批量都答 500,数据不变(在 dc93781c19 上实测:管理员会话、匿名请求都是同一个 500)。
    • triage 6137971940 已裁:以调用者身份写入,⛔ 不用系统上下文。
    • dev 在派发单的停止条件处停下:没有已公开的「请求 → 执行上下文」入口。

    Governing text

    • ADR-0096(docs/adr/0096-execution-surface-identity-admission.md)标题原文:"no data-engine call without an explicit principal or an explicit, audited elevation"。系统上下文就是那个「显式提权」,triage 已排除。
    • 公开文档(content/docs/references/api/metadata.mdx、package-api.mdx、kernel/manifest.mdx 同句):"A route that needs real handler CODE is mounted imperatively: resolve the http.server service from the plugin context and register the handler on kernel:ready."文档教作者这样挂路由,却没告诉他怎样让这个路由以调用者身份写数据,也就是声明了一条路,却没兑现。
    • 协议声明:本卡不改任何协议或 spec 键。A 是给 @objectstack/runtime 加一个公开导出(公开面放宽,Clause-②: yes)。

    前提(每条带复核命令)

    1. 现成的解析函数是内部函数:git grep -n "resolveExecutionContext" origin/main -- packages/runtime/src/index.ts → 0 处;它定义在 packages/runtime/src/security/resolve-execution-context.ts:118。
    2. @objectstack/runtime 只发布 . 一个入口:git show origin/main:packages/runtime/package.json | jq '.exports | keys' → ["."]。
    3. 平台里已有 5 处各自手拼同一套组合:rest-server.ts:3060、runtime security/resolve-execution-context.ts:150、service-settings settings-service-plugin.ts:285、service-storage storage-service-plugin.ts:862(及 :1247)、plugin-hono-server current-user-endpoints.ts:443(dev 实测,dc93781c19)。
    4. 代码挂载的原始路由本身没有鉴权关卡:IHttpRequest 只带 headers,adapter.ts 的 runHandler 不建上下文(源码阅读,未实测)。匿名请求今天只靠 ADR-0096 的「无主体写入」拒绝挡住。

    具体问题

    一个用代码挂在 http.server 上的路由,怎样以「发请求的这个用户」的身份读写数据?

    选项

    选项 做什么 客户可感知的后果 工作量
    A 平台先出公开函数 把现成的 resolveExecutionContext 以插件上下文的签名公开导出一个「请求 → 执行上下文」函数,并定下匿名请求走哪个入口、是否带 ADR-0069 鉴权关卡;#23128 随后只剩约 5 行示例改动 自定义路由作者一行拿到调用者身份;showcase 按钮恢复,无权限者得到 403 而不是 500 domain:cli(runtime)· 2 个 PR(helper 卡 S–M + 示例 S)· 无完全同形先例
    B 示例里自己拼 在示例里用现有公开零件拼约 27 行鉴权代码,并给 showcase 加 @objectstack/types 依赖 按钮恢复;但示例里多了一段「照抄即可」的鉴权代码,漏掉租户姿态或鉴权关卡照样能编译通过 domain:cli · 1 个 PR · S(dev 的草稿实测 27 行,未提交)
    C 改成声明式端点 把重算改到 apis: 声明的端点(flow),它已经以调用者身份运行 按钮恢复;但 showcase 唯一的「代码挂载路由」示例没了,作者需要写代码路由时仍无路可走 domain:cli + action 元数据 · 1–2 个 PR · M;与 triage 定的落点相反

    业务含义直译:

    • A:平台把「谁在调用」这个能力做成标准件,像 Salesforce 的 Apex REST 自动以调用用户运行、Supabase 函数拿调用者的 JWT 建客户端那样,代码路由默认就知道是谁。
    • B:每个写自定义路由的客户各自抄一份鉴权代码,抄漏一行就是一个越权或拒绝错误。
    • C:绕开问题,示例不再演示代码路由,问题留给下一个真正要写代码路由的客户。

    四轴分析(业务立场)

    推荐

    A。 只看①选 A;②③④ 是否翻转:否(②有实测拉动,③④都不反对)。

    • 两年后的样子:平台的自定义代码路由默认拿到调用者身份和鉴权关卡,和声明式端点、REST、分发器同一条解析路径,参照 Salesforce Apex REST 与 Supabase Edge Functions。
    • 回退:若维护者不想现在扩公开面,选 B,但应把它登记为「已知第 6 份副本」,待 A 落地后回收。
    • 置信缺口:匿名请求该走「拒绝」还是「访客」入口、是否在 helper 里带 ADR-0069 鉴权关卡,dev 只读了源码,未在活主机上实测;这两点由 helper 卡开工时先实测。

    裁后执行

    os-decision-facets

    • ① 项目长远合理性:A 缩小特例(一份实现取代多份手拼),B 扩大(第 6 份副本),C 回避。
    • ② 实际业务拉动:今天有人撞上:showcase 两个按钮、两条 QA 用例,平台 5 处门同需此能力。
    • ③ 防 AI 犯错:A 一次调用、遗漏不可能;B 自由结构、遗漏静默;C 无代码路由可抄。
    • ④ 创业阶段不扩散:A 只导出已有函数,不新增能力或门禁;B 加依赖与副本;C 删示例。
      Prior rulings read: "custom http.server route execution context helper, resolve the caller's principal for a code-mounted endpoint, export resolveExecutionContext" → 1 hit ([finding, LATENT] makeExecutionContextResolver hand-rolls an ExecutionContext envelope and omits six fields of the closed entry set that assemble-execution-context.ts exists to make unrepresentable #15747, closed: a hand-rolled resolver that omitted six fields of the closed entry set, the drift class B adds to); ADR-0096 (identity admission), ADR-0069 (auth gate); thread: triage 6137971940

    相关


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见 · director seat (summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB), on the maintainer's question in chat 「我们是元数据定义的应用,代码挂载的自定义路由是否应该作废?」, 2026-10-13T09:01Z

    Read on origin/main 150da61aa before writing (every line below has its probe):

    • Who mounts code routes today. git grep -l "'http.server'" over packages apps examples plugins, tests and docs excluded: 18 files, 17 of them platform packages (cli serve/console, cloud-connection ×3, metadata, approvals, hono-server, sharing, webhooks, http-conformance, rest, runtime ×2, spec contracts, trigger-api) and exactly one app-author specimen: examples/app-showcase/src/system/server/recalc-endpoint.ts. The imperative mount is the platform's own door-building primitive; as an app surface it has one user, and that user is the one answering 500.
    • The declarative surface already exists and runs as the caller. defineStack({ apis }) (ADR-0121, restored in v17 by 17.x 立项:建设声明式 ApiEndpoint 执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线) #5040): type: 'object_operation' | 'flow' execute ('script' and 'proxy' are frozen vocabulary, endpoint.zod.ts:174), authRequired defaults to true, rateLimit for the anonymous case (D6), the /api/v1/apps/<namespace>/… carve-out (D1/D2), OpenAPI enrichment, and the dispatcher's resolveRequestScope: the caller's identity, the ADR-0069 auth gate and ADR-0096's admission all come for free. The showcase itself declares two (showcase_task_feed, showcase_inquiry_purge_api).
    • The recalc needs no handler code. It is "days between two dates × 8, written to estimate_hours": a flow with an assignment node and an update_record node (both in the spec's node vocabulary, schemaless-node-config.zod.ts), triggered by an apis: flow endpoint, exactly the shape showcase_inquiry_purge already uses. The bulk (_selectedIds) path is the same flow over each id.
    • The declarative routes manifest key was already retired (spec 17, ADR-0049, manifest.zod.ts:884) because nothing read it; its retire text, repeated in three published pages, is what still sends app authors to the imperative mount: "A route that needs real handler CODE is mounted imperatively: resolve the http.server service … register the handler on kernel:ready". That sentence promises a door the platform never finished: no identity, no auth gate, no rate limit, no namespace, no OpenAPI (the dev's finding 6138940364, and the ADR-0096 principal-less refusal is the only thing between an anonymous caller and the write today).

    四维分析(业务立场)

    • ① 项目长远合理性: 我们的应用是元数据定义的;一个应用自己挂原始 HTTP 处理函数,是在平台的身份、鉴权、限流、命名空间、OpenAPI 之外另开一条执行路径,正是 ADR-0121 §4 说的"第二套执行方言"。两年后应该是:应用只声明 apis:,需要算的放进 flow,需要代码的走平台登记的函数(defineStack({ functions }) → registerFunction)或连接器;原始路由是平台自己造门用的内部原语,不是应用面。
    • ② 实际业务拉动: 今天撞上的是 showcase 唯一的代码路由答 500;它要做的事声明式已经能做(上面第三条)。平台里 5 处手拼"请求 → 身份"是平台内部的重复,拉动的是一个内部共享函数,不是一个发布给应用作者的 API。
    • ③ 防 AI 犯错: showcase 是 AI 照抄的范本。留着代码路由示例,AI 学到的是"按钮后面挂一段 handler",而且今天这段 handler 连身份都拿不到;换成 flow 端点示例,AI 学到的是"声明端点 + flow",漏不掉鉴权,因为那不在它手里。
    • ④ 创业阶段不扩散: 选 A 要给 @objectstack/runtime 加一个公开导出并长期维护一个"应用代码路由的身份合同"(匿名入口、鉴权关卡两处待定),为一个在树里只有一个使用者的面扩公开面;退役这个应用面只删一个示例、改三页文档一句话,不新增任何能力。

    推荐:C′(在卡上 C 的基础上,把"代码挂载路由"作为应用作者面一并退役),而不是 A

    1. showcase: the "Recalculate Estimate" / "Recalculate Selection" api actions answer 500 — recalc-endpoint.ts calls ctx.ql.update with no execution context, which the security layer refuses as a principal-less write #23128 本身:showcase 的重算改成 apis: flow 端点(/api/v1/apps/showcase/recalc,authRequired: true),flow 用 assignment + update_record 算并写 estimate_hours,两个 api action 的 target 改到新路径;删除 recalc-endpoint.ts 和它的 kernel:ready 挂载。三条锁定测试不变:管理员 200 且重算、无编辑权者被安全层拒绝(403 信封,不是 500)、批量同形。这是 triage 裁定的"以调用者身份写入"的声明式实现,只是落点从 recalc-endpoint.ts 换成 flow + apis:,按卡上「裁后执行」走 pm:retriage 改落点即可,不必重新 triage 方向。
    2. 应用作者面退役:manifest.zod.ts:884 的 retire 文本和它在 metadata.mdx / package-api.mdx / kernel/manifest.mdx 的三处复制,把"需要代码的路由用 http.server 命令式挂载"改成"应用不挂原始路由:声明 apis:;要算的进 flow;要代码的登记为 stack 函数或连接器"。http.server 服务本身不动:平台插件(sharing、storage、settings、auth、cloud-connection…)继续用它造平台的门。
    3. 平台内部的 5 份手拼解析器(rest、runtime、settings、storage、hono-server):合并成一个内部共享函数是对的(A 的 helper 价值在这里),但那是 packages/runtime 的内务卡,p3,不是发布面;showcase: the "Recalculate Estimate" / "Recalculate Selection" api actions answer 500 — recalc-endpoint.ts calls ctx.ql.update with no execution context, which the security layer refuses as a principal-less write #23128 不等它。

    回退:若维护者要保留应用代码路由,则 A(公开 helper)是唯一诚实的做法,因为 B 在示例里手拼鉴权是第 6 份副本;但请把"保留应用代码路由"作为一条明确裁决记下来,它和"应用是元数据定义的"方向相反。

    置信缺口:flow 的 assignment 节点能否直接写日期差 × 8 的表达式(flow-node-expression-paths.ts 的表达式面)由执行席位开工时先在活主机上实测;若表达式面不够,script 节点是否在 17.x 执行也一并实测。两者都不够时退回 A。

  7. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling · letter C′ · the maintainer's word, verbatim: 「同意」 (in the director seat's session, 2026-10-13T09:13Z, on the seat's reading 6142067136)

    What the ruling binds:

    1. showcase: the "Recalculate Estimate" / "Recalculate Selection" api actions answer 500 — recalc-endpoint.ts calls ctx.ql.update with no execution context, which the security layer refuses as a principal-less write #23128 re-lands declaratively. The showcase recalc becomes a flow (assignment computing the window's days × 8, update_record writing estimate_hours) behind an apis: endpoint of type: 'flow' at /api/v1/apps/showcase/recalc with authRequired: true; the two api actions (showcase_recalc_estimate, showcase_recalc_selection) retarget to it; the bulk path runs the same flow per _selectedIds entry (all-or-nothing as the aggregate contract says); examples/app-showcase/src/system/server/recalc-endpoint.ts and its kernel:ready mount are deleted, and src/system/apis/index.ts no longer calls it "the code-mounted counterpart". Triage's three pins stand: an admin's recalc answers 200 and updates the estimate; a caller without edit rights on the row is refused by the security layer (its envelope), not a 500; the bulk path is the same. ⛔ Not a system context (triage 6137971940 holds). If the flow's expression surface cannot write the computation (flow-node-expression-paths.ts), the seat measures whether a script node executes in 17.x; if neither can, it stops and reports, and the fallback is A.
    2. The imperative http.server route is retired as an app-author surface. The retire text of manifest.contributes.routes (packages/spec/src/kernel/manifest.zod.ts:884) and its three published copies (content/docs/references/api/metadata.mdx, package-api.mdx, kernel/manifest.mdx) stop sending authors to a kernel:ready handler: an app declares apis:, computes in a flow, and registers code as a stack function or a connector. The http.server service itself is untouched: platform plugins keep mounting their own doors on it.
    3. The platform's five hand-rolled request-to-context resolvers (rest rest-server.ts:3060, runtime security/resolve-execution-context.ts:150, service-settings :285, service-storage :862/:1247, plugin-hono-server current-user-endpoints.ts:443) are consolidated into one INTERNAL function in @objectstack/runtime: a p3 housekeeping card, not a published export, and showcase: the "Recalculate Estimate" / "Recalculate Selection" api actions answer 500 — recalc-endpoint.ts calls ctx.ql.update with no execution context, which the security layer refuses as a principal-less write #23128 does not wait for it.

    Execution: per this card's 裁后执行 for C, needs-user-decision → pm:retriage in this act, for the triage seat to re-set the landing point as item 1 says (the domain:cli seat re-dispatches from there). Items 2 and 3 are filed by the director seat as two cards to the lanes' queues in this act.

  8. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 13, 2026
  9. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: landing point re-set under ruling C′ (6142279866). domain:cli, priority:p3, into pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T09:54Z. ⛔ Not a claim, ⛔ not a dispatch. Answers the ruling's request to re-set the landing point. pm:retriage is removed in this act.

    • The landing, as ruling item 1 binds it:
      • examples/app-showcase: the recalc becomes a flow (an assignment computing the window's days × 8, then update_record writing estimate_hours).
      • The flow sits behind an apis: endpoint (type: 'flow', /api/v1/apps/showcase/recalc, authRequired: true).
      • The two api actions retarget to it. The bulk path runs the same flow per _selectedIds entry, all-or-nothing.
      • src/system/server/recalc-endpoint.ts and its kernel:ready mount are deleted, and src/system/apis/index.ts's "code-mounted counterpart" wording goes.
    • Pins (unchanged from 6137971940):
      • an admin's recalc answers 200 and updates the estimate;
      • a caller without edit rights on the row is refused by the security layer (its envelope), not a 500;
      • the bulk path is the same.
      • ⛔ Not a system context.
    • Stop condition (the ruling's): if the flow's expression surface cannot write the computation, the claimant measures whether a script node executes. If neither can, it stops and reports, and the fallback is A.
    • Lane: examples/* goes to the subsystem it exercises. Here that is the declarative apis: endpoint and the dispatcher's flow execution, so domain:cli, and the domain:cli seat re-dispatches.
    • Ruling items 2 (retiring the imperative route as an author surface) and 3 (one internal request-to-context resolver) are the director's two cards. ⛔ This card does not wait for them.
  10. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:clipm:queuepriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions