Skip to content

fix(mcp): validate_expression refuses the label() call the object door refuses - #28774

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-d7vyq6
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-d7vyq6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #28715

Clause-②: yes (widening) — the published McpDataBridge interface (@objectstack/mcp, export type) gains one optional member, optionFieldsOf(name); a bridge that omits it behaves exactly as before. No tool input or output shape changes: describe_object serves the same projection, and validate_expression's new verdict is an errors[] entry with an existing code. Expected no in the claim; measured yes (widening) because the member lands on an exported type.
Metadata-protocol: none

维护者速读

  • 改了什么:MCP 的 validate_expression 工具现在对 label(record.某字段)(字段没有声明选项,比如文本字段)给出与保存对象时相同的拒绝:同一个错误码 label-field-no-options、同一句话、点名字段。以前工具答 ok,保存时才被拒。
  • 为什么改:上一张卡让保存门拒绝这种公式,但 MCP 两个桥(stdio 与 HTTP)把字段投影成名称/类型/标签/必填,工具看不到选项,于是和保存门答案不一致,AI 代理白跑一轮。
  • 风险与回滚:只加一个可选的桥方法 optionFieldsOf,describe_object 的返回不变;没有这个方法的桥照旧不检查。判定仍是同一个校验器,没有新规则。回滚即 revert 本 PR。
  • 先看:packages/mcp/src/mcp-http-tools.ts(validate_expression 处理器与 VALIDATE_SITE_MAP 的 optionLabels)、packages/mcp/src/stdio-data-bridge.ts 与 packages/runtime/src/domains/mcp.ts(两个桥的 optionFieldsOf)。
  • 两分钟验证:pnpm --filter @objectstack/mcp exec vitest run --maxWorkers=2 src/mcp-validate-expression.option-labels.test.ts,预期 13 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • @objectstack/mcp
    • McpDataBridge gains an optional optionFieldsOf(name), answering the list of field names (or null): the fields of the object describeObject reads that declare options, or null when no such object is served.
    • The validate_expression handler, on the formula site, asks the bridge for that list and hands it to the one validator as ExprSchemaHint.optionFields (the member PR fix(formula,lint)!: a formula whose label() names a field that declares no options is refused at author time (#28659) #28714 added). label(record.FIELD) on a declared field outside the list is then refused as label-field-no-options, in the sentence the door carries. No second rule and no second spelling of "declares options".
    • Only the formula site asks (VALIDATE_SITE_MAP[...].optionLabels): @objectstack/lint's formula pass hands the validator optionFields on a formula field's expression and nowhere else, so validation, flow_condition and template answer exactly what the door answers there, as before.
    • The stdio bridge answers optionFieldsOf from objectView.getObject(name), the read its describeObject makes, through @objectstack/formula's declaresOptions.
  • @objectstack/runtime
    • The HTTP bridge (buildMcpBridge) answers optionFieldsOf the same way. Its by-name object read is factored into one readServedObject(name) that both describeObject and optionFieldsOf call, so the two cannot read different objects.
    • vitest.config.ts aliases @objectstack/formula to source, as check:test-source-alias prescribes for the new import.
  • describe_object is unchanged. The option reading is a bridge member of its own, not a key on describeObject's answer; a test pins the answer's keys (name, label, fields, enableFeatures; each field name, type, label, required).

Which predicate, measured

The order said to pick the predicate by the document the tool reads. A one-time probe (scratch test, not committed) booted real kernels (ObjectKernel + MetadataPlugin + ObjectQLPlugin over better-sqlite3, once without and once with environmentId: 'env_local') with a package declaring a picklist tier and an object whose tier field is { type: 'select', picklist: 'tier' }, then read the documents each bridge reads:

Probe readings (field tier; first text and kind inline select as controls)
read shape tier as read
protocol.getMetaItems({ type: 'object' }) (both bridges' merged read) no environmentId {"type":"select","picklist":"tier","options":[{"label":"Gold","value":"gold"}]}
metadata.getObject(name) (both bridges' fallback with no merged read) no environmentId same: picklist + resolved options
protocol.getMetaItems({ type: 'object' }) env_local same: picklist + resolved options
metadata.getObject(name) env_local object absent (the #25120 shape; the merged read answers)
HTTP bridge describeObject (before) both {"name":"tier","type":"select","label":"Tier","required":false}: options stripped

The stdio bridge's objectView.getObject reads the same protocol.getMetaItems({ type: 'object' }) (readMergedObjects, stdio-data-bridge.ts), else metadataService.getObject.

Both bridges read the SERVED object, where a picklist-bound field already carries its list's options, so the served-form predicate declaresOptions is the one that matches; the door reads the authored body and uses declaresOptionsOnceServed. A picklist-bound select reads ok through MCP exactly as at the door (pinned below).

Tests

  • packages/mcp/src/mcp-validate-expression.option-labels.test.ts (new, 13 cases): drives the real stdio bridge (createStdioDataBridge over a protocol serving the measured served shape) through the shared tool handler.
    • ⭐ label(record.first) and label(record.kind) + label(record.first) answer ok: false with one error, code: 'label-field-no-options', params: { field: 'first' }.
    • Controls read ok: inline select / radio options, the picklist-bound select, mixed expressions. label(record.nope) keeps exactly ['unknown-field']. The validation, flow_condition and template sites answer deep-equal to the same bridge without optionFieldsOf. A bridge without optionFieldsOf answers as before. describe_object's keys are unchanged.
    • PARITY: for five expressions, the same object goes through the door (runRuntimeAuthoringRules on the authored body, picklist-bound tier with no options) and through validate_expression (served body); same verdict, same error count, and each door message contains the tool's message.
  • packages/runtime/src/domains/mcp-option-fields-read.test.ts (new, 2 cases): the HTTP bridge the real /mcp entry builds (HttpDispatcher.handleMcp) answers ['kind', 'stage', 'tier'] and null for an absent object, from the merged listing and from the metadata service on a host with no merged read; its describeObject fields carry no options reading.
  • Census pins updated for the new bridge member: stdio-data-bridge.stored-metadata-body.test.ts (BRIDGE_MEMBERS) and mcp-write-response-internal-fields.tripwire.test.ts (RECIPES), both "no row body / no write result".
Red first (base 7a48bf7 sources, new tests)
  • mcp: packages/mcp/src/mcp-http-tools.ts and stdio-data-bridge.ts restored from 7a48bf73 (on-disk check: optionFieldsOf count 0 in both), new test file run: 4 failed | 9 passed (13). Red: the stdio bridge's optionFieldsOf case, both ⭐ LIT cases (AssertionError: expected true to be false on ok), PARITY. Restored with git checkout HEAD --, git diff HEAD empty.
  • runtime: packages/runtime/src/domains/mcp.ts restored from 7a48bf73: 2 failed (2), TypeError: bridge.optionFieldsOf is not a function. Restored, git diff HEAD empty.
Ablations (one line each, scripts/ablation-replace.mjs, src-resolved suite so no build leg)
  • Drop optionFields from the hint (...(optionFields ? { optionFields } : {}) → ...({} /* ABL_28715 */), marker count 1 on disk): 3 failed | 10 passed: the two ⭐ LIT cases and PARITY (its refused rows). Every control stays green. Restore blob 999355773ff1 = HEAD blob.
  • Ask on every site (optionLabels && … → (/* ABL2_28715 */ true || optionLabels) && …, marker count 1): 1 failed | 12 passed: the site control (validation: label(record.first) == 'x' answers ok: false where the door answers ok). Restore blob = HEAD blob.

Acceptance notes

  • Clause-② deviation from the claim. The claim expected no; I declare yes (widening) because optionFieldsOf is a new optional member on the exported McpDataBridge type. No wire shape changes. The order says the seat runs a contract review for a widening.
  • Sites. The tool judges label() targets on the formula site only, because that is the only site where the door does (judgeFieldFormula in @objectstack/lint). If the door later judges validation predicates too, VALIDATE_SITE_MAP takes optionLabels: true on that row.
  • Cost. validate_expression on the formula site now makes a second object read through the bridge (the merged listing on the HTTP bridge, the same listing describeObject already reads). It is an authoring tool called once per check.
  • The order's per-bridge parity is pinned on the measured served shape in each package; a single real-kernel MCP round trip through both transports is left to the dogfood suite's existing MCP rigs (not added here).

Verification

Local, on head 46c4246f:

  • pnpm --filter @objectstack/mcp typecheck: exit 0. pnpm --filter @objectstack/mcp test: Test Files 49 passed (49), Tests 626 passed (626).
  • pnpm --filter @objectstack/runtime typecheck: exit 0. pnpm --filter @objectstack/runtime test: Test Files 506 passed (506), Tests 8206 passed, 19 skipped (8225), lock VERDICT command-exit 0 (re-run after the @objectstack/formula alias).
  • Derived gate families (node scripts/pm/dispatch-gates.mjs --commands, 60): all 60 exit 0, including check:type-check-debt (under the lock, VERDICT command-exit 0), check:test-source-alias (red on the first pass for the new runtime import; fixed by the alias) and check:dual-build-cjs-loads (after the full build check:type-check-debt ran). dispatch-gates --ran: "60 derived famil(ies) accounted for — 60 run, 0 NOT-MEASURED". Record: pm-orders/server/28715-gates-ran.txt.
  • CI runs the whole farm; the seat reads its check-runs.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (no spec change)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (repo-wide lint is CI's run)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…or does (#28715)

Both MCP bridges answer optionFieldsOf(name) from the served object
describeObject reads, through @objectstack/formula's declaresOptions, and
the validate_expression handler hands it to the shared validator as
ExprSchemaHint.optionFields on the formula site. describe_object's answer
is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5pqvaBsS9WCP8ecER5
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/mcp, @objectstack/runtime, touching 6 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/runtime/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/authorization.mdx (via buildMcpBridge (symbol, a top-level function))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/runtime/vitest.config.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 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 d08b859a7177cf16d6190163f2e42efa2d980d3a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8c5ebc764aece48d8239b405c6fb537b2b690ecd — the merge of head 46c4246f66c0528d68c74ad9a8446190e4bf0ce6 into base d08b859a7177cf16d6190163f2e42efa2d980d3a, 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 8c5ebc764aece48d8239b405c6fb537b2b690ecd && git checkout 8c5ebc764aece48d8239b405c6fb537b2b690ecd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d08b859a7177cf16d6190163f2e42efa2d980d3a 46c4246f66c0528d68c74ad9a8446190e4bf0ce6 && git checkout -B drift-repro d08b859a7177cf16d6190163f2e42efa2d980d3a && git merge --no-ff 46c4246f66c0528d68c74ad9a8446190e4bf0ce6

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

⚠️ 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 d08b859a7177cf16d6190163f2e42efa2d980d3a → 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#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 46c4246f66c0528d68c74ad9a8446190e4bf0ce6
Local-runs: none

Face: declaration — record-recognisers.mjs --paths <9 changed paths> derives no schema, rules or narrowing face; the committed changeset .changeset/28715-mcp-validate-expression-label-targets.md carries Clause-②: yes (widening), so the review owed is the declaration face alone (--face declaration ⇒ TIER_DEFAULT). Reviewed the diff of head 46c4246f against merge-base 7a48bf73 (on origin/main), the card (#28715: triage 6559838316, claim 6561442620, os-dev-report 6562313371), the order, the PR body and the head's check-runs. No ④ on this face.

Read at 2026-11-01T18:56Z; the head is pinned above.

① Derived judgments

  1. One judge, same code and sentence (order ruling 1). CORRECT. packages/mcp/src/mcp-http-tools.ts keeps the single validateExpression(role, expression, hint) call and only adds optionFields to hint (...(optionFields ? { optionFields } : {})). The refusal is the validator's own checkOptionLabelTargets (packages/formula/src/validate.ts), code label-field-no-options, sentence optionlessLabelSentence(field) (packages/formula/src/stdlib.ts), the one the door's lint pass (packages/lint/src/validate-expressions.ts, formula-field expression, optionFields: optionFieldNames) surfaces as expression-invalid. The tool returns result.errors unprojected, so code, params.field and message are byte-identical to the door's. No second spelling of "declares options": both bridges import declaresOptions from @objectstack/formula. An object with declared fields and no option-bearing field answers [], which is truthy and still hands the check its (empty) set — the same verdict the door gives on that body.
  2. Predicate matches the document each bridge reads (order ruling 2). CORRECT. Stdio: optionFieldsOf reads objectView.getObject(name) — the very read describeObject makes (stdioObjectView: readMergedObjects via protocol.getMetaItems({ type: 'object' }), else metadataService.getObject). HTTP: describeObject and optionFieldsOf now share one readServedObject(name) (merged readMergedItems(protocol, 'object'), else meta.getObject), so the two cannot read different objects. Both documents are served bodies: the registry's finishServedObject → resolvePicklistFields (packages/objectql/src/registry.ts, resolvePicklistFieldsOnto) writes a picklist-bound field's resolved options beside picklist, and the boot bridge copies registry.getAllObjects() — that served body — into the metadata service (packages/objectql/src/plugin.ts, metadataService.register('object', …)), which is why the dev's probe read resolved options on the fallback too. So declaresOptions (served form) is the matching predicate, and the picklist-bound tier reads ok on both bridges (pinned: stdio ['kind','stage','tier'] in mcp-validate-expression.option-labels.test.ts; HTTP both read paths in mcp-option-fields-read.test.ts). I found no bridge path that serves the AUTHORED form on a kernel that runs ObjectQL. Residual, not a must-fix: a host with a metadata service but no registry (no ObjectQLPlugin) would hold authored bodies and declaresOptions would refuse a picklist-bound label() there; such a host evaluates no formula and is not a measured MCP deployment. Noted under ③.
  3. Site gate (order: parity with lint per site). CORRECT. Lint hands optionFields on exactly one validateExpression call, the formula field's expression (validate-expressions.ts ~3231); the validation predicate call (~2384), the flow-condition call (~2480) and the template calls (~2874, ~3751) hand none. VALIDATE_SITE_MAP marks formula alone optionLabels: true, and the handler asks the bridge only then; the site control pins validation/flow_condition/template deep-equal to a bridge without the member, and the second ablation (ask on every site) reds that control. main has moved validate-expressions.ts since the merge-base (b357250, flow message slots); that commit touches no optionFields/declaresOptions line, so the per-site reading above holds on the landing base.
  4. The new optional bridge member. CORRECT and additive. optionFieldsOf?(name): Promise<readonly string[] | null> is optional on McpDataBridge, read behind bridge.optionFieldsOf so an implementer that omits it gets no label() target check (the validator's opt-in: optionFields absent ⇒ check does not run) — pinned by the "bridge without optionFieldsOf answers as before" control. McpDataBridge is re-exported from packages/mcp/src/index.ts and the package exports map addresses ./dist/index.*, so the member lands on the published accept set: the Clause-②: yes (widening) declaration (deviating from the claim's expected no) is the right reading, and the PR body says so. describe_object is unchanged: neither bridge's projection (name/type/label/required per field; name/label/fields/enableFeatures) gains a key; the HTTP describeObject hunk is a pure factor-out into readServedObject; pinned on both bridges. Census pins (BRIDGE_MEMBERS, RECIPES) classify the member as reading no row body and writing no record — right, it reads field names off the object definition only.
  5. Unknown field / other sites unchanged. CORRECT. checkOptionLabelTargets judges only names in schema.fields, so label(record.nope) keeps exactly ['unknown-field'] (pinned). Non-record sites are untouched (see 3).
  6. Controls, ablations, parity, red-first. Present and sound. Red-first on base sources: mcp 4/13 red (bridge member, both LIT, PARITY), runtime 2/2 red (optionFieldsOf is not a function). Ablation 1 (drop optionFields from the hint) reds the two LIT cases and PARITY only, every control green — the order's predicted shape. Ablation 2 (ask on every site) reds the site control only. PARITY drives the same object through runRuntimeAuthoringRules (authored body, picklist-bound tier with no options) and through validate_expression (served body) for five expressions: same verdict, same error count, door message contains tool message. The order's parity hypothesis is met on the stdio transport; the HTTP bridge is pinned on the same served shape through the real /mcp entry (HttpDispatcher.handleMcp), and a real-kernel round trip through both transports is declared out of this PR. packages/runtime/vitest.config.ts gains the @objectstack/formula source alias that check:test-source-alias prescribes for the new import; the alias is anchored and scoped to the bare specifier.
  7. Overlap. At 2026-11-01T18:56Z, by REST, the open PRs on packages/mcp/src/ are fix(mcp): validate_expression refuses the label() call the object door refuses #28774 itself and fix(mcp,runtime)!: the MCP data tools refuse to filter, sort, group or aggregate on an internal field, with the data door's own 400 INVALID_FIELD #28772; only fix(mcp,runtime)!: the MCP data tools refuse to filter, sort, group or aggregate on an internal field, with the data door's own 400 INVALID_FIELD #28772 (draft, head 79470662, internal-field evaluation refusal) touches stdio-data-bridge.ts, in the import block (~133–160), a new helper (~548) and the query/aggregate arms (~954, ~1077). fix(mcp): validate_expression refuses the label() call the object door refuses #28774's hunks are the declaresOptions import after line 127 and optionFieldsOf between describeObject and query (~945–958): different lines, no semantic overlap. Whichever lands second rebases textually at most.

② Semver level

@objectstack/mcp: patch, @objectstack/runtime: patch, beside Clause-②: yes (widening) and Metadata-protocol: none on both the PR body and the changeset. CONSISTENT: under ruling A (scripts/check-changeset-no-major.mjs, "the level follows the protocol": none ⇒ every introduced entry patch) and AGENTS.md §3 ("a non-protocol change, widening or breaking, ships patch"). Metadata-protocol: none is right — nothing an author can write is added, refused or re-read; the door already refused this formula (#28714). The head's Check Changeset run concluded success. The widening is a TypeScript-only optional member; no tool input or output shape moves, so no wire consumer is affected.

③ Boundary flags

  • Author: PR user.login = objectstack-fleet[bot]; no relay login to name.
  • Claim deviation (dev flag): claim expected Clause-②: no; measured yes (widening). ANSWERED in ① 4: correct, and this record is the review the order said the seat would run for a widening.
  • open_questions: none declared.
  • out_of_scope_findings (dev, carrier none, not filed): Studio's live CEL lint (celAuthoring.ts) is another face of the same judge that cannot see field options. ACCEPTED as out of scope here: it is a client surface outside packages/mcp, unmeasured, and no finding of this review depends on it. Not escalated; the seat may file a p3 card from the dev's note when it measures.
  • Reviewer's residual (not a must-fix): declaresOptions on a host whose metadata service holds AUTHORED bodies (no ObjectQLPlugin, so no boot-bridge served copy) would refuse a picklist-bound label() through MCP where the door accepts it. No measured MCP deployment has that shape (the MCP data tools need the engine), and the order ruled the predicate by the document measured on real kernels. Recorded so a future host without the registry knows which reading to re-measure.
  • CI at 2026-11-01T18:56Z: 45 check-runs on head 46c4246f; 0 red; 16 still in_progress (Build Core, Test Core 1–8, Type Check ×3, Lint ×4). Every completed required-family run is success (Check Changeset, Governed Surface Queue Guard, PR-body guards, PR Size, Auto Label, Lint · PM dispatch-gates). Landing precondition ② (all green) is the seat's to re-read before enqueue; this record does not wait on it.
  • Local gate claim: the dev's 28715-gates-ran.txt records 60 derived families exit 0 on this head; read, not re-run.

Implemented-by: claude/project-thread-d7vyq6
Reviewed-by: session_0186vfjHHMsRXC9EUT9hTFMo

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-11-01T18:57Z · head 46c4246f · #28715

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.

  1. Shape:
    • A draft against main with Fixes #28715, carrying patch changesets for @objectstack/mcp and @objectstack/runtime; Clause-②: yes (widening) and Metadata-protocol: none.
    • check-governed-merges --pr 28774 reads NOT governed (0 of 9 paths), +404 / -8. Landing tier S.
    • The claim (6561442620) names this PR's branch, claude/project-thread-d7vyq6. The claim expected no; the dev measured a new optional member on the exported bridge type and declared the widening.
  2. Against the order:
    • On the formula site, MCP validate_expression now answers what the authoring door answers for label() over a field without options: the same code and sentence, from the one validator. The only change is the optionFields hint.
    • The predicate is the served form, which matches what both bridges read. The dev measured this, and the review confirms that a picklist-bound select and an inline-options select both read ok.
    • describe_object is byte-unchanged on both bridges (pinned). Other sites are unchanged, matching what lint passes.
    • Red on base (mcp 4, runtime 2); two ablations red only their own cases.
  3. Contract review: owed (widening). PASS on this head, no must-fix (6562403592).
  4. Gates: the dev reports 60 of 60 derived gate families exit 0. CI on this head was in progress with 0 red at review, and auto-merge waits for it.
  5. Overlap: draft fix(mcp,runtime)!: the MCP data tools refuse to filter, sort, group or aggregate on an internal field, with the data door's own 400 INVALID_FIELD #28772 touches stdio-data-bridge.ts in different hunks.
  6. Landing: pr_ready + automerge_enable through the relay.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 18:57
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 18:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit fc28644 Nov 1, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-d7vyq6 branch November 1, 2026 19:33
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

Projects

None yet

1 participant