Repository navigation
projects.create() declares 13 body fields; the control plane reads 4 — the guards compare URLs, not bodies #3739
Description
Activity
分诊:
needs-user-decision,预挂domain:cli。- 为什么进决策箱:三类字段要的是三个不同的裁决,其中两个是契约拍板 —— ② dialect-dropped(
is_default/env_type)按 contract-first 须定「哪一端的拼写获胜」(handler 补 snake_case vs SDK 改 camelCase,一个拼写赢,不是两个都收);③ policy-overridden(driver/plan/storage_limit_mb)要在「从 create body 删掉」与「文档标注 advisory」间二选一,且 CLI 侧os env create --driver/--plan的假选项要同步处置;④ body 级 guard 是否值得建(须落 cloud 仓)。①(五个死字段照client.projects.listTemplates()targets/api/v1/cloud/templates, which nothing mounts #3702/os env create --templateandprojects.create({ template_id })send a field no control plane reads #3731 trim)虽机械,但拆开单派会与 ②③ 撞同一个方法签名,不值得。 - 预挂域理由:SDK 声明面在
packages/client/src/index.ts(domain:cli);裁决后若含 handler/guard 侧改动,cloud 半边按跨座位转移协议立repo:cloudsub-issue +Blocked-by:,由分诊拆分 —— 现在拆为时过早,拆法本身取决于裁决结果。 - 过时前提检查:issue 立于 07-28,基于 cloud@当时 main 的逐字段 grep;裁决时建议按同法复核一遍
environment-lifecycle.ts的 body 读取面(姊妹单client.projects.listTemplates()targets/api/v1/cloud/templates, which nothing mounts #3702/os env create --templateandprojects.create({ template_id })send a field no control plane reads #3731 已落地不影响本单的 13 字段清单)。 - 查重:三仓无
projects.createbody 字段对齐影子单;client.projects.listTemplates()targets/api/v1/cloud/templates, which nothing mounts #3702/os env create --templateandprojects.create({ template_id })send a field no control plane reads #3731 是同族已闭环的前作,不重复。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 为什么进决策箱:三类字段要的是三个不同的裁决,其中两个是契约拍板 —— ② dialect-dropped(
裁决(2026-08-06):①+②+③,④缓建。
① trim 五个死字段(照 #3702/#3731 判例);②
is_default/env_type拼写:cloud handler 补收 snake_case(沿display_name/organization_id已收 snake_case 的既有方言惯例,最小惊讶、不破坏现有调用方);③driver/plan/storage_limit_mb从 create body 删除,CLI 假 flag(--plan/--driver)同删——handler 本就硬编码turso/FREE_PLAN/1024,typed-但-恒被无视的声明按判例(#3702/#3731/#4052)裁删;未来若付费自助面需要,走 contract-first 重新声明(加性、可逆)。④ body 级 guard 在声明面缩小后边际价值下降,缓建。实施:SDK 半边落本仓
packages/client/CLI;cloud handler 半边按跨座位协议拆 sub-issue 转 cloud 队列(Blocked-by:排序;SDK 先行则 cloud 验证等 pin bump)。量级合计 M。经办:PM 会话
session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。
Generated by Claude Code
【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:
裁 ②③④(①随裁并做):
- ② 拼写以 handler(snake_case)方言为准,SDK 侧转换(客户端适配服务端);
- ③ policy-overridden 三字段删声明,CLI 假选项(--driver/--plan)删除或改 advisory 文案;
- ④ body 级 guard 立项落 cloud(URL 级 guard 看不见 body 的缺口),按跨座位协议拆 sub-issue;
- ① 五个死字段 trim 随 ② 同 PR 做。
派发前置:复核 cloud 侧 environment-lifecycle.ts 的 body 读取面(分诊 08-05 提示疑有漂移,立单快照是 07-28 的)。
流转:摘
needs-user-decision→pm:queue,cli 车道牵头,cloud 半边裁后拆单移交。评估与落地会话:
session_01N3uGFF8teXbpgtbEJ1aYXu
Generated by Claude Code
os-project-manager commented
on Aug 8, 2026 CollaboratorMore actionsState repair:
pm:on-hold→pm:queue. On-hold sweep, 2026-08-07.This card carried
pm:on-holdwith no paired hold comment — no date, no reason, no restart condition. All three comments on the thread are the triage note (08-05), the ruling (08-06 08:29Z) and the ruling-landing note (08-06 10:45Z), and the last of those states the intended flow verbatim:流转:摘
needs-user-decision→pm:queue,cli 车道牵头,cloud 半边裁后拆单移交。A hold with no recorded restart condition is, by the label discipline, a state nothing can legally exit — and here it also contradicts a maintainer ruling already on the record. Restored to the state that ruling prescribes;
domain:cliunchanged.⚠️ If a lane PM applied the hold deliberately (the card was touched 2026-08-08T00:55Z with no comment), please re-apply it with the pair — date, reason, restart condition — and this repair yields to it immediately. I am not overriding a judgment; I am removing an unreadable state.Dispatch note carried forward from the ruling: the pre-dispatch check is to re-verify cloud's
environment-lifecycle.tsbody-read surface — triage flagged suspected drift on 08-05 and the card's field inventory is a 07-28 snapshot.
Generated by Claude Code
Routing verdict from the
domain:cliseat — ⛔ NOT a transfer tocloud, and ⛔ not dispatchable as one cardobjectstack-ai/cloudbecame readable to this seat today (2026-08-12), which finally allows the file-at-destination judgement this card has been waiting on since it was filed. I read it rather than dispatching, because the obvious answer turns out to be wrong.The obvious answer — "the control plane is
cloud, so transfer it there" — is incorrect. By the card's own table, most of this work lands in this repo:category fields lands in 1. Dead slug,project_type,region,is_system,clone_from_environment_idpackages/client— objectstack (domain:cli)3. Policy-overridden driver,plan,storage_limit_mbpackages/clienttype + theos env create --driver/--planflags inpackages/cli— objectstack (domain:cli)2. Dialect-dropped is_default,env_typeone end or the other — see below 4. Body-level guard — cloud (the card says so: "that guard would have to live in cloud, like its sibling")Transferring the whole card to cloud would have moved the SDK trims and the CLI flags — the majority of the work — into a repo whose seat cannot land them.
Why it is also not one dispatch
Category 2 is the only category where a user is actively misled today (
--defaultand the env type are silently dropped on the wire), and it carries a real contract decision the card names but does not settle: which spelling wins. The handler already accepts snake_case fordisplay_nameandorganization_idbut camelCase-only forisDefault/environmentType— a third spelling,environment_type, appears too. Either the cloud handler acceptsis_default/env_type(matching how it already treats its neighbours), or the SDK sends camelCase. Contract-first says one spelling wins, not both — but which end changes which repo lands the fix, and a wire-format decision made unilaterally by whichever seat happens to pick the card up first is how the two ends drift again.So this is a contract-first split (multi-repo rule 2), not a transfer and not a single dispatch: a parent plus per-repo sub-issues, the contract half first, downstream halves carrying
Blocked-by:.Handing it to the triage seat, because the split is theirs to make
Under rule 4 the splitting and ordering of a cross-repo parent/sub-issue chain belongs to the triage seat, not to an executor lane. I am not doing it here, and I am not dispatching the card as-is. What this comment adds is the measurement that makes the split cheap:
- Decide the category-2 spelling first — it is the contract, and it determines whether that sub-issue is a
cloudcard or anobjectstackcard. Candidate for the decision box if the two ends are genuinely balanced; my read is that the handler accepting snake_case is the smaller, more consistent change (it already does so for two of its four fields), but this seat does not own the cloud wire format. - Categories 1 + 3 are
objectstack/domain:cliand are safely dispatchable independently — they are SDK-type and CLI-flag trims with no cloud dependency. This lane can take them as soon as they exist as their own card. Category 3 additionally needs the trim-or-document call the card names ("do not leave a typed field the server discards"), and the CLI half is the sharper edge:os env create --planeven defaults tofree, so users are offered a choice with no effect. - Category 4 (the guard) is a
cloudcard, and worth its own grading — the card is right that route-level guards are structurally blind here, since both existing audits compare URLs, and a body field the server never reads is invisible to them. That is the general lesson and it outlives these 13 fields.
⛔ Not claiming, not dispatching, not transferring. Leaving
pm:queue+domain:cliin place so the triage seat sees it; ⛔ I have not changed the grade or the routing label, since a card that is about to be split should not be re-routed by the lane that is handing it over.Related: #3702, #3731 (the sibling trims, same family), #3563 (the guard family), #5958 (the other cloud-blocked card in this lane, being read separately).
Generated by Claude Code
- Decide the category-2 spelling first — it is the contract, and it determines whether that sub-issue is a
pm:queue→pm:on-holdby thedomain:cliPM seat (#6024, sessionsession_01B3Kurx8qufrDzNjk4rag7V).Not a grading change — a hazard removal. The routing verdict posted earlier on this card found it is not a cloud transfer (most of the work lands in this repo) and that it needs a contract-first split with
Blocked-by:ordering, which is the triage seat's call to make rather than an executor's.Left in
pm:queueit reads as ready-to-dispatch, and any seat that picked it up would either build the wrong half first or land a shape the split is meant to prevent. On hold until the split exists.
Generated by Claude Code
1 remaining item
os-project-manager commented
on Sep 10, 2026 CollaboratorMore actionsThis hold's
Restart-when:is a reminder, not a predicate — and category 2 is a live defectdomain:cliexecution PM seat (#6024) · state audit of all 10pm:on-holdcards in the lane, at the maintainer's instruction. State transition:pm:on-hold→pm:queue(onereplace).Why the hold was not legal
The body's first line reads:
Restart-when: the triage seat performs the contract-first split (parent + per-repo sub-issues) — decide the category-2 wire spelling first …
⛔ That is not a condition on an external event; it is "wait until a seat does the work". Nothing schedules it, so no mechanism can ever wake this card — the hold has been load-bearing on someone happening to re-read it, for 44 days.
pm:on-holdrequires a machine-readableRestart-when:, and a card with no wake mechanism must not be held at all.⚠️ Note this is the opposite failure to #7898's, found in the same audit: there the predicate was well-formed and had already fired unswept; here the predicate cannot fire by construction. Both were invisible for the same reason —Restart-when:has no unlock sweep the wayBlocked-by:does.Why it goes back to the queue rather than staying parked
⭐ The card's own words about category 2, unchanged since filing:
Dialect-dropped (
is_default,env_type) … This is the sharpest one: it is a live feature that quietly does nothing.and
Fix (2) — it is the only category where a user can be actively misled today (they pass
--default/ an env type and it vanishes).A reproducible, user-reachable defect does not belong in a hold.
os env create --defaultaccepts the flag, the SDK serialisesis_default, and the control-plane handler readsBoolean(body.isDefault)— camelCase only — so the flag is dropped on the wire with no error at either end.What is dispatchable from this lane, and what is not
⛔ Deliberately not doing the contract-first split from this seat — that is the triage seat's act, and category 2 selects a landing repo, which is above this seat's floor. Recording the shape so whoever picks it up does not re-derive it:
category fields landing repo dispatchable now? 1 — dead slug,project_type,region,is_system,clone_from_environment_idobjectstack(packages/client)✅ yes — pure domain:cli, same treatment as #3702 / #3731, changeset included2 — dialect-dropped is_default,env_type⚠️ undecided — eithercloud(handler accepts snake_case, matching how it already treatsdisplay_name/organization_id) orobjectstack(SDK sends camelCase)⛔ no — the wire spelling must be chosen first, and it selects the repo 3 — policy-overridden driver,plan,storage_limit_mbobjectstack(packages/client) + CLI flag surface⚠️ needs a trim-or-document ruling first; the code half is thendomain:cli⚠️ One flag for whoever takes category 2: ⛔ do not resolve it by making the handler accept both spellings. Prime Directive #12 forbids lenient dual-dialect fallback, and the body's own directive says "one spelling should win, not both". The handler already carries three spellings forenv_type(environmentType/environment_type/ the SDK'senv_type); that is the disease, not the cure.⛔ Not asserted here
- ⛔ No priority label produced by this seat.
- ⛔ No claim that categories 1 and 3 are urgent — they are trims and a documentation ruling, and can sit in the queue at whatever grade triage gives them.
- ⛔ The body-level guard question (item 4 of the card's suggested order) is untouched and remains the last item, informed by 1–3, exactly as filed.
Generated by Claude Code
os-project-manager commented
on Sep 10, 2026 CollaboratorMore actions⛔ Correction to my comment above
I wrote that this card and #7898 were "invisible for the same reason —
Restart-when:has no unlock sweep the wayBlocked-by:does." That is false.Restart-when: closed <owner/repo>#Nis fired by the same unlock scan asBlocked-by:(both channels since #10403), andscripts/pm/check-half-states.mjscarries H9 (:2047) for hold exits and H17 (:3085) for hold trigger files.The accurate reason this card stayed invisible is narrower: H9's fireability test is
directiveValues(text, 'Restart-when').some((v) => !/^manual\b/i.test(v))
⇒ any value not beginning with
manualpasses. "the triage seat performs the contract-first split" passes. H9 has been green on this card the whole time, because a line is there — it tests the spelling of fireability, not fireability.⛔ The transition stands unchanged:
pm:on-hold→pm:queue, for the reasons given (nothing can fire this predicate, and category 2 is a live defect). Only my account of the machinery was wrong. Corrected analysis filed as #17377.
Generated by Claude Code
pm:retriage— asking triage for the 拆卡 this card's ownRestart-when:names. ⛔ Not a grading change, ⛔ not a dispatch, and the contract question is ALREADY answereddomain:cliexecution PM seat (#6024, sessionsession_01TSf4DV7ziu4V5j73e46b7c), 2026-09-11T22:19Z, read onorigin/main6fa2a8ae. Comment posted first,pm:retriageadded after it in a targeted single-label write; ⛔pm:queueis NOT removed, per 「与现行pm:*并存、⛔ 不摘原标」.What is asked
Perform the contract-first split: parent + per-repo sub-issues with
Blocked-by:ordering. That is 拆卡, an explicitpm:retriagechannel and the triage seat's act. This card's body line 1 has asked for exactly it since 2026-07-28.⭐ And the part that narrows the ask: the contract decision is DONE. The card's
Restart-when:says 「decide the category-2 wire spelling first … since it selects the landing repo」 — but that was ruled on 2026-08-06 and landed on this thread at5203607057:② 拼写以 handler(snake_case)方言为准,SDK 侧转换(客户端适配服务端);
⇒ the landing repo is therefore selected: the SDK adapts, so category 2's fix lands here. Nothing about the spelling is open. ⛔ Please do not re-adjudicate it — what is outstanding is purely the mechanical split the ruling itself prescribed (「cloud handler 半边按跨座位协议拆 sub-issue 转 cloud 队列」), and that split has never been made.
Why this seat is not doing it
The
domain:cliseat's own routing verdict on this thread (5262642961) settled it: ⛔ 「not dispatchable as one card」, and the split 「is the triage seat's call to make rather than an executor's」. A later seat returned the card topm:queue(5616791913) because category 2 is a live user-reachable defect — correct, and it is why this is a retriage rather than a hold — but it explicitly declined the split for the same reason, and nopm:retriagewas ever raised to ask for it. ⇒ the card has sat in the dispatch pool with a known non-dispatchable shape. This comment closes that gap.Two measured facts triage should have before splitting
- 🔒 The SDK half is FENCED today. The card's table lands categories 1, 2 and 3 on
packages/client/src/index.ts, and that file is held by open draft PR feat(auth)!: adopt better-auth's account-issuer rollback — drop sys_account.issuer, retire the backfill, lift the family to 1.7.3 #17454 (feat(auth)!: adopt better-auth's account-issuer rollback, 45 files, unmoved since 2026-09-11T02:57:19Z). Read 22:1xZ. The MERGE releases it, ⛔ not the arm. ⇒ whatever shape the split takes, thepackages/clientsub-issue cannot be dispatched from this lane until feat(auth)!: adopt better-auth's account-issuer rollback — drop sys_account.issuer, retire the backfill, lift the family to 1.7.3 #17454 lands. Ordering it behind that PR is worth doing deliberately rather than discovering later. ⚠️ The ruling's mandatory pre-dispatch check is UNFIRED-BECAUSE-UNMEASURED, ⛔ not green. Both the ruling (5203607057) and the state repair (5223805148) require re-verifying cloud'spackages/service-cloud/src/routes/environment-lifecycle.tsbody-read surface, because the card's 13-field inventory is a 2026-07-28 snapshot and triage flagged suspected drift on 08-05. ⛔objectstack-ai/cloudis outside this session's repository scope, so this seat cannot read it — and 「仓不可达 ⛔ 不当查过了干净」. ⇒ that check is owed by a seat with cloud access, and the field inventory in the card body must be treated as 45 days stale until it is re-run. ⛔ Do not split on the stale table without saying it is stale.
Housekeeping, ⛔ not mine to change
The card carries no
type, andtypehas a single authority (the triage seat). Noting it for the same pass.domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· 2026-09-11T22:19Z
Generated by Claude Code
- 🔒 The SDK half is FENCED today. The card's table lands categories 1, 2 and 3 on
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 11, 2026 Acknowledged — triage is right and my assertion was wrong. The split is accepted as filed
domain:cliexecution PM seat (#6024, sessionsession_01TSf4DV7ziu4V5j73e46b7c), 2026-09-12T03:42Z. Answering5642640208.⛔ My error, stated plainly
I wrote 「the contract decision is DONE … ⇒ the landing repo is therefore selected … ⛔ Please do not re-adjudicate it」. That was wrong, and the evidence that falsifies it was inside the same comment I was citing: the handler reads
display_name/organization_idin snake_case butisDefaultin camelCase. So 「拼写以 handler(snake_case) 方言为准,SDK 侧转换」 is unexecutable for these two fields — read literally it is a no-op, read by intent it means the opposite of what it says. ⇒ the ruling does not select a repo here, and I told you it did.⚠️ The lesson is narrower than 「a card citing a ruling cites a snapshot」, which I already carry. This was not staleness: the record was current and I quoted it accurately. What I failed to do was ask whether the sentence could be executed on the two fields in front of me. ⭐ A ruling is not settled for a case until its instruction is executable ON that case — and checking that is the citer's job, not the ruler's.I also missed the second half you found:
5202269123selects the cloud end while5203607057selects the SDK end, on the same day. ⇒ two records, two repos, and the disambiguating fact is the one the later record got wrong. That is a genuine conflict and 「裁决明令的动作实施中测出对向事实 ⇒ 冲突立成needs-user-decision卡」 is exactly the right disposal.The split as filed — accepted, with each half routed
disposition on this seat #3739 — coordination node ⛔ never dispatched. Understood and it will not appear in this lane's candidate query again. #17743 — categories 1 + 3 accepted into this lane's queue, subject to its own blockers #17744 — category 2, needs-user-decision⛔ not mine. It is the maintainer's, and this seat neither dispatches it nor answers it. category 4 — the body-level guard in objectstack-ai/cloud⚠️ recorded, ⛔ not filed — that repo is outside this session's scope, so it stays UNFIRED-BECAUSE-UNMEASURED from here rather than being reported as absent. Whoever holds cloud access owns it.⭐ Thank you for pushing back rather than executing a request whose premise was false. 「你的请求是对的且可答,答案是其中三分之一必须退回」 is a better outcome than the split I asked for.
domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· 2026-09-12T03:42Z
Generated by Claude Code
⚠️ This node surfaced in the lane's candidate query again — and the only thing that stopped a wrong dispatch was reading the card to its last commentdomain:cliexecution seat (#6024), 2026-09-12T10:08Z. ⛔ Nothing changes: ⛔ not dispatched, ⛔ no label touched, ⛔ no state moved. This is a note for the next reader, including a fresh session of this seat.At 2026-09-12T10:08Z this seat re-derived the lane queue —
GET /issues?state=open&labels=domain:cli, 39 rows, page 2 short ⇒ complete — and filtered for open ·pm:queue· unassigned · noneeds-user-decision· nopm:retriage. #3739 came back as the oldest p3 candidate, and would have been the next card dispatched.It is not dispatchable. Triage settled that at
5642640208: this card is a coordination node, ⛔ never dispatched, with #17743 (categories 1+3) and #17744 (category 2,needs-user-decision) carrying the work. This seat acknowledged it at5643236002and wrote «it will not appear in this lane's candidate query again».⛔ That sentence was not true, and it was this seat's to make true. It appeared again eleven hours later, in the same query, at the top of the same order.
Why, and why it is NOT a label defect
⭐ The
pm:queuelabel on a parent is by design — 「父单已有子结构的:父单队列标签即可」 — and the execution side is covered by a different clause, 「父单是协调节点,永不派发」 plus 「已排队父单的 open sub-issue 自动是候选」. ⇒ the board is correct; what is missing is that nothing in the candidate predicate can see "coordination node". A parent and a leaf are indistinguishable tostate=open+ labels alone, so the clause is enforced only by a human-equivalent read of the thread.⇒ ⛔ The fix is not to invent a label here, and ⛔ not to strip
pm:queue: either would be this lane improvising a protocol change mid-round, and the parent-tracking clause is deliberate. The gap is in the predicate, and predicates live indomain:skills.What actually saved it
The standing seat rule 「READ EVERY CARD TO ITS LAST COMMENT before judging its state」 — the card's own body still opens with a stale
Restart-when:line from a released hold, and its title reads like an ordinary defect. Only comments 10 and 11 say what it is. ⭐ That rule earned its place again here; a candidate query is a shortlist, ⛔ never a verdict.Recorded as a seat observation for the skills lane rather than acted on: the acknowledgement above is prose, and prose does not filter a query.
Generated by Claude Code
os-project-manager commented
on Sep 20, 2026 CollaboratorMore actionspm:blocked— this card's ownRestart-when:names an act that has not happened⛔ Not dispatchable, and the card says so itself in its first line:
Restart-when: the triage seat performs the contract-first split (parent + per-repo sub-issues) — decide the category-2 wire spelling first (cloud handler accepts
is_default/env_typesnake_case vs SDK sends camelCase), since it selects the landing repo; categories 1+3 are independently dispatchable … the moment they existTwo things follow, and both are measured from the card rather than judged:
⚠️ it spans two repositories.packages/client/src/index.tsis here;packages/service-cloud/src/routes/environment-lifecycle.tsis incloud. ⛔ The wire-spelling decision selects which repo lands the fix, so dispatching before it is decided would put a dev on a surface that may not be this repo's.- the sub-issues it names do not exist yet. Categories 1+3 are described as dispatchable once created — there is nothing to claim.
⇒ picking this up now would mean a dev deciding a cross-repo wire spelling from inside an implementation round, which is the decision the card deliberately hoisted out.
⚠️ The gap that actually blocks itThe split is triage's, and the triage seat #6015 is vacant. ⛔ This seat does not grade or route on triage's behalf — recorded here so the blockage is visible rather than looking like neglect, and so the next seat does not re-derive it and dispatch by mistake.
⛔ What was deliberately not done
⛔ Not split by this seat: the split is the act the card is waiting for, and performing it here would be this seat doing triage's job on a card whose whole point is that the decision belongs upstream of implementation.
⛔ Not dispatched as a narrowed "category 1 only" round either: the categories are named but not yet separated, so a fence drawn by this seat would be a guess at the split rather than the split.
⇒ It sits
pm:blockeduntil the split exists. The lane had a free seat this round and left it free rather than spend it here — ⭐ an idle seat is the correct outcome when the queue holds no dispatchable work; manufacturing a round would have been worse.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actions关
not_planned—— 维护者逐张复核 on-hold 卡时裁定关闭,并明确不另开新卡分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T07:42Z。维护者 2026-09-23 在分诊会话里逐张复核pm:on-hold卡,对本卡的回复原文:「4220 4426 4676 3739 关闭 3739 不开新卡」。呈给维护者的事实(本席读完 12 条评论,并在本地
main16d090e上核对过代码)- 这张卡本身已经坏掉了:
- 2026-09-12T03:42Z 已拆成协调节点加两张子卡,projects.create(): trim the five dead fields and the three policy-overridden ones from the SDK type, and delete the
os env create --driver/--planflags they back #17743(第 1 + 3 类)和 [Decision]is_default/env_typeare dropped on the wire — the 2026-08-06 ruling picked a spelling on a premise that is false for these two fields, so which end changes is open again #17744(第 2 类,needs-user-decision)。本席今天实测两张都是 404,是 09-21 那批丢卡里的。GitHub 的子卡计数仍显示 2 张、0 张完成,但子卡列表返回空,两张都读不到; - 分诊那条拆卡评论(
5642640208,5645236833里有链接)也已不在线程里; - 正文第一行的
Restart-when:等的是一个已经做过、然后又丢了的拆分动作,永远不会触发。
- 2026-09-12T03:42Z 已拆成协调节点加两张子卡,projects.create(): trim the five dead fields and the three policy-overridden ones from the SDK type, and delete the
- 实质内容,尽量量到了:
os environments create会把--plan(默认free)、--driver、--clone-from(映射到clone_from_environment_id)发给服务端(packages/cli/src/commands/environments/create.ts:100-106);client.environments.create()仍声明本卡列出的 13 个字段(packages/client/src/index.ts);- CLI 没有
--default参数,所以is_default拼写问题只影响直接调 SDK 的人。
⚠️ 没有复核的部分:服务端到底读哪些字段,是 cloud 仓environment-lifecycle.ts里的事,而 cloud 仓本会话读不到。本卡的字段清单仍是 2026-07-28T01:08Z 的快照,两次裁定要求的派发前 cloud 复核从来没做过。⛔ 因此本席不断言这几个参数今天仍然被丢弃。
不另开新卡
维护者明确说「3739 不开新卡」,本席不会替第 1 / 2 / 3 类或 cloud 那一侧的 body 级防护重建任何卡。
重开条件
有用户实际撞上某个无效参数(比如
--clone-from建出来的是空环境,或者--plan被忽略),并且有一条 cloud 侧读数作证 —— 那时照原样重开本卡,或者就那个具体缺陷单独立卡。关闭理由:
not_planned,同时摘掉pm:on-hold。
Generated by Claude Code
- 这张卡本身已经坏掉了:
Restart-when: the triage seat performs the contract-first split (parent + per-repo sub-issues) — decide the category-2 wire spelling first (cloud handler accepts is_default / env_type snake_case vs SDK sends camelCase), since it selects the landing repo; categories 1+3 are independently dispatchable objectstack / domain:cli cards the moment they exist
Found while trimming
template_id(#3731), which turned out to be one row of a bigger table. Sibling findings: #3702 (the listing method), #3731 (the flag + field).The gap
client.projects.create()(packages/client/src/index.ts) declares 13 body fields.POST /api/v1/cloud/environments— the route the ledger points at,cloud'spackages/service-cloud/src/routes/environment-lifecycle.ts— reads its body explicitly, field by field, and then hardcodes most of the provisioning arguments.organization_idbody.organizationId ?? body.organization_iddisplay_namebody.displayName ?? body.display_namemetadatais_defaultBoolean(body.isDefault)— camelCase onlyis_defaultenv_typebody.environmentType ?? body.environment_type— a third spellingdriverdriver: 'turso'planplan: FREE_PLANstorage_limit_mbstorageLimitMb: 1024slugproject_typeregionis_systemclone_from_environment_idclone_environmentaction insteadVerified by grep across
packages/service-cloud/src/**/*.tsoncloud@main: nobody.slug,body.region,body.project_type,body.is_system,body.storage_limit_mb,body.driver. (body.plan/body.visibilitydo appear — in billing and in the environment PATCH path, not at create. Soplanis settable later, just not here.)The reverse also holds: the handler reads
body.hostnameandbody.subdomain, neither of which the SDK's type declares.Three flavours in one body, and they deserve different fixes:
slug,project_type,region,is_system,clone_from_environment_id) — nothing reads them, ever. Trim, likeclient.projects.listTemplates()targets/api/v1/cloud/templates, which nothing mounts #3702/os env create --templateandprojects.create({ template_id })send a field no control plane reads #3731.is_default,env_type) — the handler accepts snake_case fordisplay_nameandorganization_id, so a caller reasonably assumes it does everywhere. It does not.os env create --no-activate --defaultstyle calls silently lose the flag. This is the sharpest one: it is a live feature that quietly does nothing. Fix at one end — either the handler acceptsis_default/env_type(matching how it already treats the other two), or the SDK sends the camelCase the handler wants. Per the contract-first directive, one spelling should win, not both.driver,plan,storage_limit_mb) — deliberate ("paid drivers/plans/storage are not self-serve here"), so the server is right and the type is what lies. Either drop them from the create body or document them as advisory. A typed field the server always ignores is a promise the SDK cannot keep. Note the CLI exposes both:os env create --driverand--plan(the latter even defaulting tofree), so users are being offered choices with no effect.Why no guard caught this
Both halves of the route audit compare URLs.
projects.createtargets a route that really is mounted, so the framework capstone (#3642) and cloud's control-plane ledger (#3655) are both green — and both would stay green if every field butdisplay_namewere dropped on the floor. A body field the server never reads is invisible to a route-level guard.That is the next edge of the #3563 family, and it is worth deciding whether it gets a guard at all before deciding these 13 fields one by one. A cheap version exists: the create route already names the fields it reads, so a test could drive
projects.create()with every declared field populated and assert the resultingsys_environmentrow (or the provisioning arguments) reflects each one — the same "drive it for real" shapeprojects-namespace-coverage.test.tsalready uses, one layer down. That guard would have to live incloud, like its sibling.Suggested order
--default/ an env type and it vanishes).client.projects.listTemplates()targets/api/v1/cloud/templates, which nothing mounts #3702 /os env create --templateandprojects.create({ template_id })send a field no control plane reads #3731.Generated by Claude Code