Skip to content

[decision] #19489 落地带出两条裁量分叉:公网明文 AS 要不要自己的告警;裁定的中文日志措辞与 AGENTS.md 语言惯例相抵 #19571

Description

@huangyiirene

Ruled: 5770446134 · letter B/B · 2026-09-22T02:40Z — batch #210 item 5 (D1 B · D2 B); state pm:queue + domain:services

Path: 接一个 Agent(社区版走 MCP) | 决策 | none

由 domain:services 席(session_01AhQASwqJr2Z7XfGWUdvnbF)在 #19489 / PR #19534 的交付中上呈。两条裁量分叉,席位 ⛔ 不自裁。 二者都不阻塞 PR #19534 落地(它按裁定原文执行)。


决策 1 —— 公网明文部署的 AS,要不要有一条属于它自己的告警?

事实(隔离达档复核实测,13 组 boot 真源码驱动)

公网明文 boot(http://example.com)上:

  • OAuth 发现路由仍然挂着 3 条(/.well-known/oauth-authorization-server、/.well-known/openid-configuration、路径插入变体),oauthProvider 的安装只看 resolveOidcProviderEnabled,与传输无关。⚠️ 这是 main 上既有行为,feat(plugin-auth): MCP OAuth over plain HTTP on a private / link-local deployment host #19534 未改。
  • 该部署的日志现在只有一条讲 MCP 的 OAuth track is NOT live,没有任何一行说「你的 AS 正在公网明文提供」。

裁定第 ④ 条逐字

One loud startup warning whenever OAuth is served over plain HTTP(「OAuth 未加密:仅限可信内网」)

⇒ 歧义点:公网明文部署算不算「OAuth is served over plain HTTP」?MCP OAuth 轨是暗的(fail-closed,正确),但 AS 发现面确实在公网上明文提供。

选项

  • A(现状,PR 已如此):资格决定发不发 —— 只有 eligible(loopback/私网)才发那条告警;公网明文只有 OAuth track is NOT live。
  • B(复核建议):资格决定说哪一句而非说不说 —— eligible 时说「接受、仅限内网」;不 eligible 时另发一句「AS 正在公网明文提供,MCP 轨已关闭,请上 TLS」。
  • C:维持 A,另立卡处理「公网明文仍挂 AS 发现路由」这件事本身(那是 main 上的既有形态)。

推荐:B。 理由:裁定要的是「明文服务时有一条响亮告警」,而公网明文恰恰是最该响的那一侧;A 把最危险的那一侧改成了相对安静。B 的成本是一行条件文本,且不改任何准入判定。⚠️ 但 A 也能自圆其说(MCP OAuth 确实没被服务),所以这要你定。

四棱

  • 业务需求:运维要能从启动日志看出「我的 AS 在公网上裸奔」。
  • 长远合理性:告警的存在与否绑到「是否被接受」,会让最危险的配置最安静 —— 这是个会复发的形状。
  • 防 AI 写错:B 让两种部署各有一句确定的话,后续改动更难把某一侧改哑。
  • 创业阶段聚焦:B 是一行条件文本,成本极低。

决策 2 —— 本裁定的中文措辞与 AGENTS.md:31 的语言惯例相抵,要不要修订裁定?

事实

  • 裁定要求日志carries「OAuth 未加密:仅限可信内网」。
  • AGENTS.md:31 逐字:「代码、标识符、提交信息(commit messages)、ADR/文档正文等仓库产物保持现有语言惯例(以英文…)」。
  • 实测:今天 packages/*/src 下(translations 之外)零 CJK ⇒ 落这条串是首例。

⚠️ 本席此前判过「日志走英文」并已撤回(更正 5757931777):那是重裁,不是裁量。PR 现按裁定原文执行。

选项

  • A(现状):按裁定原文,日志行携带该中文串 —— 接受它成为 packages/*/src 的首个 CJK 可执行字符串。
  • B:修订裁定为英文日志行 + 中文原话留代码注释 —— 保持仓库单语,但裁定产物在可观测面上消失(checklist 的 grep 需改锚)。
  • C:双语一行(中文原话在前,英文细节在后)—— 兼顾,但仍是 CJK 首例。

推荐:A,直到你另有裁定。 理由:具体裁定优先于一般惯例,而且这条串是给运维看的告警,不是标识符或提交信息 —— AGENTS.md:31 的列举里没有「面向用户的运行时消息」这一类。⇒ 张力可能是 AGENTS.md 的枚举不够细,而不是裁定错。

四棱

  • 业务需求:告警的读者是中文运维,原措辞是维护者选的。
  • 长远合理性:若采 A,建议 AGENTS.md 补一句把「面向用户的运行时消息」与「仓库产物」分开,否则每次都要重判。
  • 防 AI 写错:现状下两条规则相抵,后续 agent 会反复在这里自裁(本席就是一例)。
  • 创业阶段聚焦:A 零成本。

出处

#19489(裁定原文与验收段)· PR #19534 · 隔离达档复核两轮(第二轮:接受面 83/83 逐行不变、393,216 地址 CIDR 对拍 0 mismatch、13 组 boot 告警矩阵)· 本席更正 5757931777。

Dedupe words: plaintext OAuth public host warning · AGENTS.md language convention runtime message · 19489 ruling wording CJK log · AS discovery routes public plain http


Generated by Claude Code

Activity

  1. os-support-ai commented on Sep 22, 2026

    @os-support-ai
    Collaborator

    Ruling: batch #210 item 5 · letter B/B (D1 B · D2 B) · maintainer 「210 同意」 2026-09-22T02:40Z

    Director seat, summon #26 (session_01SPwf6Kmqo1gqzCSWSuMtQM). Presented with this seat's recommendations B / B from facet ①; the maintainer approved batch #210 whole.

    Governing text: the ruling on #19489, item ④, verbatim 「One loud startup warning whenever OAuth is served over plain HTTP(「OAuth 未加密:仅限可信内网」)」; AGENTS.md:27-31 (chat in Chinese; GitHub artefacts in English; quoted Chinese rulings verbatim; code and repository artefacts keep the existing English convention).

    Ruled D1 (B): eligibility decides WHICH sentence, not whether one is emitted. On a plain-HTTP boot: eligible (loopback / private network) ⇒ the accepted-intranet-only warning as ruled; not eligible (public host) ⇒ a second, distinct loud line stating that the AS discovery surface (the three /.well-known/... routes) is being served over public plain HTTP, that the MCP OAuth track is disabled, and that TLS is the remedy. No admission decision changes. The discovery routes' mounting itself (a main behaviour PR #19534 did not touch) is not altered by this ruling; if the seat judges that mounting a defect, it is a class-① card on its own evidence, ⛔ not folded here.

    Ruled D2 (B): the log line is written in English, the codebase's convention; the maintainer's sentence 「OAuth 未加密:仅限可信内网」 is kept verbatim in the code comment beside it and cited to the #19489 ruling. The checklist's grep anchor moves to the English line. ⛔ No CJK executable string enters packages/*/src; ⛔ no bilingual line. AGENTS.md is not amended — the general convention holds, and the ruling's Chinese wording is read as the sentence's meaning, not its literal encoding.

    State in this stroke: needs-user-decision → pm:queue + domain:services (the ruling names the lane, as batch #157 item 5 did; the code is #19489's). One PR carries D1 and D2. priority:* / type are triage's. PR #19534 is landed and unaffected.


    Generated by Claude Code

  2. self-assigned this
    on Sep 22, 2026
  3. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    CollaboratorAuthor

    Claim: domain:services PM seat · 2026-09-22T07:21Z
    Seat: domain:services#1
    Session: session_01AhQASwqJr2Z7XfGWUdvnbF
    Branch: claude/issue-19571-plaintext-as-warning
    Thread-read: 5770446134
    Clause-②: no

    Clause-② reading, with its citation: SKILL.md:524 is the negative boundary — a runtime permission/security BEHAVIOUR change is not clause ②, it belongs to the manual floor; :525 「条款②只指已发布契约面」. This card emits one additional startup log line and moves a code comment; it neither 放宽接受集 nor 扩大公开面 — no admission decision changes (the ruling says so verbatim), and no published contract surface is touched.

    ⚠️ Standing correction to the dev: the mechanical floor at references/contract-review.md:12 「新导出符号或已发布载荷上的新键恒 yes」 OUTRANKS this reading. If the implementation adds ANY new exported symbol, the declaration flips to Clause-②: yes and a 达档契约复核 is owed before enqueue. Keep the change inside the existing call site and export nothing new, or declare yes.

    ⛔ Zero packages/spec in this lane.


    Generated by Claude Code

  4. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 19571,
      "status": "done",
      "branch": "claude/issue-19571-plaintext-as-warning",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19689",
      "session": "session_01AhQASwqJr2Z7XfGWUdvnbF",
      "premise_still_valid": true,
      "summary": "Implemented ruling batch #210 item 5 (B/B) as one PR. D1: verified on origin/main that the `OAuth track is NOT live` warning sits inside `if (readMcpServerEnabledEnv() && ...)` while the three AS discovery routes mount unconditionally, so a public plain-HTTP boot with OS_MCP_SERVER_ENABLED=false emitted no warning at all; added a sibling `else if` beside the accepted-origin branch (NOT inside the MCP block) emitting a distinct loud line that names the discovery surface and issuer, states the MCP OAuth track is DISABLED, and points at TLS. D2: dropped the CJK prefix from the executable string, kept the maintainer's sentence verbatim in the code comment cited to #19489 + batch #210 item 5, and rewrote the comment paragraph that argued the opposite. No admission decision changed, no transport predicate touched, no route mounting changed, no new exported symbol (`servedOverPlainHttp` is a local const), zero packages/spec. All four carriers moved. The card arrived with assignee huangyiirene already set by the PM; I wrote no assignee.",
      "tests": "pnpm --filter @objectstack/plugin-auth test -> 'Test Files 114 passed (114) / Tests 2439 passed (2439)'. pnpm --filter @objectstack/plugin-auth run typecheck -> exit 0 (first run exit 2 on tsconfig.examples.json only because the package's own dist was absent; built the package, re-ran green; check:test-typecheck OK, 10 files / 94 errors / 23 pinned signatures held). pnpm --filter '@objectstack/plugin-auth^...' build -> VERDICT command-exit 0. pnpm lint (repo-wide `eslint . --no-inline-config`) -> EXIT=0, so no narrowing was needed and none is claimed. Targeted suite src/mcp-oauth-plaintext-notice.test.ts -> 15 passed (15). Derived gate families via `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (66 commands, change set 5 paths vs merge base 97f4f8c82): 63 exit 0, 1 red BY DESIGN, 2 NOT MEASURED. RED BY DESIGN: `node scripts/check-empty-changeset.mjs --base origin/main` exit 1 on carrier 4 - its DELIBERATE CORRECTION class, whose own text says do NOT restore the file and that the correction must be stated on the PR and confirmed by a person; stated in the PR body under its own heading. NOT MEASURED (both print `PREREQUISITE NOT MET`, exit 3, explicitly 'not a pass and not a finding'; both need a full workspace build, which is CI's): `pnpm check:dual-build-cjs-loads`, `pnpm check:type-check-debt`. ABLATION, both legs run from the committed state through scripts/ablation-replace.mjs (blob-hash proof of the on-disk mutation, restore proven by `git diff HEAD` = 0 bytes); subject resolves from src via a relative import so no dist leg applies. (1) `} else if (servedOverPlainHttp) {` -> `} else if (servedOverPlainHttp && false) {`, blob 17bd405104b2 -> eb7d11ae9d01: 9 failed | 6 passed (15), every public-face assertion red; restored, blob == HEAD. (2) CJK prefix re-added to the accepted line, blob 17bd405104b2 -> c7aba397fff2: 1 failed | 14 passed (15) on the no-CJK pin; ablation-replace printed 'ok restored: blob == HEAD (17bd405104b2) and `git diff HEAD` is empty'. Predicted direction was RED for both and RED was observed. Control-character self-scan over all five changed files: no hits.",
      "mcp_calls": "0 - no MCP GitHub tool was called; all GitHub traffic went through the REST proxy with curl.",
      "api_writes": "2 - POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/19571/comments (this report). ZERO label writes: the dispatch word named no label and `skip-changeset` does not apply (a changeset was added), so the writable set was empty. 4 git pushes on the branch (empty-branch routing probe, 2 WIP, 1 final).",
      "open_questions": [
        {
          "question": "check:empty-changeset is red by design on carrier 4 (.changeset/19489-oauth-private-host-transport-rule.md, present on the merge base and changed by this PR). The gate refuses to go green without a human word; its text forbids the one mechanical fix (restoring the base copy) because that republishes a sentence this PR makes false. Who gives that word?",
          "options": [
            "A - a person confirms the correction on PR #19689, naming the note, and the gate stays red until merge as the gate intends",
            "B - drop the carrier-4 edit, let the old changeset ship its now-false sentence, and correct it in a separate release-note PR after landing",
            "C - delete the old changeset and fold its content into the new one - the gate calls this 'the same act one step further' and refuses it"
          ],
          "recommendation": "A, because the dispatch word ordered this carrier explicitly and the gate's own text names exactly this path ('say so on the PR, naming the note and what changed under it, and get it confirmed'). B ships a release note asserting the startup line carries the Chinese and that a public plain-HTTP boot gets no such line - both false the moment this lands. C is refused by the gate."
        }
      ],
      "out_of_scope_findings": [
        "carrier: PR #19689 itself (Acceptance notes) - the AS discovery routes (/.well-known/oauth-authorization-server, /.well-known/openid-configuration and the RFC 8414 3.1 path-insertion variant) are mounted regardless of transport, so a PUBLIC plain-HTTP deployment publishes authorization-server metadata in the clear even though isOAuthEligibleBaseUrl refused the origin. The ruling reserves this as a class-a card on its own evidence and forbids folding it here. I did NOT file it: I have no evidence it is a defect rather than a deliberate posture - the AS is genuinely live for browser SSO (resolveOidcProviderEnabled is transport-independent by design), so the documents are truthful, and what is wrong is the deployment, not the code. Noted in the PR's Acceptance notes and in the code comment; handing the judgement back rather than making it. Dedupe words: `AS discovery routes mounted public plain http` / `well-known unconditional transport` / `resolveOidcProviderEnabled transport-independent`."
      ]
    }

    Generated by Claude Code

  5. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    CollaboratorAuthor

    Review — ACCEPT on the code; landing HELD on two things, one of which needs a person

    Reviewed-by: domain:services PM seat · session_01AhQASwqJr2Z7XfGWUdvnbF · 2026-09-22T08:05Z
    Implemented-by: PR #19689 @ 629178648a

    Reviewed against GitHub and against origin/main, ⛔ not against the report's self-narrative.

    Verified, each by its own read

    • Shape. Draft, base main, first line Fixes #19571. Whole-body closing-keyword scan: one hit, Fixes #19571; the other refs (#19489, #19534, batch #210) carry no keyword. Fixes is right here — the card is implemented whole, so merging should close it.
    • Scope. 5 changed files read from the API: the four carriers the dispatch word named, plus the new changeset. ⛔ No content/docs/releases/**. ⛔ No packages/spec. Nothing unrelated to the card.
    • Changeset. @objectstack/plugin-auth has no private key ⇒ published ⇒ a changeset is owed, and .changeset/19571-plaintext-oauth-public-host-notice.md (patch) is present.
    • No new exported symbol. Grepped every + line in the full diff for export: the only hit is prose inside a changeset. servedOverPlainHttp is a local const. ⇒ the mechanical floor (contract-review.md:12) is not tripped and the claim's Clause-②: no stands as declared.
    • D1, structurally. On the branch the new branch is at auth-plugin.ts:3180; the MCP condition opens at :3237. The new line is therefore outside it and fires independently of OS_MCP_SERVER_ENABLED — which was the whole defect: the public plain-HTTP boot with the MCP surface off emitted nothing at all. ⛔ No transport predicate touched; ⛔ the .well-known mounts at :3216-3217 are byte-unchanged.
    • D2. The CJK prefix is out of the emitted string; the ruled sentence 「OAuth 未加密:仅限可信内网」 is kept verbatim in the comment beside the call, cited to dev mode: os dev accepts plain-HTTP OAuth for MCP on any host (loud warning); production keeps TLS-required with no switch — the need behind fork PR #19342 #19489 item ④ and batch Fix fumadocs-mdx validation: flatten nested pages in concepts meta.json #210 item 5. The comment paragraph that argued the opposite is rewritten rather than left contradicting the code under it.
    • The checklist edit is coherent, not a half-edit. Title, step, clause, verify, the negative conflation entry, source and a revision: 2 history entry all moved together, and the new verify states outright that grepping the old Chinese now scores 0 on every boot. That sentence is the item's own false-green guard — it is the difference between this edit and one that quietly turns a passing run into a miss.
    • Positive control. The ablation ran both legs from the committed state with blob-hash proof and a verified byte-identical restore; both went RED as predicted (&& false on the new branch: 9 of 15 red; CJK prefix re-added: the no-CJK pin red). ⇒ the green is a real green, not a dumb-instrument green.

    Hold 1 — CI has not converged

    At head 629178648a: Lint & Repo Gates, the four Type Check · * jobs, Test Core (1..6/6), Build Core and the Dogfood gates are all still in_progress. Per the review rules that is an honest reading at draft time, ⛔ not a rework reason. ⛔ Not flipped to ready, ⛔ no auto-merge attached, ⛔ not enqueued.

    Hold 2 — Check Changeset is red, and the gate wants a PERSON

    Check Changeset = failure at this head. I read the gate's own source on origin/main rather than taking the report's word for it. scripts/check-empty-changeset.mjs separates two classes, and this is the DELIBERATE CORRECTION one:

    FOREIGN_CORRECTION_REMEDY = 'do NOT restore it -- say so on the PR and get it confirmed'

    and, in the body it prints: "there is no second command to run … say so on the PR, naming the note and what changed under it, and get it confirmed. That is the existing human path; … a person instead of routing around it." Deleting the old changeset is refused explicitly — "the same act one step further."

    The PR body already carries the seat-side half under its own heading, naming the note (19489-oauth-private-host-transport-rule.md) and what changed under it. The remaining half is a person's confirmation, and ⛔ it is not this seat's to supply — the gate names a human on purpose.

    ⚠️ Owning the cause: this carrier was in the dispatch word because I put it there. The alternative was to leave #19489's pending note asserting that the startup line carries the Chinese and that a public plain-HTTP boot gets no such line — both false the moment this lands, in a note that ships. I still think correcting it is right, but the red gate is a consequence of my instruction, not of the dev's judgment.

    Out-of-scope finding — handed back, ⛔ not filed

    The dev reports that the three .well-known AS-discovery routes mount regardless of transport, so a public plain-HTTP deployment publishes authorization-server metadata in the clear. It declined to file it and handed the judgement over, which is the right call: the ruling reserves that question as a class-① card on its own evidence, and the dev's counter-reading is serious — the AS is genuinely live for browser SSO (resolveOidcProviderEnabled is transport-independent by design), so the documents are truthful and what is misconfigured is the deployment. ⇒ It needs evidence before it is a card. I am not filing one on this reading. Dedupe words carried forward: AS discovery routes mounted public plain http · well-known unconditional transport · resolveOidcProviderEnabled transport-independent.

    Landing to-do recorded now, ⛔ not left to the next patrol

    Once CI is green and the changeset correction is confirmed: flip ready → attach auto-merge only after mergeable_state settles → confirm added_to_merge_queue → landing is delivery on origin/main with parent count 1, ⛔ never the PR-closed event. Fixes closes the card automatically; pm:dispatched is then stripped by hand and read back.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions