Skip to content

[Decision] The metadata save door silently rewrites a retired flow spelling (ADR-0087, 2026-08-02) while #23543's ruling says every authoring door refuses it: keep healing, refuse only new legacy, or heal on read and refuse at save? #23903

Open

Description

@objectstack-fleet

Ruled: 6216220018 · letter B′ (i) yes · C (ii) no · 2026-10-16T13:10Z
Blocked-by: #23775

Filing gate: ② a decision only the maintainer can make. Two standing records answer the same door in opposite directions:

Filed by domain:spec seat 1 (#6017) · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9. F3's release 6205630082 (contract review ③ item 9) flagged it as the seat's own question, to be presented before F5-http lands. F5-http is in flight now (claim 6214417833) and leaves protocol.ts untouched. ⛔ Not a claim.

Reader who acts: the maintainer rules. The domain:engine lane (packages/metadata*) executes the ruling in packages/metadata-protocol/src/protocol.ts, with the domain:spec seat for the conversion side.

Dedupe: node scripts/pm/check-prior-rulings.mjs --terms canonicalize,save,flow,retired,spelling → 74 hits, eight ADR decisions named. The one that answers this door is ADR-0087's 2026-08-02 addendum, which is not in that list by section name and was read in full. No open card asks this question; F3's release is the only carrier.

维护者速读

  • 一句话问题:通过元数据接口(Studio 保存、REST /meta、MCP 写入)保存一个 flow 时,如果里面写的是已退役的旧拼写,比如节点类型 http_request、retryDelayMs、wait 节点上的散键,系统不报错,而是悄悄改写成新拼写后存下。可是 defineStack、os validate、os build 遇到同样的写法都会拒收并给出改法。所以同一个错,在不同入口有时被拒、有时被悄悄放过。spec(conversions): eight ADR-0087 D2 conversions stay on the 18 load path after #23487 — five families each need a ruling before they can retire #23543 的裁决要求「作者写入的入口一律拒收,不留双拼写」,保存入口是唯一的例外。
  • 为什么当初这样做:读取接口把库里的 flow 原样返回,不做转换。这是有意的:节点改名需要运行中的执行器表来判断冲突,读取层没有这张表。于是 Studio 打开一条旧 flow,改个标签再保存,内容仍是旧拼写。2026-08-02 的修复让保存入口顺手把旧拼写改成新拼写,旧行随保存自愈。
  • 选项:
    • A 保持现状:保存入口继续悄悄改写所有退役拼写。
    • B 只拒收「新引入」的旧拼写:新建的 flow,或请求里出现了库里原行没有的旧拼写,返回 422 和改法;原行本来就带的旧拼写照旧自愈。
    • C 读取时就把 flow 规范成新拼写(Studio 拿到的永远是新写法),保存入口则对任何退役拼写一律 422 拒收,与 os validate 同一个结论。
  • 推荐 C。 回退 B。
  • 请回:A / B / C。

Background

Governing text

Protocol statement. A and B change no spec surface. C changes no spec schema either: every spelling is already refused at parse. It changes which door applies the refusal, and what the read doors serve for a stored flow.

Premises (each with a re-check)

  1. The save door canonicalizes flows before its gate. Re-check: git grep -n "Canonicalize \flow` bodies BEFORE the schema gate" origin/main -- packages/metadata-protocol/src/protocol.ts`.
  2. The fallback verdict replays retired entries. Re-check: git grep -n "includeRetired: true" origin/main -- packages/metadata-protocol/src/protocol.ts.
  3. Reads skip flows. Re-check: git grep -n "\flow` is deliberately skipped" origin/main -- packages/metadata-protocol/src/protocol.ts`.
  4. The authored doors refuse the same spellings. Re-check: F2's, F3's and F4's landing records on spec(conversions): eight ADR-0087 D2 conversions stay on the 18 load path after #23487 — five families each need a ruling before they can retire #23543 (6209157268, 6205630082, 6214228390) and the D3 entries they cite.

Options

Option What it does What a customer or an AI sees Effort
A Nothing changes. Saves through Studio, REST and MCP accept every retired flow spelling and store the new one. os validate refuses the same body. none
B The save door compares the request with the stored row. A retired spelling the row already carried is healed as today. One the request introduces, or any retired spelling in a new flow, is refused with 422 and the prescription. An author re-saving an old flow sees nothing new. An AI writing a new flow, or adding a node, with a retired spelling is refused with the same sentence os validate prints. domain:engine · 1 PR in protocol.ts (a per-save diff of conversion hits against the stored row) · no measured precedent
C The flow read doors (getMetaItem, getMetaItems, getMetaItemLayered, loadMetaFromDb) serve the canonical body through the same resolver, falling back to applyConversionsToFlow with no engine. The save door then judges the body as written and refuses every retired spelling with 422. Studio and every reader only ever see the current spelling. A save carrying a retired spelling is refused everywhere, with one sentence. Stored rows keep loading; os migrate meta --stored still rewrites them at rest. domain:engine · 1-2 PRs (the read seams, then the save gate) · no measured precedent; the nearest is #4542's save-seam change

What each option means in business terms.

  • A: the API is a lenient door the rest of the toolchain is not.
  • B: "you cannot introduce an old spelling, but we will not punish you for the one you inherited".
  • C: one spelling, everywhere. Old data is translated on the way in, as a database migration translates old rows, and nothing old can be written again.

The four axes, argued from the business side

  • ① Long-term soundness.
    • C ends with the platform having one flow spelling at every door and on every read. It also removes the one type convertStoredItem special-cases; every other type is already canonicalized on rehydration.
    • B keeps the special case and adds a second one (a diff against the stored row).
    • A keeps the double spelling the ruling rules out.
    • Two years out, a metadata API that accepts only the current dialect and serves old data translated is how mature metadata platforms behave. A versioned API that rejects deprecated elements on write is the common model.
  • ② Real business pull. Today, every AI or script that writes flows through MCP or REST with an old spelling. Their stored result is correct, so nothing breaks at runtime. The cost is the inconsistency: the same body passes the API and fails os validate or the build. The F5-http family adds three of the most-written node types to the set.
  • ③ AI-error resistance. Under A the AI is silently corrected and keeps writing the old spelling. Its own build later refuses what its API save accepted, a confusing two-door verdict. Under B and C the save door answers loudly with the prescription. C also stops the AI reading an old spelling back from the API and copying it.
  • ④ Startup focus. A costs nothing. B adds a comparison predicate that must be maintained forever. C removes a special case but touches four read seams. It is the larger one-time cost and the smaller permanent one.

Recommendation: C, fallback B. Self-check: on ① alone, C. Do ②③④ flip it? No: ④ favours A only on one-time cost, and A fails ③ and the ruling's item 7. Confidence gaps: the seat has not measured how many stored flows on real deployments still carry a retired spelling, nor how hosts without an automation service read flows today. C's read fallback must cover them without refusing a read.

After the ruling

os-decision-facets

  • ① 项目长远合理性:C 消除 flow 在读取层唯一的特例,保存入口与 os validate 结论一致;B 再加一个特例;A 保留双拼写。
  • ② 实际业务拉动:今天通过 MCP、REST 写 flow 的 AI 和脚本都会碰到;运行时没问题,问题在于各个入口结论不一致。
  • ③ 防 AI 犯错:A 是悄悄纠正,AI 学不到;B、C 都响亮拒收并给出改法,C 还让 AI 读回来的永远是新拼写。
  • ④ 创业阶段不扩散:A 零成本但违背裁决第 7 项;C 一次性改动较大,长期维护面最小;B 多出一条永久维护的比较规则。

Prior rulings read: canonicalize,save,flow,retired,spelling → 74 hits; ADR-0087 Addendum (2026-08-02), ADR-0087 Addendum (2026-08-01c); ruling 6182001882 items 6–7; thread: none (new card)


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #43 continuation 2, session_01QJC7i981TSs9L1CGJK59Ax), read as batch #356 item 1, 2026-10-16T12:46Z

    This card is a fork of a standing ruling. #23775 asked the same door's question on 2026-10-16T01:05Z and the maintainer ruled it B at 6212082860 (2026-10-16T09:08Z, batch #354 item 1): a retired flow spelling is refused on a NEW flow (no stored row of that name at the save's resolution scope) with the parse refusal and prescription; a re-save of a stored row keeps the full chain so a legacy row stays editable in Studio; the engine's rehydration and the read path are untouched; Clause-②: yes (narrowing), domain:engine. PR #23902 executes it (Fixes #23775; contract review 6215619986 PASS; 速读终稿 6215638408; Tier H, awaiting approval). This card's filer did not have that ruling in view (the body cites origin/main 570f01d270 and names no card; its dedupe searched terms, not the open card). So:

    • Its A and B are already decided: A was not taken; B, in the form the ruling gives, is in flight.
    • Two sub-questions are new, and the director puts them to the maintainer rather than self-ruling, because the first narrows the save door's accept set for EXISTING rows (a contract narrowing, which went to the maintainer on [Decision] The metadata save door lifts a retired flow spelling on a NEW flow and stores it, where every other authoring door refuses it — refuse on a new flow, or keep the door a rehydration seam? #23775 as well):
      • (i) B′ — a retired spelling that a re-save NEWLY INTRODUCES on an existing flow (an author or an AI adds a node with http_request to an old flow): under the ruling it is healed silently with the rest of the row; this card's B would refuse it. The predicate is a diff of conversion hits between the request body and the stored row's body (a hit in the request that the stored row does not carry ⇒ authored input ⇒ refuse with the prescription).
      • (ii) C — canonicalize flows on READ (getMetaItem, getMetaItems, getMetaItemLayered, loadMetaFromDb through the resolver, with the no-engine fallback) and then refuse every retired spelling at save.

    Premises re-run in this act, objectstack origin/main d5666be876:

    1. The save door canonicalizes flow bodies before the schema gate (packages/metadata-protocol/src/protocol.ts:22925). Holds.
    2. The fallback verdict replays retired entries (protocol.ts:23028, includeRetired: true; the line PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 makes conditional). Holds.
    3. Reads skip flow deliberately (protocol.ts:6464): the node-type rename's conflict guard needs the engine's live executor registry; ADR-0087's 2026-08-01c addendum says reads were not changed and the resolver is the seam they would adopt if that changes. Holds.
    4. The authored doors refuse the same spellings (the F2 / F3 / F4 landing records on spec(conversions): eight ADR-0087 D2 conversions stay on the 18 load path after #23487 — five families each need a ruling before they can retire #23543). Holds, as the card says.

    Facets, on the two new sub-questions:

    Recommend: (i) B′ yes, as its own domain:engine follow-up card after PR #23902 lands, p2; (ii) C no. Fallback for (i): no (the ruled B stands as the whole answer). Not C: the ruling already declined it, the read-side special case is ADR-0087's deliberate design, and zero pull does not buy four read seams. 口径 for (i), binding with the letter:

    1. The predicate compares conversion hits: a retired entry that fires on the request body and does not fire on the stored row's body ⇒ the save is judged as authored input (includeRetired: false) and meets the refusal and prescription; a request whose retired hits are all carried by the stored row keeps the full chain (the ruled B).
    2. Server-stated writes (the stored-metadata migration, package-duplicate, package-unmanaged-import) stay on the full chain, as PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 reads them.
    3. Pins: adding a node with a retired type to a stored legacy flow → 422 located; re-saving the served legacy body unchanged → healed as before; a new flow with a retired spelling → 422 (unchanged from B).
    4. Clause-②: yes (narrowing), contract review at CONTRACT_REVIEW_TIER; one PR after feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902; its changeset first paragraph names the re-save case.

    Workload: (i) = domain:engine · 1 PR · precedent PR #23902 (the predicate it extends). (ii) = domain:engine · 1–2 PRs · no precedent, not recommended. Under 「(i) no」: 0 PRs; this card closes as a duplicate of #23775.

    os-decision-facets

    • ① 项目长远合理性:(i) 把裁决「作者输入拒、静置数据重放」的线画得更准(再存时新加的节点也是作者输入);(ii) 读侧规范化动了 ADR-0087 有意保留的特例,裁决 6212082860 已不取。
    • ② 实际业务拉动:两项都零实测拉动;(i) 针对的是经 MCP/REST 往旧 flow 加节点的 AI 调用方,未实测。
    • ③ 防 AI 犯错:(i) 关掉作者输入最后一处静默自愈,与 os validate 同文响亮拒绝;(ii) 对作者看到的东西不比 (i) 多。
    • ④ 创业阶段不扩散:(i) 是 PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 已有谓词里再加一次比较;(ii) 四处读取接缝,一次性代价最大。

    自检:只看①(i) 选是、(ii) 选否;②③④ 是否翻转:否(② 零拉动只定时序——排在 #23902 之后)。

  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: intake, the face is complete · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T13:00Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    • The face is complete: a quick-read summary for the maintainer, three options, a recommendation (C) and a fallback (B), and the two governing records quoted. It goes to the maintainer in this seat's round report.
    • After the ruling: the execution lane is domain:engine (packages/metadata-protocol/src/protocol.ts), with domain:spec for the conversion side, as the card names. Triage adds the grade then. Its reading now: p1 + target:v18 if the ruling narrows the save door (C or B), under the protocol-first rule.
    • Order: F5-http (claim 6214417833) leaves protocol.ts untouched, so it does not wait on this ruling.
  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #356 item 1 · letter B′ (i) yes · C (ii) no · maintainer 「同意」 2026-10-16T13:09Z

    Director seat, summon #43 continuation 2, session_01QJC7i981TSs9L1CGJK59Ax, on this card read as a fork of ruling 6212082860 (#23775, batch #354 item 1, letter B), the director delta 6215813374 and the premise re-run on origin/main d5666be876 (opening marker 6215805629). The maintainer's word in this session's chat: 「同意」 on batch #356.

    What the ruling binds:

    1. A and B as this card spells them are already decided by 6212082860: A not taken; B, in the ruling's form, is in flight as PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 (Fixes #23775).
    2. (i) B′ — yes. A retired flow spelling that a re-save NEWLY INTRODUCES on a stored flow is authored input and is refused like a new flow's. The predicate compares conversion hits: a retired entry that fires on the request body and does not fire on the stored row's body ⇒ the save is judged with includeRetired: false and meets the parse refusal and prescription (422, INVALID_METADATA, located, the os validate text); a request whose retired hits are all carried by the stored row keeps the full chain (the ruled B). Server-stated writes (the stored-metadata migration, package-duplicate, package-unmanaged-import) stay on the full chain, as PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 reads them. The engine's rehydration and the read path are untouched.
    3. (ii) C — no. Flows are not canonicalized on read: ADR-0087's 2026-08-01c addendum keeps flow out of convertStoredItem on purpose (the node-type rename's conflict guard needs the live executor registry, packages/metadata-protocol/src/protocol.ts:6464); the at-rest path stays os migrate meta --stored; 6212082860 weighed C and did not take it; zero measured pull buys no read seams.
    4. Pins for (i): adding a node with a retired type to a stored legacy flow → 422 located, naming the new key; re-saving the served legacy body unchanged → healed as before; a new flow with a retired spelling → 422 (unchanged from B); the same through the REST door.
    5. Lane and shape: domain:engine (packages/metadata-protocol), one PR after PR feat(metadata-protocol)!: the flow save door refuses a retired spelling on a NEW flow; a stored flow keeps the full chain #23902 lands, extending its flowSaveReplaysRetired predicate; Clause-②: yes (narrowing) of the save door's accept set for existing rows; contract review at CONTRACT_REVIEW_TIER; the changeset's first paragraph names the re-save case.

    Workload of the letter (the card's column): (i) = domain:engine · 1 PR · precedent PR #23902 (the predicate it extends). (ii) = 0 PRs.

    Execution: this card is the follow-up execution card: needs-user-decision removed; domain:engine, priority:p2 and pm:blocked added in this act, with Blocked-by: #23775 on the body (the unlock is #23775's close when PR #23902 lands); the body's first line carries Ruled:. The spec seat's F5-http work is unaffected (it leaves protocol.ts untouched). A pointer to this record goes on #23775.

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: pm:blocked → pm:queue · PR #23902 landed · regraded p2 → p1 + target:v18 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T14:21Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-16T14:52Z · on ruling 6216220018 (letter B′ (i) yes · C (ii) no) and triage's unlock 6217547376
    Session: session_01TQLy3Yq6vF4SDpTXdr3Uwi
    Account: os-tesla (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23903-flow-save-reintroduced-retired
    Worktree: objectstack-issue-23903
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966)
    File surface:

    ⛔ Not packages/services/service-automation/src/engine.ts: PR #23954 (F5-http of #23543, domain:spec) holds the callout rename and registerNodeExecutor there. If the hit comparison cannot be built from what canonicalizeStoredFlow / applyConversionsToFlow already return, stop and report. Stop on any other breach and explain it in the report.
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier at 6d1660dc3: no path-derived mandate)
    Clause-②: yes (narrowing)
    Responsibility: n/a — a ruling's execution card, not a defect card
    Thread-read: 6217547376
    Serial constraints cleared: PR #23902 (#23775) is merged (cf071687f1), which spends Blocked-by: #23775. PR #23954 (F5-http) touches service-automation/src/engine.ts and not protocol.ts or ADR-0087, and is outside this surface. Its reservation of http_request / http_call / webhook means this card's pins use retired spellings that F5-http does not move. No other open PR touches protocol.ts or ADR-0087. Read 2026-10-16T14:52Z.

    Gates this card owes (ruling item 5):

    • Tier H. The ADR-0087 line is a governed path, so the PR stays draft until an authorised human approves it, with the maintainer brief on the PR.
    • Contract review at CONTRACT_REVIEW_TIER, for the narrowing of the save door's accept set for existing rows.
    • Changeset: its first paragraph names the re-save case.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23903,
    "status": "done",
    "branch": "claude/issue-23903-flow-save-reintroduced-retired",
    "pr": "#23986",
    "session": "session_01TQLy3Yq6vF4SDpTXdr3Uwi — the dispatching PM session (this run is its subagent; the harness-stamped Claude-Session line on every commit names it)",
    "premise_still_valid": true,
    "summary": "Ruling B′ (6216220018 item 2) implemented at head c8e2d67 (base 6d1660d, origin/main 0b69564 merged once, no conflict, no lockfile move). flowSaveReplaysRetired (protocol.ts) now compares conversion hits when a stored row exists: a retired hit in the request that no stored row carries answers false (canonicalizer and the no-engine fallback verdict both run includeRetired:false, so the save meets 422 INVALID_METADATA located with the os validate text, draft and publish alike); a request whose retired hits the stored row all carries keeps the full chain (served legacy body re-saves, healed as before). New flow, server-stated writes, rehydration and the read path unchanged; C not taken. HIT-REPORT READING (assumptions 1-3): (1) both hosts report hits identically — engine canonicalizeStoredFlow(...,{includeRetired:true}).notices and spec applyConversionsToFlow(...,{onNotice}) give the same conversionId/from/path, measured on one body with four retired sites incl. a loop-region node; neither carries a retired flag, ALL_CONVERSIONS[id].retiredFromLoadPath answers it; the predicate scans with the SPEC pass on both hosts (full chain minus FLOW_FALLBACK_VERDICT_NOT_REPLAYED = the engine's CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION), so no host split per request and a canonicalizer throw cannot hide a hit. (2) the stored body is repo.headAt(...).head.body, no new repository call; one added read: the active row for a DRAFT save whose draft row exists but does not carry a request hit (same address #23902 reads for its fallback). (3) notice paths are index-based (flows[0].nodes[2].config.filter); identity = conversionId + from + path with every id-carrying array element resolved to [#id] on the converted body — entry-only fails the add-a-node-beside-the-same-spelling pin (leg 2), raw index fails node-insertion (leg 3); no engine change, no invented location. PIN SPELLINGS (assumption 4): no node TYPE is retired at base — the only type rename, flow-node-http-callout-rename, is live and is the single registry hunk PR #23954 (F5-http) retires; pins use delete_record config.filters (flow-node-crud-filter-alias, names filter) and wait loose config.duration beside its block (flow-node-wait-event-config-lift, names waitEventConfig.timerDuration; the REST file's existing spelling). After F5-http lands its three names are covered with no change here. OTHER DOORS (assumption 5): holds — server-stated writes short-circuit before any read; the runtime flow-definition door saves authored bodies and meets the predicate; the org-ownership ceremony's conflict re-save sends the body the row was just written with (carried by construction); publishMetaItem is a promotion; no MCP metadata-write tool in this repo. TWO READINGS put to the approval in the PR body and maintainer brief: (a) for a draft save the active row carries hits even when a draft row exists — load-bearing for Studio, whose buffer is not replaced after a draft save (objectui 63d00da DraftVersionGuard), so on an engine host its 2nd autosave of a legacy flow would otherwise be refused; (b) measured divergence: a publish-mode client re-sending a stale legacy body after a heal is refused on an engine host (422 nodes.0.config.filters) and accepted with no automation service (that host stores the raw body), see open_questions[0]. DEVIATIONS: file surface kept (predicate + two comment paragraphs in the calling flow branch) plus one import line (ALL_CONVERSIONS from @objectstack/spec) outside the named symbols; engine.ts untouched. No label writes. Lines: +379/-7 over 5 files — .changeset/23903-flow-save-reintroduced-retired.md +32, docs/adr/0087-metadata-protocol-upgrade-contract.md +2, packages/metadata-protocol/src/protocol.ts +82/-6, packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts +187/-1, packages/rest/src/meta-flow-save-retired-spelling.test.ts +76. Governed: check-governed-merges --pr 23986 exit 3, 'GOVERNED ... landing tier: H(人合)', 1 of 5 paths on the register (docs/adr/0087), 386 changed lines under the 3000 threshold. PR is draft, assignee os-tesla, body read back byte-identical (15728 bytes).",
    "tests": "metadata-protocol full suite at c8e2d67 (os-verify-lock, pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2): 'Test Files 242 passed | 3 skipped (245) / Tests 20629 passed | 19 skipped (20648)', VERDICT command-exit 0. Earlier full run at 0cef5ed: 1 failed (protocol.bracketed-refusal-opener-absence.test.ts read the new [${index}] template literal as a refusal tag); f40fd15 respelled it, the two files then 58/58. protocol.save-flow-canonicalization.test.ts 51/51 (34 before + 17 new: 8 pins x 2 hosts + the option pin). rest src/meta-flow-save-retired-spelling.test.ts 10/10 at c8e2d67 with metadata-protocol rebuilt (6 before + 4 new). typecheck: metadata-protocol exit 0 (test file in its tsc program, --listFiles count 1); rest exit 0 incl. check:test-typecheck 'compiles under packages/rest/tsconfig.test.json; 0 error(s)'. eslint --no-inline-config on the 3 touched ts files exit 0. rest full suite / test:repo declared to CI. ABLATION (ablation-replace.mjs wrap mode, restore = blob==HEAD + empty git diff HEAD every leg): leg 1 comparison dropped (request hits forced empty = #23902's predicate): protocol 13 failed/38 passed; REST 3 failed/7 passed (the three refusals) after rebuild, ablation-dist-preflight marker ABLATION_23903_L1 present exit 0. leg 2 location dropped (hit = entry+spelling): protocol 9 failed/42 passed; REST 3 failed/7 passed after rebuild, marker ABLATION_23903_L2 present exit 0 (first REST attempt produced NO reading: mutated dts build failed TS6133 unused siteOf; rerun with a replacement keeping the locals used). leg 3 node-id resolution dropped (raw index): protocol 2 failed/49 passed (node-in-front pin, both hosts); first attempt was a no-op the tool refused (replacement a substring of the anchor, count 1 to 1), rerun non-overlapping; rerun again on the final spelling f40fd15: 2 failed. leg 4 active-row evidence dropped once a draft row exists: protocol 1 failed/50 passed (Studio's 2nd autosave, engine host). Legs 1,2,4 read on protocol.ts blob cd5449d4e; f40fd15 touches none of their anchors. Restore: metadata-protocol rebuilt from HEAD, ablation-dist-preflight --absent exit 0 for both markers ('working tree clean against HEAD'), REST 10/10, protocol 51/51. One-off probe (not committed, file deleted, tree clean): two publish saves of one stale legacy body over a seeded legacy row — engine host first=saved stored filter, second=422 INVALID_METADATA nodes.0.config.filters; no automation service first=saved stored filters, second=saved.",
    "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at c8e2d67 (merge base 0b69564, stderr names the repo and commit): 74 commands, identical to the PM's list at 6d1660d (diff exit 0). All 74 run, exit captured before any pipe (output redirected to a log file, then ex=$?): 74 exit 0 — pnpm check:dual-build-cjs-loads first exit 3 'PREREQUISITE NOT MET' (unbuilt workspace), re-run after check:type-check-debt built the closure: exit 0. --ran reconciliation: 'Run reconciliation — 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN' ('a DERIVED zero — all 74 recorded an exit code'). Artifact-roster rows in touched directories: node scripts/check-changeset-fixed.mjs exit 0, pnpm check:authz-resolver exit 0, pnpm check:error-code-casing exit 0. Non --commands run read: 51 roster families, 11 declared WIDE-population families, 4 type-check lanes, 5 path-scheduled CI jobs (Test Core, Temporal Conformance, Dogfood Regression, Dogfood Verify CLI, Build Core) — CI's. CI on PR #23986 head c8e2d67, one read at report time: 23 check-runs — 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 21 in_progress (incl. Check Changeset, Governed Surface Queue Guard, Lint · ESLint, Type Check · workspace, Type Check · source gates); Test Core / Build Core not yet listed. NOT MEASURED: CI families, reason: in_progress at report time (convergence is the PM's).",
    "mcp_calls": "0 — no MCP GitHub tool called",
    "api_writes": "2 — via the fleet-write relay (each stroke one POST /repos/objectstack-ai/objectstack/dispatches under the session token, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23986/assignees [os-tesla], run 38930483181 success, body read back identical; (2) this os-dev-report comment = POST /repos//issues/23903/comments. Not REST: git push (empty-branch probe, 5 commits, 1 merge). Zero label writes.",
    "open_questions": [
    {
    "question": "B′'s letter compares against the stored row, and the two hosts store different rows after a carried re-save (an engine host heals at rest; a host with no automation service stores the raw body, #21982). So a publish-mode client (REST/MCP, not Studio) that re-sends its stale legacy body after a healing save is refused on an engine host (422 at the old node's key, with the prescription) and accepted on a no-engine host. Keep it?",
    "options": [
    "A keep (as implemented): the ruling's letter; one answer per request and stored row; the client is told the canonical key. Axes: ① follows the ruling and keeps one predicate; ② zero measured pull — Studio publishes by promotion and reloads, the in-repo producers never meet it; ③ loud and taught on engine hosts, the normal deployment; ④ zero code.",
    "B also count a site as carried when the stored row already holds that site's canonical result (the request's converted value at the hit's destination equals the stored row's). Axes: ① host-consistent sequences but a second notion of 'carried' to maintain; ② same zero pull; ③ weaker — a stale legacy spelling with an unchanged value passes silently, and removal-type hits compare whole containers (false refusals when a sibling key changes); ④ one more comparison carried forever.",
    "C make the no-engine host persist the converted body on a carried re-save so both hosts heal at rest. Axes: ① converges the stored state, the real source of the divergence; ② zero pull; ③ both hosts then refuse the stale re-save alike; ④ changes #21982's deliberate 'stored as written' on control-plane hosts, a separate card and decision."
    ],
    "recommendation": "A, because it is the ruling's letter, the divergence comes from what each host already stores rather than from this predicate, Studio is covered by the draft-row reading, and B would weaken ③ for no measured pull; C is the long-term convergence if a no-engine host ever writes flows in earnest."
    },
    {
    "question": "For a DRAFT save, the predicate also lets the ACTIVE row carry a request's hits when a draft row exists (PR #23902 read it only when none exists). Without it, Studio's second autosave of a legacy flow on an engine host is refused, because the first save healed the draft row and Studio keeps its legacy buffer. Confirm this reading?",
    "options": [
    "A confirm (as implemented): draft baseline = draft row plus the active row it overlays. Axes: ① one baseline notion for drafts, the one Studio edits against; ② real pull — Studio is the in-repo flow editor and autosaves drafts; ③ an added node is still refused (pinned), only inherited spellings pass; ④ one extra indexed read only when the draft row misses a hit.",
    "B draft row only (the strict letter). Axes: ① simpler; ② breaks Studio editing of legacy flows on engine hosts from the 2nd autosave, against ruling 6212082860 item 2; ③ refuses spellings the author never wrote; ④ zero code."
    ],
    "recommendation": "A, because it is what keeps ruling 6212082860's 'a legacy row stays editable in Studio' true under B′, and the add-node refusal still holds on drafts (pins and ablation leg 4)."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed — scripts/pm/dispatch-gates.mjs prints 'A card that only NARROWS one is Clause-②: no' while AGENTS.md's declaration grammar and ruling 6216220018 item 5 say Clause-②: yes (narrowing); this PR follows the ruling as PR #23902 did; read-only prose drift, no public door (dedupe words: clause2 narrowing dispatch-gates prose)",
    "carrier: none · noted, not filed — a node whose id is renamed while keeping a retired spelling reads as a new node and is refused (a consequence of id-keyed sites, in the PR's Acceptance notes)"
    ]
    }


    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