Skip to content

fix(service-storage)!: an upload link is refused at issue for a file field whose readonlyWhen holds now (#32768) - #33079

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-4h9gy8
Nov 13, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-4h9gy8

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #32768

Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill under skills/** teaches create_upload_link or upload links; the tool's own description and content/docs/ai/connect-mcp.mdx carry the refusal, updated here

维护者速读

  • 改了什么:AI 通过 MCP 的 create_upload_link 给用户要上传链接时,如果目标文件字段此刻被 readonlyWhen 锁住(例如报销单已审批后,发票字段只读),现在直接拒绝、不发链接,拒绝理由用的是更新接口对同一把锁的原话。引擎新增一个只读不写的判断 judgeFieldWriteLock,复用更新路径自己的 readonlyWhen 判定,存储层只调用它,不另写一套表达式求值。
  • 为什么改:Upload links: a link is issued for a record the user cannot change, and the upload page then says "Done. The file was attached" when nothing was attached (readonlyWhen), or "Upload failed" with no reason (approval lock, 500) #32734 之后,这种情况下用户要先选文件、上传,才在页面上被告知「没有附上」。白跑一趟。现在在发链接那一步就告诉 AI 原因,AI 可以直接跟用户解释。
  • 风险与回滚:收紧(BREAKING 标注):原来能拿到链接的调用现在被拒,403 FIELD_NOT_ACCESSIBLE,与静态只读字段同一个错误码。附件列表(不指定字段)不受影响;没有 readonlyWhen 的字段不多读一次库。回滚 = revert 本 PR,用户回到「上传后被拒」的旧体验,数据不受影响。
  • 先看:packages/objectql/src/engine-field-write-lock.ts(判断本体,看它是否与更新路径同源);packages/services/service-storage/src/record-file-access.ts 的 createUploadLinkAs 新增的那段拒绝;packages/objectql/src/engine.ts 新方法 judgeFieldWriteLock。
  • 两分钟验证:pnpm --filter @objectstack/service-storage exec vitest run src/record-file-access.integration.test.ts,预期 36/36 通过,其中「minting refuses a field whose readonlyWhen holds now」一条绿。
  • 需要你做的:本 PR 是收紧(narrowing),按车队规则需要你一句话同意后席位才排队落地。

BREAKING for a caller that minted an upload link onto a file field while the field's readonlyWhen held.

What changes

  • @objectstack/objectql — ObjectQL#judgeFieldWriteLock(object, id, field, { context }), the one write-free judgement of "is this field writable on this record now" for its readonlyWhen lock. Its body (engine-field-write-lock.ts) runs the update's own conditional strip, stripReadonlyWhenFields, over the update's own inputs: the stored row from readUpdatePriorRow as previous, the master-detail header from resolveMasterDetailParent under the same gate (hasParentScopedReadonlyWhenInPayload), and a payload carrying the value the field holds now. It answers null, or { reason: 'readonly_when', phrase }: the DroppedFieldsEvent['reason'] the update reports the field under, and the phrase ReadonlyFieldRejectedError uses for that reason (readonlyDropReasonPhrase, newly exported from readonly-strict-errors.ts, not from the package entry). A faulting predicate refuses as the write refuses; an unbound root holds the lock; a row the caller cannot read is RECORD_NOT_FOUND; a field with no readonlyWhen reads nothing. The facade method in engine.ts is a delegation in the shape validate uses. FieldWriteLock is exported as a type.
  • @objectstack/service-storage — createUploadLinkAs, for a file-field target, asks engine.judgeFieldWriteLock after the static readonly refusal and before the approval lock, and refuses 403 FIELD_NOT_ACCESSIBLE with the engine's phrase. No CEL is evaluated here. The member is optional on RecordFileEngine: an engine without it is not asked, and the upload's own strict write still refuses on arrival. An attachments target (no field) is unchanged — it carries no field lock and is not judged.
  • @objectstack/mcp — the create_upload_link description names the new refusal. packages/mcp/src/file-content.ts is not touched: it only declares the bridge types; the issue-time refusals live in service-storage (measured, and declared on the re-posted claim 6847846915).
  • Docs — content/docs/ai/connect-mcp.mdx: which fields get no link, and with what answer.
  • Update preview (engine.validate, ValidateDataResponse) — not changed. Its published limit (readonlyWhen is not run) stays as written, so no published preview surface widens. The new judgement is a separate method; its only widening is the method itself.

Tests

  • packages/objectql/src/engine-field-write-lock.test.ts (new, 7): locked with the update's reason and phrase; unlocked while the condition is false; a parent-scoped lock reads the header; each verdict cross-checked against what engine.update reports for the same field (onFieldsDropped, readonly_when); no readonlyWhen costs no read; a faulting predicate refuses as ValidationError; a missing row is RECORD_NOT_FOUND 404; no driver write and no strip warning in any case.
  • packages/services/service-storage/src/record-file-access.integration.test.ts (+2): minting on an approved claim's invoices is refused 403 with the exact sentence and writes no link row; the same field on a draft gets a link, a static readonly field keeps its refusal word for word, and the attachments list of an approved claim still gets a link.
  • packages/qa/dogfood/test/showcase-mcp-record-files.dogfood.test.ts (+1, the one dogfood pin, over the real MCP HTTP face): after approval create_upload_link is refused 403 with the engine's phrase and mints nothing, and update_record on the same field answers droppedFields with reason readonly_when naming it.

Acceptance notes

Verification

All readings at head c3e09f34 unless named; the full dogfood battery ran at d9c65672, and the one commit after it (c3e09f34) changes only the objectql unit test's driver double (it applies the caller's limit, for check:objectql-double-limit), re-run below.

Package tests and typecheck (objectql, service-storage, mcp)
  • pnpm --filter @objectstack/objectql test — Test Files 594 passed (594), Tests 12784 passed (12784)
  • pnpm --filter @objectstack/service-storage test — Test Files 79 passed (79), Tests 1486 passed (1486)
  • pnpm --filter @objectstack/mcp test — Test Files 63 passed (63), Tests 1096 passed (1096)
  • pnpm --filter @objectstack/objectql typecheck, @objectstack/service-storage typecheck, @objectstack/mcp typecheck — exit 0 each (after the dependency closure build)
  • engine-field-write-lock.test.ts re-run at c3e09f34 — Tests 7 passed (7)
  • record-file-access.integration.test.ts at d9c65672 — Tests 36 passed (36)
Ablation of the issue-time refusal

Through scripts/ablation-replace.mjs on packages/services/service-storage/src/record-file-access.ts (committed head as the restore point): anchor refuse('FIELD_NOT_ACCESSIBLE', 403, ... the field is ${lock.phrase}.) x1 -> x0, replacement void lock; x0 -> x1, blob a771bfae -> d5ac8211. record-file-access.integration.test.ts under the mutation: 1 failed | 35 passed, the failing one minting refuses a field whose readonlyWhen holds now 403 FIELD_NOT_ACCESSIBLE, in the update's words, and writes no link. Restored: blob == HEAD (a771bfae), git diff HEAD empty. The test runs the package's source, so no dist/ leg.

Full dogfood battery (Clause-② narrowing arm)

pnpm --filter @objectstack/dogfood test under the verify lock at d9c65672: Test Files 458 passed | 3 skipped (461), Tests 3845 passed | 42 skipped (3887), VERDICT command-exit 0. The new pin in showcase-mcp-record-files.dogfood.test.ts is among them (that file alone: 15/15).

Derived gates and lint

node scripts/pm/dispatch-gates.mjs --commands (from the merge base, after origin/main was merged once at 05853f3a): 104 families. Run record with exit codes: --ran reconciles 104 accounted, 101 run, 3 NOT MEASURED:

  • node scripts/check-plugin-teardown-shape.mjs --self-test — exit 3, PREREQUISITE NOT MET: its positive control is a pinned commit a shallow clone cannot reach. NOT MEASURED, reason: shallow checkout; CI runs it.
  • pnpm --filter @objectstack/spec run check:skill-examples — exit 3, PREREQUISITE NOT MET: packages/client-react/dist not built (outside this diff's closure). NOT MEASURED; CI runs it.
  • pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET: unbuilt packages outside this diff's closure (studio, client-react, scenarios, …). NOT MEASURED; CI runs it.

Two reds found and fixed on this branch: check:tenant-audit-census (the RecordFileEngine type, widened as a type literal, stopped being an engine-shaped type the census reads; now an interface extending IDataEngine, census back to 310) and check:objectql-double-limit (the new test's driver double ignored limit). Both exit 0 at c3e09f34, as do check:engine-double-contract and check:nul-bytes. node scripts/check-platform-checklist.mjs --changed-from origin/main — OK, 21 anchors judged, 0 lost. pnpm lint — exit 0.


Generated by Claude Code

…ld whose readonlyWhen holds now

ObjectQL gains judgeFieldWriteLock, a write-free verdict on whether a field
takes a caller's write on a record now, built on the update's own
readonlyWhen strip. createUploadLinkAs asks it for a file-field target and
refuses FIELD_NOT_ACCESSIBLE 403 in the words the update's strict refusal
uses, writing no link row.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czfTdXkf83P5ogS9TT
… tenant-audit census reads

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czfTdXkf83P5ogS9TT
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/mcp, @objectstack/objectql, @objectstack/service-storage, touching 22 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

25 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json c4196099eee97f9fc48995063dde978fe5516a5d.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 80 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 35 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c4196099eee97f9fc48995063dde978fe5516a5d → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3837bf7ffbe49e1c11df6ba421a61c2d621e4188 — the merge of head 506782945fd54b00d5ea4e4f8717d1049c23d03c into base c4196099eee97f9fc48995063dde978fe5516a5d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3837bf7ffbe49e1c11df6ba421a61c2d621e4188 && git checkout 3837bf7ffbe49e1c11df6ba421a61c2d621e4188
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c4196099eee97f9fc48995063dde978fe5516a5d 506782945fd54b00d5ea4e4f8717d1049c23d03c && git checkout -B drift-repro c4196099eee97f9fc48995063dde978fe5516a5d && git merge --no-ff 506782945fd54b00d5ea4e4f8717d1049c23d03c

node scripts/docs-audit/affected-docs.mjs --json c4196099eee97f9fc48995063dde978fe5516a5d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs c4196099eee97f9fc48995063dde978fe5516a5d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract-review-claim: session_016WV4cwqTK71kjQzHae8T5J · head c3e09f3 · 2026-11-13T09:43Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c3e09f3499f39756036d4774816589bed4ee2333
Local-runs: none
Scope: .changeset/32768-upload-link-readonly-when-at-issue.md, packages/objectql/src/engine-field-write-lock.ts, packages/objectql/src/engine-field-write-lock.test.ts, packages/objectql/src/engine.ts, packages/objectql/src/index.ts, packages/objectql/src/readonly-strict-errors.ts, packages/services/service-storage/src/record-file-access.ts, packages/services/service-storage/src/record-file-access.integration.test.ts, packages/mcp/src/mcp-http-tools.ts, packages/qa/dogfood/test/showcase-mcp-record-files.dogfood.test.ts, content/docs/ai/connect-mcp.mdx + packages/objectql/src/engine-update.ts, packages/objectql/src/validation/rule-validator.ts, packages/objectql/src/engine-insert.ts, packages/objectql/src/engine-validate.ts, packages/runtime/src/domains/mcp.ts, packages/services/service-storage/src/file-attachment-service.ts, packages/services/service-storage/src/storage-service-plugin.ts, packages/mcp/src/skill-md.ts (cited code outside the diff, read, not reviewed)

Read at 2026-11-13T09:44Z: the diff against merge base 4079b06b, the live PR body, card #32768 with claim 6847540308, re-post 6847846915 and report 6849438419, and the head's check-runs. Narrowing face (no schema or governed path in the tree; Clause-②: yes (narrowing) on body and changeset).

① Derived judgments

  • Check-runs on the head at read time: 16 completed success (among them Check Changeset, PR-body guards, Governed Surface Queue Guard, Spec property liveness, Type Check · source gates, Lint · docs & code guards); 19 still in progress (Test Core 1/8 to 8/8, Build Core, Build Docs, Type Check · workspace / consumer gates / debt ledger, five Lint families, Live E2E UI); none failed. This record does not stand in for the pending ones.
  • The dogfood shards do not run on pull_request by workflow design, so the dogfood pin's evidence is the dev's local battery at d9c65672 (458 files, 3845 tests, exit 0). The one later commit touches only the objectql unit-test driver double; read in the diff, true.
  • Report claims read against the code: no packages/spec path and no engine-validate.ts in the diff (true); file-content.ts untouched (true); the surface correction to service-storage is declared on re-post 6847846915 before the push (true).
  • Ablation (report): removing the refusal line in record-file-access.ts fails exactly the new integration pin. The pin's shape agrees: it asserts code, status, the whole sentence and an unchanged open-link row count, all of which need the refusal.

② Semver level

  • patch on @objectstack/objectql, @objectstack/service-storage, @objectstack/mcp: correct. Metadata-protocol: none is true (no key, value or metadata type moves), and a non-protocol change, widening or breaking, ships patch with its banner. The three packages are exactly the published ones the diff moves; the dogfood package and docs publish nothing.
  • BREAKING banner present on the changeset and the PR body, with FROM, TO and the one-line fix; fix(...)!: title agrees.
  • ADR-0087 marker not-required (no-migration-prescription) is present and its reason holds: no schema key or stored shape moves. Check Changeset, which runs the registration check, is green on this head.

③ Boundary flags

  • Clause-②: yes (narrowing): correct arm. The tool issues less than it did. The accompanying widening (one public method ObjectQL#judgeFieldWriteLock, one type export FieldWriteLock from packages/objectql/src/index.ts) is declared in the changeset prose and the claim's declaration note; one arm per line, and narrowing is the governing one.
  • readonlyDropReasonPhrase is exported from its module only, not from the package entry; true of index.ts.
  • IDataEngine (spec) is not widened: service-storage reads the method as an optional member of its own RecordFileEngine interface. An engine without it is not asked and the consume-side strict write still refuses.
  • engine.validate / ValidateDataResponse: not in the diff; the documented limit ("readonlyWhen is not run") does not move.
  • Skills-docs: none holds: nothing under skills/** names create_upload_link. The tool description and connect-mcp.mdx carry the refusal.

④ Design judgment

  • The judgement is the engine's own evaluation. judgeFieldWriteLock calls stripReadonlyWhenFields (the function engine-update.ts calls for the by-id update), with previous from readUpdatePriorRow and parent from resolveMasterDetailParent behind hasParentScopedReadonlyWhenInPayload, the update's own gate. No CEL is evaluated in service-storage or packages/mcp. With the payload carrying the stored value, record equals the stored row, so any predicate over other fields gets the verdict the upload's write gets. The two places it can differ from the write (beforeUpdate hooks not run; a predicate reading the incoming value of its own field) are named on the method. In both the consume-side strict write still judges, so the worst case is a link whose upload is refused (the old behaviour) or a refusal slightly early; never an attach past a lock.
  • The narrowing makes an existing refusal real earlier; it takes no working capability away. FROM and TO are true of the code: before, the link was minted and the PUT answered 403 FIELD_NOT_ACCESSIBLE "Nothing was attached: ..."; now createUploadLinkAs refuses before store.createFile, so no link row is written.
  • Complete over the doors: createUploadLinkAs has one implementation (RecordFileAccess), one delegate (FileAttachmentService) and one caller (packages/runtime/src/domains/mcp.ts, the MCP bridge). The REST routes under /storage/upload-links/:token only open and spend a link; no REST or SDK door mints one. The attachments target takes the field === undefined branch and is not judged; pinned by the integration test.
  • Code and words: 403 FIELD_NOT_ACCESSIBLE, the code this door already answers for static readonly and for the same lock at consume time. The phrase is REASON_PHRASE.readonly_when read through the new accessor, not re-spelled. The dogfood pin ties it to the lock update_record reports on the same field (droppedFields, reason readonly_when).
  • should-fix (docs wording, content/docs/ai/connect-mcp.mdx): "the words update_record's strict refusal uses". MCP update_record has no strict mode; it reports the lock under droppedFields with reason readonly_when. The words are the engine update's strict refusal (strictReadonlyWrites). Say that, or name the droppedFields reason.
  • nit: for the same lock the door now speaks two sentences, the engine phrase at issue and "read-only on this record in its current state" at consume. Both true; the reader differs (agent, then person).
  • nit: a record that is both readonlyWhen-locked and approval-locked now answers 403 where it answered 409 RECORD_LOCKED. Refused either way.
  • nit: UPLOAD_LINK_TOOL_LINES in packages/mcp/src/skill-md.ts names no field refusal; it named none before, so nothing regresses.

No must-fix.

Implemented-by: claude/project-thread-4h9gy8
Reviewed-by: session_016WV4cwqTK71kjQzHae8T5J

VERDICT: PASS

…'s droppedFields reason, for the upload link's readonlyWhen refusal

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4czfTdXkf83P5ogS9TT
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

Delta note (docs only) · head 50678294

This is a delta of record 6849553232's PASS on c3e09f34. It is one commit, 50678294, changing 1 file (+4/-2), with no main merge. Read in full at 2026-11-13T09:48Z.

  • content/docs/ai/connect-mcp.mdx: the refusal is now attributed to the engine update's strict refusal (strictReadonlyWrites). The page also says that update_record reports the same lock under droppedFields with reason readonly_when. This closes the record's should-fix, and it is true of the code the record read.
  • No executed line changed. Every judgment of the record carries over: Clause-②: yes (narrowing), Metadata-protocol: none, patch on three packages, the BREAKING banner, and ADR-0087 not-required (no-migration-prescription).

Landing waits for two things: the 18.4.0 freeze to lift (#29963) and the maintainer's word on the narrowing.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#8 · os-elon-musk · session_016WV4cwqTK71kjQzHae8T5J

ACCEPT · PR #33079 · head 506782945fd54b00d5ea4e4f8717d1049c23d03c · 2026-11-13T11:17Z · Fixes #32768

席位核对

  1. Shape: draft PR to main, Fixes #32768. Files (11):
    • objectql: engine-field-write-lock.ts and its test, engine.ts facade, the index.ts export, readonly-strict-errors.ts.
    • service-storage: record-file-access.ts and its integration test.
    • mcp: the mcp-http-tools.ts tool description.
    • One dogfood pin, content/docs/ai/connect-mcp.mdx, one changeset.
    • The surface correction to service-storage was declared on the re-post 6847846915. No governed path.
  2. Premise measured: createUploadLinkAs minted a link onto a file field whose readonlyWhen held. The upload was refused only after the person had sent the file. It is now refused at issue with 403 FIELD_NOT_ACCESSIBLE, through the engine's own readonlyWhen evaluation (judgeFieldWriteLock). There is no second CEL evaluation, and no link row is written.
  3. Gates: check-runs on this head: 38 success, 15 skipped, 0 failed, 0 pending. The dogfood shards run in the merge queue; the dev's full battery was 458 passed, 3 skipped.
  4. Contract review: CONTRACT_REVIEW_TIER PASS 6849553232 on c3e09f34, with no must-fix. The docs-only delta note 6849591863 on this head closes its should-fix.
  5. Narrowing, maintainer's word: Clause-②: yes (narrowing), BREAKING banner, FROM → TO, Skills-docs: none. The maintainer agreed (option A, 「同意收紧」; ruling recorded on Upload links: refuse at issue a field whose readonlyWhen holds now (carries the issue-time readonlyWhen half of #32734) #32768).
  6. 18.4.0 freeze: lifted. The Version Packages PR chore: version packages #29963 has merged.
  • Landing: Metadata-protocol: none, patch on objectql, service-storage and mcp, ADR-0087 not-required (no-migration-prescription). main was merged once, before the PR opened. K = 1 (c4196099ee, this head's Test Core affected-set base, to origin/main), under 15.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 13, 2026 11:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 13, 2026
Merged via the queue into main with commit 5430b4a Nov 13, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-4h9gy8 branch November 13, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upload links: refuse at issue a field whose readonlyWhen holds now (carries the issue-time readonlyWhen half of #32734)

2 participants