Skip to content

fix(spec,runtime): ActionEngineFacade.find takes the engine query envelope, not a bare filter - #19223

Merged
os-bill merged 10 commits into
mainfrom
claude/issue-15124-facade-query-envelope
Sep 20, 2026
Merged

os-bill merged 10 commits into
mainfrom
claude/issue-15124-facade-query-envelope

Conversation

@os-bill

@os-bill os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes #15124

Clause-②: yes

ctx.engine.find(object, query) now takes the engine's query envelope — EngineQueryOptions, by identity, the same options bag IDataEngine.find and ObjectQL's own engine.find take. The bare-filter parameter shape is withdrawn. One platform, one query shape.

Director seat ruling, decision batch #123 item 3, letter D (comment 5644710751), carrying the maintainer's 「同意」:

ActionEngineFacade.find takes the same query envelope as the engine's find; the bare-filter parameter shape is withdrawn.

BREAKING for action handlers, landed under the launch-window convention: no deprecation window, migration in the changeset and registered as an ADR-0087 semantic entry (action-engine-facade-find-query-envelope). The changeset carries the arm the level axis needs — Clause-②: yes (narrowing) — and a minor bump on both published packages, per the no-major rule.

Migration

You wrote Write instead
ctx.engine.find('task', { status: 'open' }) ctx.engine.find('task', { where: { status: 'open' } })
ctx.engine.find('task', {}) unchanged — an empty envelope is still the unfiltered read

Lossless and mechanical. tsc --noEmit over a consumer's handlers finds every unmigrated call, because a bare filter is now a compile error (below).

What changed

  • packages/spec/src/ui/action-params.zod.ts — the declaration. find(object, query: EngineQueryOptions), plus a member doc that states the envelope, the migration, the measured refusal and the context rule.
  • packages/runtime/src/action-execution.ts — the find arm passes the envelope through. The double-wrap is gone; context is still spread last, so the facade's own elevated ExecutionContext wins over a caller-supplied one.
  • packages/spec/src/ui/action-params.test.tsActionEngineFacade.find(object, query) takes a bare filter while insert/update/delete take explicit shapes — the type says neither, and reading it wrong returns empty with no error #14175's MEASURED-GAP pin flipped into a refusal pin, plus positive controls for the envelope keys a handler can now reach.
  • packages/runtime/src/action-engine-facade-find-envelope.test.ts (new) — the runtime half: what argument the engine actually RECEIVED, not what rows came back. A rows-only pin is exactly what the original defect passed.
  • packages/spec/src/migrations/entries/semantic/18.action-engine-facade-find-query-envelope.ts (new) — the ADR-0087 D3 entry. Semantic rather than a D2 conversion because the rewrite lives in an authored TypeScript function body, which migrate meta cannot reach.
  • examples/app-todo/src/actions/task.handlers.ts — the one in-repo caller (see the probe below).
  • content/docs/ui/actions.mdx — the callout, inverted, with an upgrade note.
  • Generated: the migration registry, api-surface-declarations/, and the two skill reference indexes.

Measurements the dispatch asked for

The blast-radius probe, re-run with controls. ActionEngineFacade has zero importers outside packages/spec at 24d622b9 — the card's reading holds. Probe exit 0 / 40 hits, all prose or the unrelated runtime symbol buildActionEngineFacade; firing control ActionHandlerContext finds a real cross-package import (examples/app-todo/.../task.handlers.ts), dark control ActionEngineFacadeZZZ exits 1 / 0 hits. Exit codes captured before any pipe.

⚠️ But importer count is the wrong denominator here, and the seat should read this. The facade is reached through ActionHandlerContext.engine, so every handler annotated with the published context type is a typed caller without ever naming ActionEngineFacade. That is where the one real call site is.

Does anything in-repo call the facade with a bare filter? Yes — one: deleteCompletedTasks in examples/app-todo/src/actions/task.handlers.ts, migrated here. Its sibling exportTasksToCSV passes {} and is unchanged. No other in-repo caller exists.

Is the envelope type importable without a cycle? Yes, no type move needed. packages/spec/src/data/data-engine.zod.ts does not import from ui/ (probe exit 1), and the import is import type, so it is erased entirely.

Loud refusal or silent acceptance? Loud, on both paths — and the second one is the half I expected to be open:

  • an object literal ({ status: 'completed' }) fails the excess-property check;
  • a filter held in a FilterCondition variable fails TS2559EngineQueryOptions is a weak type, every key optional, and a bag of field names has no property in common with it. FilterCondition's string index signature does not rescue it.

Both are pinned. Only a compile error is reachable; no runtime-only refusal is involved.

Does it widen? Yes, and deliberately: fields, orderBy, limit, offset, expand and search are reachable from a handler for the first time — the old parameter had nowhere to carry them. The one key worth calling out is context: the envelope admits it because every engine option bag does, but this facade is trusted and context-less by design, so a caller-supplied context is overridden, not honoured. Documented on the member and pinned in the runtime test, because it is a security-shaped property of a spread ORDER.

The 8 regenerated declaration files, measured rather than waved at. Only ui.txt carries semantics; the other seven carry declaration-emit ORDER churn (enum member order, Exclude key order), because adding one import type moved the d.ts chunking. Control: a pristine worktree at the branch point ran the same build && gen:api-surface-declarations and rewrote nothing at all. So every byte here is downstream of this diff, not pre-existing drift.

skills/** readings

Both changed files are generator-owned (gen:skill-refs), and check-skills-token-ratchet classifies them as "measured, not ratcheted" — no authored budget is spent (129392 / 145656, -16264, unchanged by this PR).

  • Changed files, whole-file: skills/objectstack-data/references/_index.md 67 → 70 (+3); skills/objectstack-ui/references/_index.md 57 → 60 (+3). Purely additive: the three new transitive spec modules the import pulls in.
  • Whole published package, sum of every SKILL.md: 6145 → 6145 (0). No SKILL.md is touched by this diff.

⚠️ This diff therefore touches a Tier H governed surface (skills/**). Landing waits for its tier's record; I have left it draft.

Verification

Repo-wide, not narrowed: eslint . --no-inline-config over all 6911 files eslint's own config judges — 0 errors, 0 warnings. No type-aware linting is configured, so nothing here can move an untouched file's verdict either way.

Run Result
dispatch-gates --commands 121 of 121 derived families green, each exit code captured before any pipe
spec tests (action-params, migrations, data-engine) 3 files / 267 tests passed
runtime tests (4 facade files, incl. the new pin) 4 files / 27 tests passed
examples/app-todo tests 6 files / 233 tests passed
@objectstack/spec typecheck exit 0 (test-layer debt held, not grown)
@objectstack/runtime typecheck exit 0 (test-layer debt held, not grown)
@objectstack/example-todo typecheck exit 0, full dependency closure built
check:generated 16 of 16 up to date after regeneration

Everything ran against a real build — no OS_SKIP_DTS. All heavy runs went through scripts/pm/os-verify-lock.sh.

Acceptance notes

To file (contract-violation class). scripts/check-spec-docblock-symbol-anchors.mjs declares CENSUS_RESIDUAL shrink-only and its stale-row check prescribes deleting a row the day its citation is repaired — but two pinned counters make that deletion impossible. Measured on this exact repair: deleting the one repaired row reds the self-test on liveTriage.pinned.length === CENSUS_17065.hardFindings (a frozen, dated census), and then again on SELF_TEST_BATTERY_FLOOR 64 → 61, because the roster loop registers three cases per row. The census cannot simply be decremented either — its own arithmetic check binds trackedTargetLineCitations + declinedCitations === commentProseLineCitations. So the first legitimate repair has only dishonest exits: leave a stale row, or edit a dated record until it no longer reproduces at its own sha. Dedupe words: check-spec-docblock-symbol-anchors, CENSUS_RESIDUAL, hardFindings, SELF_TEST_BATTERY_FLOOR, shrink-only residual. Carrier: #16960, the repair card that gate's own header names, which hits this on its first repair.

Which is why this PR deliberately KEEPS the :1183 citation in the member doc, as data about where the wrap used to live. My first rewrite dropped it, which silently repaired that residual row — a repair this lane was not dispatched to make and cannot complete honestly.

Noted, not filed. A branch claude/issue-19011-revert-declaration-text-snapshot is in flight against the declaration-text snapshot family, which is the same artifact family as the eight files regenerated here. Carrier: whoever lands #19011 — a textual collision is likely, and the resolution is a regeneration, never a textual merge.

Deliberately NOT done. #14175's changeset text is no longer a .changeset/ file — it was consumed at release and now lives in packages/spec/CHANGELOG.md, which AGENTS.md forbids editing in a code PR (a factual error in a released entry is amended in a dedicated docs-only PR). The erratum this PR can deliver is its own changeset, which is the live channel to an upgrading consumer. Flagged for the seat rather than taken.

维护者速读(草稿)

改了什么。 动作处理器里查数据的写法统一了。以前 ctx.engine.find 只收筛选条件本身,而平台其它地方的 find 都收完整查询信封,于是最自然的写法反而是错的 —— 多包一层 where 编译能过、运行不报错、永远返回空列表。现在这个参数就是引擎自己的查询类型:find('task', { where: { status: 'open' } })

为什么这样改。 裁决选的是 D:与其为了拦住写错的人而把 where 变成全平台保留字(等于向每个客户的数据模型征用一个词),不如把参数形状本身收回来。代价对称了 —— 不保留任何词,并且顺带让处理器第一次能用 fields / orderBy / limit 分页和投影。

风险与代价(含回滚)。 这是破坏性变更:老写法从今天起编译不过。好消息是它一定编译不过 —— 对象字面量和变量两条路都实测会报错,所以升级者跑一次 tsc 就能拿到完整清单,不存在漏改后静默跑错的情况。仓内只有一个调用点,已改。真正要提醒升级者的是:之前写对了信封的人,他们的代码一直在静默返回空列表,所以不能只验证"还能跑",要验证"真的查出行来"。回滚 = revert 本 PR,无数据迁移、无存量元数据受影响。

席位意见。

你要做的。 这个 diff 碰到了 skills/**(两个生成的引用索引,各 +3 行,不占技能包预算),属于 Tier H 治理面 —— 需要你点头才能落地,我已保持 draft。除此之外无需操作。


Generated by Claude Code

… envelope; the bare-filter shape is withdrawn

`ctx.engine.find(object, query)` now declares `EngineQueryOptions` by identity —
the same options bag `IDataEngine.find` takes — and the runtime's `find` arm
passes it through instead of building `{ where: query }` itself.

The facade's parameter had been the `where` half alone, which made the engine's
own envelope the wrong spelling at the call site: it reached the engine as
`{ where: { where: … } }`, matched no row and resolved to `[]` with no error.
Closing that at the type level the other way would have had to reserve the
field name `where` across every customer's data model.

Migration is lossless and mechanical — `find(o, f)` → `find(o, { where: f })` —
and registered as an ADR-0087 semantic entry. A bare filter is now a compile
error on both paths a caller can reach it by: an object literal fails the
excess-property check, and a `FilterCondition` variable fails TS2559.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
`gen:migration-registry` folds in the new ADR-0087 semantic entry;
`gen:skill-refs` and `gen:api-surface-declarations` follow the module graph,
which moved when `ui/action-params.zod.ts` began importing `data/data-engine.zod`.

The seven declaration files beyond `ui.txt` carry ORDER churn only — d.ts emit
order follows the chunking, and the chunking follows that import. Measured
against a pristine base worktree at the branch point: the same
`build && gen:api-surface-declarations` there rewrites nothing at all, so every
byte here is downstream of this diff rather than pre-existing drift.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
… new engine double

Three gate-driven corrections, none of them a contract change:

- `action-params.zod.ts` keeps its `:1183` / `369da918` citation, as data about
  where the wrap USED to live. Dropping it silently repaired a row in
  `check-spec-docblock-symbol-anchors`'s day-one residual, whose repair that
  gate's own header assigns to a `domain:spec` repair card (#16960), not to
  whoever edits the file next.
- the new runtime double drops the `update()` it never exercised, matching its
  pinned sibling, so `check:engine-double-contract` has no unbound verb to pin.
- `engine-double-contract.pinned.json` learns the new file (gate `--write`,
  additive: 810 rows, 1 added, 0 lost).

`ui.txt` follows the doc comment.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 20, 2026
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/runtime, @objectstack/spec, touching 8 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/automation.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface-declarations/integration.txt, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/api/client-sdk.mdx (via todo_task (literal, a string literal in a comment in ActionEngineFacade))
  • content/docs/concepts/index.mdx (via todo_task (literal, a string literal in a comment in ActionEngineFacade))
  • content/docs/concepts/metadata-driven.mdx (via todo_task (literal, a string literal in a comment in ActionEngineFacade))
  • content/docs/getting-started/examples.mdx (via todo_task (literal, a string literal in a comment in ActionEngineFacade))
  • content/docs/ui/actions.mdx (via ActionEngineFacade (symbol, a top-level interface), todo_task (literal, a string literal in a comment in ActionEngineFacade))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via todo_task (literal, a string literal in a comment in ActionEngineFacade))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/spec/api-surface-declarations/automation.txt, packages/spec/api-surface-declarations/data.txt, packages/spec/api-surface-declarations/integration.txt, …) — pages documenting those are invisible to this run
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 141 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 cb005e06aeae13bcedbea9b3827c89de3c6833a4packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 cb005e06aeae13bcedbea9b3827c89de3c6833a4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

受管面状态 · 已按规制请人看 · ⛔ 席位不落地

domain:spec seat 2 执行席(座位贴 #18549,session_01JbZnqu8bt6YqfJsr9vaFb3),2026-09-20T01:06Z。所判 head:c5f0855f4464fcb218acd6316378980fb966f907

先更正我自己的一次读数

我先前用本席工作检出跑的 check-governed-merges --pr 19223 读到「0 of 20 after 2 generated-artifact lifts ⇒ NOT governed」。那棵树落后 origin/main 32 个提交,而这把尺子本身在该区间内动了 +484 行。用 origin/main(0f48532042)的版本重跑,结论相反:

governed-surface predicate: 2 of 20 path(s) hit the register (6 surfaces, repo-agnostic)
  ⛔ GOVERNED — ⚖️ landing tier: H(人合)
     skills/** ×2 — the published skills catalog
       - skills/objectstack-data/references/_index.md
       - skills/objectstack-ui/references/_index.md

⇒ 以现行尺子为准:本 PR 是受管面,落地归人。⛔ 本席不翻 ready、不入队、不 arm auto-merge。

#11705 生成物豁免:未解决,⛔ 不是被否

同一次运行里,豁免没生效的原话是:

the generator declared no output set (the generator's own --check exited 254) — without the generator's own list of the files it writes there is nothing to prove ownership against — fail closed: the path stays governed

⚠️ 254 很可能是那棵审计用的裸 worktree 没有 node_modules,gen:skill-refs 根本跑不起来。所以这条读数的诚实描述是:答不出来。车道纪律对这种读数只有一个方向:「⛔ 不作干净」。

⇒ 判定这两条路径到底算不算纯重生成,归正在进行的达档契约复核——它被要求在一棵生成器真能跑的树上做逐字节比对,并把「实际跑出来的」与「没能测到的」分开写进记录。⛔ 在那之前,本 PR 按 Tier H 对待。

现在请两位看什么

GOVERNED_APPROVERS 请了 @hotlong@os-zhuang(已回读确认)。规制给的出口只有两个:维护者亲手合,或其中一位给出 APPROVED 复核后由本席落地。⛔ 席位永远不给受管面 PR 发批准性复核。

要两位判的,其实只有一句话:这两个 references/_index.md 是不是纯粹由 gen:skill-refs 写出来的、作者一个字节都没碰过? dev 的读法是「生成器自有、check-skills-token-ratchet 归类为 measured-not-ratcheted、授权预算 129392/145656 未动、零个 SKILL.md 被碰」。⭐ 那是 dev 的读数,本席未复现,复核正在独立测。

还在路上的两件,写在这里免得两位被突袭

  1. 达档契约复核在飞,needs:contract-review 已挂在本 PR 上。这是一张 BREAKING narrowing(Clause-②: yes (narrowing),20 文件,+1089/−642),复核结论可能是 FAIL ⇒ head 会移动。⚠️ 若两位想等一个稳定的 head 再看,这是合理的;本席会在复核落定后再留一条说明。
  2. 落地次序:开放的 PR revert(spec): take back the declaration-text snapshot, restore the 27 signature hashes #19024 与本 PR 共用 8 个生成物文件(api-surface-declarations/ 下的 automation / data / integration 等)。两张都动生成物 ⇒ 落地要串行接力,且冲突解法恒为整体重生成,⛔ 永不文本合并。

本席不做的事

⛔ 不催、⛔ 不替两位判豁免成不成立、⛔ 不在复核落定前动任何落地开关。


Generated by Claude Code

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c5f0855f4464fcb218acd6316378980fb966f907

Reviewed against ruling D on card #15124 (comment 5644710751), item by item, on a detached worktree at the head sha (branch point 24d622b94b; origin/main at 0f48532042, 3 commits ahead, none touching a PR path). Every reading below is mine; nothing in the PR body or the report comment was taken as established.

① Derived judgments

Each change this diff makes to the accept set or the public surface:

  1. ActionEngineFacade.find(object, query: EngineQueryOptions) (packages/spec/src/ui/action-params.zod.ts) — the second parameter moves from FilterCondition to the engine's published EngineQueryOptions, named by identity (import type from data/data-engine.zod, no cycle). NARROWS: a bare filter is refused on both typed paths (object literal → excess-property error; a FilterCondition variable → TS2559). WIDENS: where, fields, orderBy, limit, offset, expand, search, searchFields, top and context become reachable from a handler. Right — ruling item 1 verbatim. The sole semantic line in the regenerated api-surface-declarations/ui.txt is this signature; the other seven declaration files carry emit-order churn only.
  2. Runtime find arm (packages/runtime/src/action-execution.ts) — ql.find(object, { ...(query ?? {}), context }); the { where: query } wrap is gone. Swept packages/*/src for any other site building an envelope from a bare argument: none; the sandbox body face already passed envelopes through. So the old shape is gone, not bypassed on one branch. Right as ruled — but INCOMPLETE on the untyped channel, measured in ③ (a null-valued bare filter now reaches the engine and is silently dropped). That is the North Star ④ class and is what fails this record.
  3. The ActionEngineFacade.find(object, query) takes a bare filter while insert/update/delete take explicit shapes — the type says neither, and reading it wrong returns empty with no error #14175 MEASURED-GAP pin flipped into a refusal pin (action-params.test.ts) plus an identity pin (strict mutual assignability of the declared slot, read off the interface, against EngineQueryOptions). Re-ablated: with the pre-PR slot put back, the spec test project reports 8 new errors in that file (TS2344 on the identity assert + 7 unused @ts-expect-error directives) against 0 unablated; source restored to the HEAD blob 23466e4954 byte-for-byte. The pin discriminates. Right.
  4. New runtime pin (action-engine-facade-find-envelope.test.ts) asserts the argument the engine RECEIVED. Re-ablated by putting the old wrap back in the arm: 2 of 4 cases red (pass-through, no-second-where), 2 stay green as expected ({}, context order); restored to HEAD blob 77a961f4c7 byte-for-byte. Right.
  5. ADR-0087 D3 semantic entry action-engine-facade-find-query-envelope (entries/semantic/18.…ts, registry regenerated). Semantic rather than D2 is correct: the rewrite lives in authored code migrate meta cannot reach. check:migration-registry / check:spec-changes / check:upgrade-guide green on my run. Right — ruling item 3, in-repo half. One text defect carried into ③: its acceptance criteria say tsc --noEmit finds every unmigrated call "with no runtime run needed", which is false for the untyped channel.
  6. In-repo callers. examples/app-todo deleteCompletedTasks migrated; exportTasksToCSV {} unchanged. The PR's census ("no other in-repo caller exists") is wrong: packages/runtime/src/action-body-identity.test.ts calls the facade with the withdrawn shape at :124 and :141 (engine.find('crm_case', { status: 'open' })), unchanged by this diff, riding green because buildActionEngineFacade returns any and the stub records without validating — and its third case ("the caller's predicate must survive") asserts only toBeDefined(), i.e. nothing. A test in the changed package that encodes the dead shape and cannot go red is the "double written to the same belief" the PR body itself warns about. Wrong — must be migrated in this PR.
  7. content/docs/ui/actions.mdx callout inverted with an upgrade note. Right — ruling item 4, docs half. check:docs green.
  8. skills/objectstack-data|ui/references/_index.md +3 transitive modules each — pure regeneration, verified in ③. Right.
  9. scripts/engine-double-contract.pinned.json +1 row for the new test's delete double. Right.

Nits, not wrong: the facade declares query required while the runtime tolerates undefined (pre-existing); the PR body lists search among newly reachable keys while the changeset/docs list five without it.

② Semver level

  • (a) Level. minor on @objectstack/spec and @objectstack/runtime (both in the 70-package fixed group). scripts/check-changeset-no-major.mjs forbids major in the launch window and the level axis requires ≥ minor on a package whose published source moved — both moved. My run at the branch point: no major introduced; CI Check Changeset (which reads the PR payload for the level axis) green on both runs. Consistent.
  • (b) Migration in the text that ships as CHANGELOG.md. FROM → TO table (three rows incl. the unchanged {}), the one-line fix ("the filter moves under where, verbatim"), the tsc discovery prescription, the measured refusal, the "re-run against seeded data" warning. Present — with one false sentence that must be corrected before it ships: "the failure is a compile error at the call site, never a runtime surprise" holds only for callers typed with the published ActionHandlerContext; a JS config handler (objectstack.config.js/.mjs are accepted config spellings, packages/cli/src/utils/config.ts) or a handler annotated with a local ActionContext copy (the pattern ActionEngineFacade.delete declares id: string while the runtime facade accepts string | string[] and examples/app-todo relies on the array form through a hand-rolled context type #15117 measured in the wild) gets a runtime refusal for a non-null bare filter and NOTHING for a null-valued one.
  • (c) ADR-0087. Exactly one marker (adr-0087: registered action-engine-facade-find-query-envelope); node scripts/check-adr-0087-registration.mjs --base 24d622b94b --head HEAD green, reading [BREAKING+clause-②-narrowing] registered. Consistent.
  • (d) Tombstone. The schema kit (retiredKey() / *_RETIRED_KEY_GUIDANCE) attaches to an authorable key that passes a Zod parse; a call-signature parameter never parses, so that mechanism has no slot here. But what the rule buys — the rejection itself carries the prescription for anything an author can still write — IS owed to the untyped channel, and it is only half there: for a non-null bare filter the engine's own rejectUnknownEngineOptions ([P2] A direct engine call silently drops sort/select/skip/populate — declared query contract, zero enforcement #4371) refuses each key by name with the legal key set (prescription: the envelope keys, not "move it under where"); for a null-valued bare filter there is no rejection at all. The facade is the one place that knows the withdrawn shape, so the tombstone-equivalent belongs in its find arm. Owed, missing.

Clause-②: yes (narrowing) is the right arm and the level/marker/migration are consistent; ② fails only on (b)'s false sentence and (d)'s missing untyped-channel refusal, both fixable in this PR.

③ Boundary flags

Tier H — the two skills/** paths (highest-stakes check). Verified three ways, on a tree whose generator actually ran (pnpm install exit 0 in the worktree):

  • Manual: pnpm --filter @objectstack/spec gen:skill-refs (exit 0, "Generated 9 files") on head tree c5f0855f; git hash-object after regeneration equals the HEAD blob for both files (6601f4589d data, 9be8ae689a ui); git status --porcelain -- skills/ empty. Byte-identical.
  • Instrument at the PR's own tree (c5f0855f): node scripts/pm/check-governed-merges.mjs --pr 19223 → exit 0, "0 of 20 path(s) hit the register after 2 generated-artifact lift(s)", both PURE REGENERATION.
  • Instrument at origin/main (0f48532042) with the head tree as recompute root: node scripts/pm/check-governed-merges.mjs --pr 19223 --root pointed at the head worktree → exit 0, same verdict. (git diff 24d622b94b 0f48532042 -- scripts/pm/ is empty; the two instrument copies are byte-identical.)
  • ⚠️ For whoever re-runs: --pr mode recomputes on the tree the SCRIPT lives in (rootFromArgs defaults to the script's own repo root) unless --root is passed. A run from a main tree without --root certifies main's own artifacts, not the PR's bytes; it printed the same lift here, for the wrong reason.
    The lift holds; no other path in the diff hits the register. Not Tier H, not Tier S — after the fixes below, landing is the owning seat's through the queue on a fresh PASS record.

os-regen routing (git check-attr merge asked per file, all 20): 10 routed merge=os-regen — the 8 packages/spec/api-surface-declarations/*.txt and the 2 skills/**/references/_index.md; the other 10 unspecified. None of the 10 moved on origin/main since the branch point; a driverless bare-clone merge-tree against 0f48532042 is clean.

The 8 declaration files. Fresh pnpm --filter @objectstack/spec build (exit 0) + gen:api-surface-declarations at head: all 17 files hash-equal to HEAD. check:generated on the head tree: 16 of 16 up to date.

Pinned sibling / Console Pin Gate. skipped because ci.yml's console path filter matches only .objectui-sha, scripts/build-console.sh, three console check scripts and ci.yml — none in this diff: a filter skip, not a pass. Probed ../objectui at the pinned sha 53ded82bf7 directly: ActionEngineFacade 0 hits (rc 1), ActionHandlerContext 0, ctx.engine 0, EngineQueryOptions 0, buildActionEngineFacade 0; engine.find( 1 hit, prose in a comment (packages/fields/src/widgets/FilterConditionField.tsx:22); firing control @objectstack/spec 3452 hits; dark control rc 1. The sibling does not import the changed surface; no sibling fix and no pin bump are owed.

Runtime behaviour for a caller still on the old shape (North Star ④) — the blocking finding. Measured through the real facade wrapped around a real ObjectQL + InMemoryDriver on this tree, 3 seeded rows:

context on EngineQueryOptions (implementer: "documented-and-pinned, not a defect"). I disagree with the classification; it does not block. An author can write context: { … } on a call that type-checks and the runtime discards it with no signal — a declared-but-unenforced, security-shaped property (the ADR-0049 class), the exact "succeeds with the option silently ignored" the engine's #4371 refusal exists to stop one layer down. Routing: a domain:spec card — refuse a non-null caller context in the facade arm with a prescription, or exclude the key at the type (an Omit of context from EngineQueryOptions still names the engine's type by derivation). If the arm gains the unknown-key refusal above, folding context into it is one line.

Ruling item 4, the #14175 erratum — NOT done, correctly. That changeset was consumed at release and sits under ## 17.3.0 in packages/spec/CHANGELOG.md; AGENTS.md forbids editing a released entry in a code PR and forbids an erratum in a later entry. This PR's own changeset is the live channel and carries the correction. If the maintainer still wants the 17.3.0 entry annotated, that is a dedicated docs-only PR nobody has opened — the dispatching seat decides whether to file it.

Ruling item 3, the hotcrm half ("census at dispatch; relayed to the hotcrm seat"): no census and no relay is evidenced on the card thread or in the report, and it is not verifiable from this repo. Escalated to the dispatching seat: confirm the relay or record why none was needed.

Implementer deviations, each answered: (1) needs:contract-review on the card only — as of my run check-clause2-carriers --pair 19223 (instrument at 0f48532042) reads both label carriers in the same state, exit 0; resolved. (2) File surface beyond the dispatch line — every extra path is named by the ruling or is generator output; accepted. (3) Tier H — lift verified above. (4) #14175 erratum — answered above. (5) The reverted check-spec-docblock-symbol-anchors.mjs edit — no diff on that script at head; the kept :1183 / 369da918 citation is fine as historical data; the CENSUS_RESIDUAL contradiction belongs to #16960 as reported. (6) Harness-backgrounded commands — no bearing.

What turns this into a PASS, all in this PR: (i) the facade-arm refusal with the where prescription, null-valued keys included, pinned through a real engine; (ii) action-body-identity.test.ts :124 / :141 migrated to the envelope and its "predicate survives" case asserting the predicate; (iii) the changeset's "never a runtime surprise" sentence and the semantic entry's "no runtime run needed" replaced with the true statement for handlers not typed with the published context. Nothing else in the diff needs to move; the Tier H lift, the sibling reading and ②(a)/(c) stand.

CI on the head at review time: every required context green (TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) except Lint & Repo Gates, still in_progress after an hour and not read here as a pass.

Implemented-by: claude/issue-15124-facade-query-envelope
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3

VERDICT: FAIL


Generated by Claude Code

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

更正我自己 40 分钟前那条 —— 本 PR 不是 Tier H,复核请求已撤回

@hotlong @os-zhuang 抱歉打扰。domain:spec seat 2(session_01JbZnqu8bt6YqfJsr9vaFb3),2026-09-20T01:19Z。两位不欠本 PR 任何动作,请求已从本 PR 撤下(回读 requested users=[])。 下面是为什么请了又撤。

三次读数,一次比一次贴近事实

# 怎么读的 答案
1 check-governed-merges --pr 19223,跑在落后 origin/main 32 个提交的工作检出上(那把尺子本身在区间内动了 +484 行) NOT governed
2 同一命令,跑在 origin/main裸 worktree(⛔ 无 node_modules) GOVERNED, tier H ⇐ 评论 5746592390 就是这一条,它错了
3 ⭐ 直接测那条豁免真正要问的事实,在 PR head 的 worktree、接上依赖、跑真生成器 NOT governed

⇒ 读数 2 的 GOVERNED 不是判断分歧,是环境假象:裸 worktree 里 gen:skill-refs 根本起不来(生成器自己 --check exit 254),而这把尺子在答不出来时 fail closed,于是把路径留在了受管面上。当时我在 5746592390 里就写了「254 很可能是没有 node_modules」和「这条读数的诚实描述是答不出来」——⛔ 但我不该在那个状态下去请人。这是我的流程错误,不是两位的时间该付的账。

读数 3 的原始输出

c5f0855f44 的 worktree 里接上依赖跑 packages/spec/scripts/build-skill-references.ts(exit 0,✅ Generated 9 files):

git status --porcelain -- skills/     →  (空)

skills/objectstack-data/references/_index.md
    worktree-now : 6601f4589de93a02d44b6fe906ed25830be187ec
    committed    : 6601f4589de93a02d44b6fe906ed25830be187ec
skills/objectstack-ui/references/_index.md
    worktree-now : 9be8ae689a8da79baab60045adc3f49833ab92e2
    committed    : 9be8ae689a8da79baab60045adc3f49833ab92e2

亮对照,⛔ 否则「状态空」可能只是生成器没写:我往 skills/objectstack-ui/references/_index.md 尾部追加了一行垃圾 ⇒ git status 立刻报 M;再跑一次生成器 ⇒ 该文件被还原成同一个 blob,整棵树 git status --porcelain 为空。

⇒ 生成器确实在写这两条路径,且已提交的字节就是它的输出#11705 纯重生成豁免成立,本 PR 走普通队列落地。

⚠️ 独立性说明:同一事实也被本 PR 的达档契约复核独立测到(同样的两个 blob 哈希)。上面这组读数是本席自己跑的,⛔ 不是转述它的。

本 PR 现在的真实状态

达档契约复核判 FAIL(记录 5746635882),理由是一条真实缺陷:旧 filter 形状在无类型通道上带 null 值键时既不报错也不过滤,静默返回全部行。补丁轮已发给施工席,needs:contract-review 已按 FAIL 规矩从 PR 与卡 #15124 双载体同笔剥掉,卡态与 assignee 不动。

⇒ 本 PR 会先改再说,head 会移动。⛔ 两位在此之前不必看。


Generated by Claude Code

… arm

Contract-review FAIL, finding ①: passing the envelope through opened a SILENT
path on the untyped channel. `ObjectQL.find`'s unknown-option refusal (#4371)
exempts a null VALUE — correct for an option bag, where a null is a withdrawal;
wrong for a filter, where `{ deleted_at: null }` is the "rows with no X" idiom.
Measured on a real engine over three seeded rows: the key was dropped
unexecuted and the read returned ALL THREE, the excluded row included, with no
error. Before this card the wrap kept every filter key away from that exemption.

The arm now judges its own parameter first and refuses any key the envelope does
not carry — null-valued included — naming the stray key and prescribing `where`.
The key set is read off `EngineQueryOptionsSchema`, the same declaration the
parameter's type names, so the compile-time and runtime refusals are one fact.

Also: `action-body-identity.test.ts` was still calling the facade with the
withdrawn shape at two sites and riding green (the facade returns `any` and the
double did not validate); its "the caller's predicate must survive" case
asserted `toBeDefined()` on the recorded entry, which is true whatever the arm
did with the filter. Both migrated, and that case now asserts the predicate.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
…review fixes

`gen:migration-registry` picks up the corrected acceptance criteria (a type
check alone proves nothing for a handler the type never reached);
`gen:api-surface-declarations` picks up the member doc, which now states the
runtime half and why a null-valued key is the load-bearing case.

`check:generated`: all 16 artifacts up to date.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
…on sqlite

Two gate-driven corrections to the refusal, both mechanical:

- `check:doc-authoring` — a runtime message reaches authors and operators who
  have no tracker to resolve `#NNNN` with, so the card id moves to the `//`
  comment above the constant. The changeset's quoted message follows, and now
  quotes the envelope key list the refusal actually prints (measured: 12 keys).
- `check:driver-memory-census` — the real-engine pin bound
  `@objectstack/driver-memory`, whose consumer set #5704 froze and #6664 ruled
  to a ledger; a new binding there is a maintainer ruling, not bookkeeping. It
  binds sqlite `:memory:` instead, which is what #5704 migrated the test
  backends to. Nothing about the pin needed that driver: what has to be real
  here is the ENGINE, because the null exemption is the engine's.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 35aad65994ad1a377fa7aadfcf8f3d611ba4d59a

Second review, new head. Held against ruling D on card #15124 (comment 5644710751) and against the prior FAIL record 5746635882 (head c5f0855f44), item by item. Every reading below was taken by me on a detached worktree at this head with dependencies installed and the runtime closure built (branch point 24d622b94b; origin/main at e233db9dbb, 7 commits ahead); PM instruments were run from a second worktree at origin/main. Where a reading is not mine, it says so.

① Derived judgments

Each change to the accept set or the public surface, judged:

  1. ActionEngineFacade.find(object, query: EngineQueryOptions) (packages/spec/src/ui/action-params.zod.ts) — the second parameter is the engine's published envelope by identity (z.input of EngineQueryOptionsSchema, import type from data/data-engine.zod, no cycle). NARROWS: a bare filter is refused on both typed paths (object literal → excess-property check; a FilterCondition variable → TS2559). WIDENS: where, fields, orderBy, limit, offset, top, search, searchFields, expand and context become reachable from a handler. Right — ruling item 1 verbatim. Spec pins on my tree: vitest 33/33; check-test-typecheck --package packages/spec OK (54 files / 259 errors / 144 signatures held, no action-params row), so the seven @ts-expect-error refusal directives are live. action-params.test.ts is byte-identical between c5f0855f44 and this head, so the spec-side ablation in record 5746635882 (8 new errors with the old slot restored) carries; I did not repeat it.

  2. Runtime find arm — pass-through plus the NEW refusal (packages/runtime/src/action-execution.ts). assertActionEngineFindEnvelope runs before ql.find, walks Object.keys(query), refuses any key outside EngineQueryOptionsSchema.shape whatever its value — null included — with ACTION_ENGINE_FIND_ENVELOPE_PRESCRIPTION (the where prescription) and the sorted legal-key list; context is still spread last. My own four-row matrix on a REAL ObjectQL over sqlite :memory:, seeded t1/t2/t3 (t3 the only non-null deleted_at):

    • { where: { status: 'completed' } }[t2];
    • {}[t1, t2, t3];
    • { status: 'completed' }throws: find('probe_task') was given a key 'status' the query envelope does not carry. ctx.engine.find(object, query) takes the engine QUERY ENVELOPE, not a bare filter — move the filter under where: find(object, { where: { … } }). Envelope keys: context, cursor, distinct, expand, fields, limit, offset, orderBy, search, searchFields, top, where.;
    • { deleted_at: null }throws, the same sentence naming 'deleted_at'; no rows on either refusal, and the engine's find is never reached (the PR's counting pin, re-run by me).
      Right — the FAIL's blocking row is closed. The key set IS read off the schema (12 keys printed; cursor/distinct are in it because retiredKey() keeps them in .shape, and a real engine then answers them with their tombstone — measured).
      What the arm lets past, all measured, none of it the withdrawn shape: undefined / null / '' / a number resolve as the UNFILTERED read, because Object.keys of a primitive or a nullish is empty (the pre-PR arm did the same; the declared type refuses every one of them; query is required on the interface and optional on the runtime signature — pre-existing nit). { where: null } resolves unfiltered (the engine's null-withdrawal on the key it applies to). A string throws naming keys '0', '1', … — loud, odd prescription. The one residual worth stating: the arm judges NAMES, so a bare filter whose every key happens to be an envelope key reads as an envelope — { search: 'completed' } on an object with a field named search ran as full-text search (returned [t2]). That ambiguity is the engine's own, is inherent to ruling D (one shape at both layers), and reaches only a caller who ignores the migration AND names a field after an envelope key; recorded, not a defect of this PR. Follow-up, not blocking: a typeof query !== 'object' guard would close the primitive leg in one line.
  3. ACTION_ENGINE_FIND_ENVELOPE_PRESCRIPTION — a new runtime export; a plain Error with no ADR-0112 code, the same shape as its delete sibling, so the pins compare text. Right; the string carries no tracker id (③).

  4. Pins. (a) action-engine-facade-find-envelope.test.ts: four argument pins against a double plus five rows through the real engine. Re-ablated by me — the guard call replaced (blob 769ce0825a31711f): 5 of 29 red across the PR's file and my matrix — the PR's three refusal pins and my R3/R4, the null-valued row as expected undefined to be an instance of Error (it resolved); every pass-through and control pin stayed green. Restored with git checkout HEAD --, blob 769ce082 again, git status --porcelain empty. The pins discriminate; the null row has now been observed to fail. (b) action-body-identity.test.ts :129 / :146 on the envelope; the double records where; the predicate case asserts toEqual({ status: 'open' }) instead of toBeDefined(). Right.

  5. ADR-0087 D3 semantic entry action-engine-facade-find-query-envelope + regenerated registry. Semantic is correct (an authored TS body, unreachable by migrate meta). check-adr-0087-registration --base 24d622b94b --head HEAD[BREAKING+clause-②-narrowing] registered; spec check:generated on my built head tree: all 16 up to date (check:migration-registry, check:spec-changes, check:upgrade-guide, check:docs, check:api-surface-declarations green THERE — see ③ for the merge). Right; the acceptance text is now true (③).

  6. Callers — census re-derived, not reused. The facade is reached only through ActionHandlerContext.engine or a buildActionEngineFacade(...) result. Repo-wide grep at head: 3 files / 8 call sitesaction-engine-facade-find-envelope.test.ts ×4 (envelopes), action-body-identity.test.ts ×2 (envelopes), examples/app-todo/src/actions/task.handlers.ts :116 (envelope) and :146 ({}). Every other engine.find( hit (h.engine in the job tests, domains/share-links.ts, the org-scope integration tests, verify/harness.ts, the triggers) calls ObjectQL directly, not the facade. Prose: no bare-filter ctx.engine.find example survives in content/docs/**, skills/** or any README. The census is right now.

  7. content/docs/ui/actions.mdx — callout inverted: envelope examples, typed → compile error, untyped → runtime refusal, upgrade note. Right (wording judged in ③).

  8. Generated: the 8 api-surface-declarations/*.txt (only ui.txt semantic), the 2 skills/** indexes (pure regeneration, ③), scripts/engine-double-contract.pinned.json +1 row (check:engine-double-contract OK, 887 pinned). Right.

Nits carried, not wrong: the changeset's "what this opens" list omits search / searchFields / top while the member doc names search; the six-key refusal reuses the where prescription for a tenantId-class key, where "move it under where" is not the fix (the legal-key list in the same sentence is).

② Semver level

  • (a) Level. minor on @objectstack/spec and @objectstack/runtime. check-changeset-no-major green on my head tree (level axis N/A locally — no PR payload); CI Check Changeset success on this head. Clause-②: yes (narrowing) is the right arm: the declared accept set both narrows (bare filter refused) and widens (envelope keys admitted) — BREAKING, at least minor under the no-major rule. Consistent.
  • (b) Migration in the text that ships as CHANGELOG.md. FROM → TO table (three rows, {} unchanged), the one-line fix, tsc discovery for typed handlers, and the measured refusal text for untyped ones — that quoted text is exactly what the arm prints on a real engine, 12-key list included. The sentence the FAIL called false is gone; its replacement ("If your handler is NOT typed with it … the facade refuses the withdrawn shape at runtime instead, before the engine, with the same prescription") is true on both channels as measured. Present and true.
  • (c) ADR-0087. Exactly one marker; gate green. Consistent.
  • (d) Tombstone. No authorable spec key is retired, so retiredKey() / a guidance-map row has no slot; what that rule buys — the rejection itself carrying the prescription for anything an author can still write — is now delivered by the arm on the only channel the type does not reach, null-valued keys included. Owed, and present.
  • The six-key refusal (transaction, tenantId, tenantIds, timezone, bypassTenantAudit, preserveAudit) moves no level: they are ENGINE_DRIVER_PASSTHROUGH_KEYS in packages/objectql/src/engine.ts, never part of the PUBLISHED type the ruling names, and never deliverable through the pre-PR facade either. ① territory; adjudicated in ③.

③ Boundary flags

The four items of record 5746635882, item by item.

  1. Silent drop on the untyped channel — FIXED. Measured above on a real engine: { deleted_at: null } throws with the where prescription, no rows, engine never reached; the pin goes red under ablation.
  2. Census — FIXED. action-body-identity.test.ts :129 / :146 on the envelope, predicate asserted; my re-derived census finds no other caller.
  3. False sentences — FIXED, in four texts: the changeset ("never a runtime surprise" gone), the semantic entry's acceptance criteria (typed → tsc; untyped → refused at RUNTIME, "complete for them only once each such handler has actually been RUN"), the member doc (new "…and what refuses it for a caller the TYPE never reached" section), and actions.mdx (typed compile error / untyped runtime refusal). I read each at head; each is true for both channels as measured.
  4. context — NOT folded in, correctly. The FAIL routed it to its own card; the patch round left the override in place, documented it on the member ("Do not write one — it reads as authorization and is none") and kept the runtime pin. The patch does not make it worse: context is a legal envelope key, so the new refusal neither catches nor changes it. The card exists — spec: ActionEngineFacade.find accepts a caller-written context that type-checks and the runtime does not honour — declared-but-unenforced on an identity/tenant key (ADR-0049 class) #19237, open, filed 2026-09-20T03:11Z. Routing stands; nothing is ordered here.

Tier H — the two skills/** paths (highest-stakes check, verified independently on a tree where the generator runs). Head worktree, pnpm install exit 0: pnpm --filter @objectstack/spec gen:skill-refs exit 0 ("Generated 9 files"); git hash-object of both files equals the HEAD blobs (6601f4589d data, 9be8ae689a ui); git status --porcelain -- skills/ empty. Firing control: appended a junk line to skills/objectstack-ui/references/_index.md → status M, blob b65c58ae40; re-ran the generator → blob 9be8ae689a again, whole tree clean. The generator writes these paths and the committed bytes are its output. Instrument at origin/main (e233db9dbb; its scripts/pm/check-governed-merges.mjs is byte-identical to the head's copy) with --pr 19223 --root pointed at the head worktree: exit 0, "0 of 21 path(s) hit the register after 2 generated-artifact lift(s)", both PURE REGENERATION. No other path in the diff hits the register. Not Tier H, not Tier S; ordinary queue landing by the owning seat.

Six keys the engine honours and the arm refuses — adjudicated as a contract question. The ruling names "the engine's published query type by identity"; that is EngineQueryOptions in packages/spec, and the six are engine-internal driver pass-throughs declared only in packages/objectql. Refusing them agrees with the ruling's authority, not merely with a type. Does it break a caller? None exists (0 of 8 call sites), and none could: the pre-PR facade could not deliver any of them (a { tenantId } was wrapped into where.tenantId, a field filter); ActionHandlerContext exposes no transaction handle, so transaction is unreachable by construction; and tenantId / tenantIds / bypassTenantAudit are exactly what the facade's own stamped context decides — admitting them would be the #19237 hole with a wider mouth. Measured: { tenantId: 'x' } and { where: …, transaction: {} } both refused loudly. Right, fail-closed; ① (accept set), not ②.

Two gate-forced deviations. (a) sqlite :memory: instead of @objectstack/driver-memory: check-driver-memory-census OK on my head tree (2 ruled consumers, ledger live). The property under test — the engine's rejectUnknownEngineOptions exempting a null value — is driver-independent; under ablation the sqlite-backed pin failed exactly as the FAIL measured on the memory driver (resolved instead of throwing). Not weakened. (b) Tracker id out of the runtime string: check-doc-authoring OK (402 files, 44 skill files, 15796 strings clean); the id lives in the line comment above the constant and the string is customer-readable. Not weakened.

Ablation — re-done by me, above; the guard neutralised reds 3 PR pins + 2 of mine with the null-valued row resolving; tree restored byte-clean.

Pinned sibling / Console Pin Gate skipped. Skipped by ci.yml's console path filter (.objectui-sha, scripts/build-console.sh, three console check scripts, ci.yml) — none in this diff: a filter skip, not a pass. Probed ../objectui at .objectui-sha 53ded82bf7 with git grep at that commit: ActionEngineFacade 0, ActionHandlerContext 0, EngineQueryOptions 0, buildActionEngineFacade 0, ctx.engine 0, engine.find( 0 code hits (one prose hit in a comment, packages/fields/src/widgets/FilterConditionField.tsx:22); firing control @objectstack/spec 3452 hits; dark control ActionEngineFacadeZZZ rc 1. The sibling imports nothing this PR changes; no sibling fix and no pin bump owed.

os-regen routing. git check-attr merge asked per path for all 21 changed paths at head (.gitattributes rows not counted): 10 merge=os-regen — the 8 packages/spec/api-surface-declarations/*.txt and the 2 skills/**/references/_index.md; 11 unspecified.

⚠️ CI on this head is NOT green — a landing precondition for the owning seat, not a contract defect. Type Check · consumer gates and its required aggregate TypeScript Type Check fail on check:api-surface-declarations: ui.txt, ComponentPropsMap and ObjectTimelinePropsSchema "declaration text changed". Those two symbols are what origin/main e233db9dbb (#19219) moved AFTER this head was cut; CI ran on the merge of head into that base, where GitHub's driverless text merge of the os-regen ui.txt no longer matches the generator — ui.txt is the only PR path main has touched since the branch point. On the head tree alone check:generated reads 16/16 up to date. Reproduced by me on the driverless merge of this head into e233db9dbb (probe commit 478fb1a709, deps installed, spec built --force): check:api-surface-declarations exit 1 with the same two reshaped rows; gen:api-surface-declarations exit 0 rewrote exactly one file — ui.txt, two hunks, +2/−2, inside ComponentPropsMap and ObjectTimelinePropsSchema and nowhere else; the re-check exits 0. The red is base drift on an os-regen artifact, resolved by regeneration alone. Prescription: bash scripts/pm/os-regen-merge.sh in the branch worktree (merge origin/main, commit, then pnpm --filter @objectstack/spec build and gen:api-surface-declarations, commit, push) — ⛔ never a textual merge; open PR #19024 shares these 8 files, so the serial relay the seat recorded applies. A regeneration-only commit confined to os-regen paths does not reopen this record. Lint & Repo Gates was still in_progress at my last read (started 03:03Z) and is not read as a pass.

Implementer deviations (report 5747228829), each answered: (1) driver swap — above; gate green, pin not weakened. (2) tracker id moved — above; gate green. (3) two more texts corrected — verified, both true. (4) file surface +1 (action-body-identity.test.ts) — exactly what item 2 of the FAIL required; accepted. (5) six-key measured delta — adjudicated above: right, ①. (6) context not folded — right; #19237 carries it.

Still open from the FAIL record, none of it this PR's: ruling item 3's hotcrm half ("census at dispatch; relayed to the hotcrm seat") is still evidenced nowhere on the card thread and is unverifiable from this repo — re-escalated to the dispatching seat. Ruling item 4's #14175 CHANGELOG.md erratum stays undone, correctly (a released entry; a docs-only PR if the maintainer wants it). The check-spec-docblock-symbol-anchors shrink-only contradiction belongs to #16960 as reported.

Taken on the PR's word: nothing load-bearing. Not re-run by me: the repo-wide eslint sweep, the 121-family dispatch-gates sweep, examples/app-todo typecheck (CI's consumer job shows all five examples' tsc --noEmit Done before the declarations gate failed), check:dual-build-cjs-loads.

Implemented-by: claude/issue-15124-facade-query-envelope
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3

VERDICT: PASS


Generated by Claude Code

…ed tree

Baseline drift, not a code change. `origin/main`'s #19219 moved
`ComponentPropsMap` and `ObjectTimelinePropsSchema` after this branch forked,
and `api-surface-declarations/**` is a `merge=os-regen` path — so the merge
produced a `ui.txt` that was current for neither side, which is what turned
`Check @objectstack/spec declaration text` red.

Regenerated from a real build of the merged tree (34/34 declaration files
emitted; ⛔ no `OS_SKIP_DTS`), via `scripts/pm/os-regen-merge.sh`.

BOTH SIDES asserted present afterwards, by quoted-exact name against the INDEX
blob rather than the worktree, with a dark control reading 0 files:
this branch's `find(object: string, query: EngineQueryOptions)` and
`ACTION_ENGINE_FIND_ENVELOPE_PRESCRIPTION`; #19219's `ObjectTimelineProps`,
`ObjectTimelinePropsSchema` and `ComponentPropsMap`. Note the two `navigation`
keys that branch also added live in `authorable-surface/ui.json` and in NO
declaration file — a grep scoped to `api-surface-declarations/` reads 0 for
them out of range, not out of loss, so the assertion ran over the whole tree
and printed paths.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ee8c41133e13de338c2157de79f8c78b71f99d7d

Third review, new head. Held against ruling D on card #15124 (comment 5644710751) and against both prior records — 5746635882 (head c5f0855f44, FAIL) and 5747325899 (head 35aad65994, PASS with one red-CI landing precondition). The head moved for one stated reason, the os-regen sync; the live question is whether that sync changed, dropped or smuggled anything a contract review must judge. Every reading below is mine, taken on a detached worktree at this head with pnpm install --frozen-lockfile (exit 0), a REAL @objectstack/spec build (34/34 declaration files, no OS_SKIP_DTS) and the runtime dependency closure built (30 packages, exit 0); PM instruments were run from a second worktree at origin/main 9059a9472a, whose copies of check-governed-merges.mjs, check-clause2-carriers.mjs, check-adr-0087-registration.mjs and check-changeset-no-major.mjs are byte-identical to the head's. Where a reading is not mine, it says so.

① Derived judgments

What moved since the PASSed head. git diff --name-status 35aad65994 ee8c41133e touches 31 paths; decomposed: the merge commit 0d775cb30f (parents 35aad65994 and 847e5773a4) brings main's 8 commits over 31 paths, and the regeneration commit ee8c41133e changes exactly ONE path, packages/spec/api-surface-declarations/ui.txt. Of the PR's 21 paths, 20 are blob-identical between 35aad65994 and this head — every hand-written file included: action-params.zod.ts 356a7decf1, action-execution.ts 769ce08253, action-params.test.ts 402b40ce9c, action-engine-facade-find-envelope.test.ts a6be5b7e43, action-body-identity.test.ts 5c03cc400a, the semantic entry e3344f3b06, registry.ts 2d7384c35c, the changeset ef93af3ad4, actions.mdx 56545f994c, task.handlers.ts 3792158b35, engine-double-contract.pinned.json bde3fbc37b, the two skill indexes 6601f4589d / 9be8ae689a, and seven of the eight declaration files. Nothing executable moved; no hand-written file is judged fresh. The one changed path is an os-regen artefact, judged below and in ③.

Smuggling. The PR's three-dot contribution NOW (merge-base with origin/main = 847e5773a4) is the same 21-path set as at 35aad65994 (merge-base 24d622b94b): a diff of the two sorted --name-status lists is empty. No path appeared or disappeared. Per path, the PR's side is the same blob as before except ui.txt, whose PR-side patch against its base is the same patch as before plus a four-line enum-member reorder (desc inside the orderBy enum) — the declaration-emit ORDER churn class both prior records already named; the regenerated bytes are the authority (below).

Both sides survive, read at file grain, paths not counts. Main's 31 paths in the merged range 24d622b94b..847e5773a4: 30 carry main's exact blob at this head (git rev-parse 847e5773a4:path equals HEAD:path for each — among them packages/spec/authorable-surface/ui.json, api-surface/ui.json, declaration-map/ui.json, export-origins/ui.json, json-schema.manifest/ui.json, src/ui/component.zod.ts, src/shared/retired-key.ts, scripts/pm/measurement-claim-triage.mjs, the four docs/qa/platform-checklist/areas/*.json, the three content/docs/** pages); the 31st is the shared ui.txt. Whole-tree git grep -l at the head commit by quoted exact name — PR side: find(object: string, query: EngineQueryOptions) in packages/spec/api-surface-declarations/ui.txt and packages/spec/src/ui/action-params.zod.ts; ACTION_ENGINE_FIND_ENVELOPE_PRESCRIPTION in action-engine-facade-find-envelope.test.ts, action-execution.ts, ui.txt, action-params.zod.ts; action-engine-facade-find-query-envelope in the changeset, ui.txt, the semantic entry, registry.ts, action-params.zod.ts; assertActionEngineFindEnvelope in action-execution.ts. Main side (#19219): ObjectTimelinePropsSchema in content/docs/references/ui/component.mdx, ui.txt (declared at :12530; the ComponentPropsMap 'object-timeline' row at :5373), api-surface/ui.json, declaration-map/ui.json, export-origins/ui.json, src/ui/component.zod.ts; ui/ObjectKanbanProps:navigation and ui/ObjectCalendarProps:navigation in packages/spec/authorable-surface/ui.json :804 / :697 ONLY — a grep scoped to api-surface-declarations/ reads 0 for both, and that is a RANGE reading (those keys live in no declaration file), not a loss. Dark control ObjectNoSuchPropsZZZ: rc 1, 0 files.

The regenerated artefacts, re-derived with firing controls. gen:api-surface-declarations on the built head tree: all 17 files hash-equal to HEAD (ui.txt 06a82c7d8b); git status -- packages/spec/api-surface-declarations empty. Firing control: junk line appended to ui.txt → status M, blob 520862d268; regenerated → 06a82c7d8b again, status empty. gen:skill-refs: both skills/** indexes hash-equal to HEAD; firing control on skills/objectstack-ui/references/_index.md M, blob d37f4f9def; regenerated → 9be8ae689a, status empty. check:generated: all 16 artefacts up to date; whole-tree git status --porcelain empty afterwards.

The red that caused this round. pnpm --filter @objectstack/spec check:api-surface-declarations on the built head tree: declaration text unchanged (17 entry points, 5362 declarations), exit 0 — run by me, not read from CI. The sync's ui.txt delta (merge commit → head, +286/−2) is main's own 24d622b94b → 847e5773a4 patch on ui.txt line for line except a two-line reorder of asc inside the orderBy enum: the merged tree's regeneration, carrying the PR's signature at :218 and main's object-timeline rows together.

os-regen routing. git check-attr merge asked for each of the 21 paths at head: 10 merge: os-regenpackages/spec/api-surface-declarations/{automation,data,integration,kernel,root,security,system,ui}.txt, skills/objectstack-data/references/_index.md, skills/objectstack-ui/references/_index.md; 11 unspecified — the changeset, actions.mdx, task.handlers.ts, action-body-identity.test.ts, action-engine-facade-find-envelope.test.ts, action-execution.ts, the semantic entry, registry.ts, action-params.test.ts, action-params.zod.ts, engine-double-contract.pinned.json.

The contract itself on this head — unchanged by identity from 35aad65994, and re-exercised on the MERGED tree rather than assumed. Spec pins (action-params.test.ts, src/migrations, src/data/data-engine): 4 files / 274 tests green. Runtime pins (action-engine-facade-find-envelope.test.ts, action-body-identity.test.ts, action-engine-facade-delete-partial-failure.test.ts, action-engine-facade-nullish-id.test.ts): 4 files / 32 tests green. The envelope key set the arm reads off EngineQueryOptionsSchema.shape on the merged tree (main moved retired-key.ts in the range): 12 keys — context, cursor, distinct, expand, fields, limit, offset, orderBy, search, searchFields, top, where — exactly the list the changeset quotes. My own matrix through the real facade over a real ObjectQL + sqlite :memory:, seeded t1/t2/t3: { where: { status: 'completed' } }[t2]; {}[t1, t2, t3]; { status: 'completed' } → throws naming 'status' with the where prescription; { deleted_at: null } → throws naming 'deleted_at', no rows; positive control { where: { deleted_at: null } }[t1, t2] (the null filter IS honoured through the envelope); { tenantId: 'x' } → refused naming 'tenantId'. Caller census re-derived at head: the facade is reached only through buildActionEngineFacade( (6 files: domains/actions.ts, action-execution.ts, the four runtime tests) and examples/app-todo/src/actions/task.handlers.ts :116 (envelope) / :146 ({}); the only non-envelope literals in the tree are the refusal pins themselves. Prose: no bare-filter ctx.engine.find example in content/docs/**, skills/** or any README, and main's merged range added no engine.find( prose at all (grep over the range diff: 0 lines).

② Semver level

  • (a) Level. minor on @objectstack/spec and @objectstack/runtime; Clause-②: yes (narrowing) — BREAKING, at least minor under the no-major rule. check-changeset-no-major --base 847e5773a4: no major introduced (level axis N/A locally, by construction); check-empty-changeset --base 847e5773a4: green, no base changeset modified or deleted; CI Check Changeset success on this head. check-clause2-carriers --pair 19223 at origin/main: exit 0 — card claim Clause-②: yes, PR body Clause-②: yes. Consistent.
  • (b) Migration in the text that ships as CHANGELOG. The changeset is blob-identical to the PASSed head (ef93af3ad4): FROM → TO table (three rows, {} unchanged), the one-line fix, tsc --noEmit discovery for handlers typed with the published context, and the quoted runtime refusal for the untyped channel — the same sentence the arm printed on my real-engine run on this merged tree, 12-key list included. Present and true on this head.
  • (c) ADR-0087. Exactly one marker in the changeset (adr-0087: registered action-engine-facade-find-query-envelope). check-adr-0087-registration --base 847e5773a4 --head HEAD, and again with --base 9059a9472a (current origin/main): both green, [BREAKING+clause-②-narrowing] registered. The entry is in registry.ts at head; check:generated 16 of 16. Consistent.
  • (d) Tombstone. No authorable spec key is retired, so retiredKey() has no slot; the rejection-carries-the-prescription half is delivered by the runtime arm on the only channel the type does not reach, null-valued keys included — measured above on this head. Owed and present.

Nothing in the sync moved the level, the migration, the marker or the tombstone-equivalent; ② stands as adjudicated in 5747325899.

③ Boundary flags

Tier H — the two skills/** paths (the highest-stakes check, verified first and independently, on a tree where the generator runs). Head worktree, dependencies installed: pnpm --filter @objectstack/spec gen:skill-refs exit 0 ("Generated 9 files"); git hash-object of skills/objectstack-data/references/_index.md = 6601f4589d and of skills/objectstack-ui/references/_index.md = 9be8ae689a, both equal to the HEAD blobs; git status --porcelain -- skills/ empty. Firing control: junk line appended to the ui index → M, blob d37f4f9def; regenerated → 9be8ae689a, whole tree clean. The generator writes these paths and the committed bytes are its output. Instrument at origin/main 9059a9472a with --pr 19223 --root pointed at the head worktree (so it recomputed on THIS tree, not main's): "0 of 21 path(s) hit the register after 2 generated-artifact lift(s)", both PURE REGENERATION, 2072 changed lines under the human-merge threshold. No other path in the diff hits the register (nothing under docs/adr/**, .claude/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md). Not Tier H, not Tier S: ordinary queue landing by the owning seat on this record. check-skills-token-ratchet on the head tree: authored 129392 / 145656, generator-owned measured not ratcheted — exactly the PR body's figures.

The four items of record 5746635882, item by item, on THIS head — a sync can undo a fix as easily as a hand edit.

  1. Silent drop on the untyped channel — still fixed. action-execution.ts is blob 769ce08253 at head, the same blob 5747325899 ablated; the runtime pins are green on the merged tree; my own null-valued row throws with the where prescription and returns no rows.
  2. Census — still fixed. action-body-identity.test.ts blob 5c03cc400a unchanged; its two calls are envelopes and the predicate case asserts the predicate; the census re-derived at head finds no bare-filter caller outside the refusal pins, and main's 8 commits added none.
  3. False sentences — still fixed. The four corrected texts are blob-identical to the PASSed head: changeset ef93af3ad4, semantic entry e3344f3b06 (acceptance criteria state the runtime refusal and "complete for them only once each such handler has actually been RUN"), action-params.zod.ts 356a7decf1 (member doc), content/docs/ui/actions.mdx 56545f994c. Each remains true on this head as measured.
  4. context — still routed out, not folded in. Card spec: ActionEngineFacade.find accepts a caller-written context that type-checks and the runtime does not honour — declared-but-unenforced on an identity/tenant key (ADR-0049 class) #19237 is open (read by me); this head neither folds it in nor changes it (the context-order pin is one of the 32 green). Not re-litigated here.

What still stands from each prior record. From 5746635882: the ①-level judgments of the signature, the arm, the pins, the ADR-0087 entry and the docs; the Tier H lift; the sibling reading (../objectui at pin 53ded82bf7 imports nothing this PR changes — not re-probed by me, the pin is unchanged by this diff); the routing of context to its own card; ruling item 4's #14175 CHANGELOG.md erratum correctly NOT done in a code PR; the #16960 carrier for the check-spec-docblock-symbol-anchors shrink-only contradiction; and ruling item 3's hotcrm half, still evidenced nowhere on the card thread and unverifiable from this repo — still the dispatching seat's. From 5747325899: the PASS verdict on the same evidence — every source blob it judged is the blob at this head; the ② adjudication (a)–(d); the six-key refusal as ① fail-closed territory; the two gate-forced deviations (sqlite :memory:, tracker id out of the runtime string); the primitive-leg follow-up (typeof query !== 'object', non-blocking); and its one landing precondition — the red check:api-surface-declarations on the merge — which is what this round discharged.

Is this head WORSE than the head that passed, in any respect? No. Every hand-written and generated PR file is byte-identical except ui.txt, which is now current for the MERGED tree where before it was current only for the branch; the gate that was red on the merge is green when run by me; the pins pass on a tree that includes main's 8 commits; the contribution set is unchanged; the sync is a merge, not a rebase or force-push (parents verified), and no .gitattributes row moved. One note, not a defect: the merge commit 0d775cb30f records the BRANCH's ui.txt blob (3d268e9bbd, equal to 35aad65994's), not main's — consistent with os-regen-merge.sh step 2 touching the worktree only and the driver deferring — and the regeneration commit is what carried main's 286 lines back in. The implementer's report sentence "took main's side of ui.txt" describes the worktree, not the committed merge; read the regen commit, not the merge commit, as the record of both sides.

Landing state, for the owning seat (not a contract defect). origin/main has moved ONE commit past the merged base (9059a9472a, #19234), and it edits packages/spec/src/migrations/registry.ts, which this PR also edits and which is generated but NOT os-regen-routed. Probed from a driverless bare clone: merge-tree --write-tree of 9059a9472a × this head is conflict-free, and in a worktree at that merge result build-migration-registry.ts --self-test --check reports the textually merged registry.ts current (231 semantic entries; both tool-requires-confirmation-retired and action-engine-facade-find-query-envelope present; tree untouched by the check). So the queue's rebuilt generation should not red on this artefact and no further sync is owed today. Open PR #19024 still shares the 8 declaration files — the serial relay recorded earlier applies; resolution by regeneration, never text. CI on this head at my last read: TypeScript Type Check, Build Core, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard, Check Changeset and all six Test Core shards success; Lint & Repo Gates still in_progress and the Test Core aggregate queued — not read as a pass. Console Pin Gate skipped by its path filter, as before.

Taken on the PR's word: nothing load-bearing. Not re-run by me on this head: the repo-wide eslint sweep, the 121-family dispatch-gates sweep, examples/app-todo typecheck and the spec/runtime typecheck scripts (CI's four Type Check jobs are success on this head), check:dual-build-cjs-loads, and the ../objectui sibling probe (pin unchanged since 5747325899 measured it).

Implemented-by: claude/issue-15124-facade-query-envelope
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3

VERDICT: PASS


Generated by Claude Code

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

达档复核第三轮 PASS —— 但本席主动压住不同步、不入队,理由是接力次序,⛔ 不是这张 PR 有问题

domain:spec seat 2 执行席(session_01JbZnqu8bt6YqfJsr9vaFb3),2026-09-20T04:23Z。

Record of record: 本 PR 评论 5747584567,Head-sha: ee8c41133e13de338c2157de79f8c78b71f99d7d,VERDICT: PASS。⛔ 逐字采纳。档位证据(读自子代理转录,常量现场 import):transcript lines=246 / assistant 116 / 116 条全部 claude-fable-5-1 ✅ AT TIER

三份记录的链条:5746635882(head c5f0855f44,FAIL,四项)→ 5747325899(head 35aad65994,PASS,带 CI 红的落地前置)→ 5747584567(head ee8c41133e,PASS)。

本轮复核自己跑到的两件,值得记账

  • Tier H 独立复验:它在装了依赖的树上跑生成器并用 --root 指向 head 工作树,读到 0 of 21 path(s) hit the register after 2 generated-artifact lift(s),两条 skills/** 均为 PURE REGENERATION;并配了亮对照(污染其中一个 → M → 重生成 → 回到 HEAD blob)。
  • 一处报告口径更正:施工报告称「取了 main 那一侧的 ui.txt」,复核实测合并提交记录的是分支侧的 blob(3d268e9bbd),是随后的重生成提交把 main 的 286 行带回来的。⇒ 那句话描述的是工作区,不是已提交的合并。判为报告口径注记,⛔ 非缺陷。

⛔ 为什么现在不动它

本席在 2026-09-20T04:23Z 重测了漂移:

merge-base = 847e5773a4        origin/main = 1b82c519df
DRIFTED  packages/spec/api-surface-declarations/{data,root,system,ui}.txt   (4 条)
[firing control] 同区间主干动过的文件数: 25      [dark control] 0

无驱动探针 git merge-tree --write-tree origin/main <head> 同样点名 ui.txt:「not text-merged — it is generated. Regenerate from the merged tree」。

⇒ 这张 PR 需要一次同步。但 PR #19235 处在完全相同的状态(复核已 PASS、同样被 ui.txt 漂移挡住),而两张共用 ui.txt现在同时同步两张,必然有一张的重生成在另一张落地时立刻作废,连带作废它刚做完的那次达档复核。

⇒ 本席按 「多个已实现 PR 全碰生成物 ⇒ 串行接力一次只放行一个」 做次序选择:先同步并放行 #19235(7 个文件,面小),它落地后再同步本 PR。这不是对本 PR 的降级 —— 它是 BREAKING narrowing、复核三轮全过,只是接力里排第二。

⚠️ 代价写明:本 PR 的 head 届时会再动一次 ⇒ 按 「head 后移或无结论才重挂」 再欠一次达档复核(将是第四轮)。needs:contract-review 因此保持挂着,⛔ 本席不提前剥标。

⭐ 这一整个循环已立为决策卡 #19244(needs-user-decision)。本轮它已命中 4 次:#19226 入队后被踢 · 本 PR 的 CI 红 · #19235 arm 前漂移 · 本 PR arm 前漂移。⭐ 后两次是在入队之前测出来的 —— 那是「在 arm 的同一动作里重测漂移」这条做法换来的,代价从「被踢一轮」降到「多一次同步」。本席已把这个计数补到 #19244 上。

一件复核两轮都答不了、归本席升级的

ruling item 3 的 hotcrm 普查/转达在本卡线程上没有任何证据,且从本仓不可验证。两轮复核各自点名一次。⇒ 本席据实升级:它既不在本 PR 的范围内,也无法由本仓的任何读数关闭。⛔ 不以「没找到」当作「不存在」。


Generated by Claude Code

…ged tree

Baseline drift, not a code change — the fourth sync lap on this branch. Main's
#19226 and #19235 moved `packages/spec/api-surface-declarations/{data,root,
system,ui}.txt`, and that directory is a `merge=os-regen` path, so the merge
produced four files current for neither side.

Regenerated from a real build of the merged tree (34/34 declaration files
emitted; ⛔ no `OS_SKIP_DTS`), via `scripts/pm/os-regen-merge.sh`, with
`MERGE_HEAD` confirmed absent first — the build opens with `gen:schema`, and
running that in MERGE state is the anchor-rollback trap.

⚠️ The `MM` grade was live here and was read on purpose. After regenerating,
the index held main's side (803/535) while the worktree held the regeneration
(323/4); a bare `git commit` would have landed the index. `git add -A` first,
then `git diff --cached` re-read as the 323/4 it should be, and every one of the
four index blobs hash-matches its worktree file.

BOTH SIDES asserted by quoted-exact name over the WHOLE TREE with paths printed,
then again against the index blobs, with a dark control reading 0 files:
this branch's facade signature and prescription constant; #19226's
`DEFAULT_VIEW_ROW_LIMIT`, `KanbanConfigParsed` and the three
`ui/{Gallery,Kanban,Timeline}Config:limit` keys; #19235's
`RecordRelatedListProps.columns[number]` and `z.array(ListColumnSchema)`; and
#19219's `ObjectTimelinePropsSchema` carried forward. Note the three `limit`
keys live ONLY in `authorable-surface/ui.json` and `authorable-defaults/ui.json`
and the related-list row ONLY in `content/docs/references/ui/component.mdx` —
a grep scoped to the declaration files reads 0 for them out of range, not loss.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1e391fe8b516d0721847d9cf7daa6d720c873e8f

Fourth review, new head. Held against ruling D on card #15124 (comment 5644710751) and against all three prior records — 5746635882 (head c5f0855f44, FAIL), 5747325899 (head 35aad65994, PASS with a red-CI landing precondition) and 5747584567 (head ee8c41133e, PASS). The head moved for one stated reason, a second os-regen sync after sibling PRs landed on main; the live question is whether that sync changed, dropped or smuggled anything a contract review must judge. Every reading below is mine, taken on a detached worktree at this head with pnpm install --frozen-lockfile (exit 0), a REAL @objectstack/spec build (34/34 declaration files emitted, no OS_SKIP_DTS, 3m01s under os-verify-lock) and the runtime dependency closure built (exit 0); PM instruments were run from a second worktree at origin/main cb005e06ae, which is also this head's merge base, and whose scripts/pm/ tree is byte-identical to the head's (git diff empty; check-governed-merges.mjs blob 6e1112bcd5 in both). Where a reading is not mine, it says so.

① Derived judgments

What moved since the PASSed head. git diff --name-status ee8c41133e 1e391fe8b5 touches 35 paths (from 35aad65994: 60); decomposed: the merge commit b35c02554d (parents ee8c41133e and cb005e06ae) brings main's FOUR commits — #19234, #19226, #19235, #19251, not two — over 35 paths, and the regeneration commit 1e391fe8b5 changes exactly four paths, packages/spec/api-surface-declarations/{data,root,system,ui}.txt (+323/−4). Of the PR's 21 paths, 16 are blob-identical across 35aad65994, ee8c41133e and this head — every hand-written file included: action-params.zod.ts 356a7decf1, action-execution.ts 769ce08253, action-params.test.ts 402b40ce9c, action-engine-facade-find-envelope.test.ts a6be5b7e43, action-body-identity.test.ts 5c03cc400a, the semantic entry e3344f3b06, the changeset ef93af3ad4, actions.mdx 56545f994c, task.handlers.ts 3792158b35, engine-double-contract.pinned.json bde3fbc37b, the two skill indexes 6601f4589d / 9be8ae689a, and automation / integration / kernel / security.txt. Five changed ee8c41133e → head: the four os-regen declaration files (regenerated; judged below and in ③) and packages/spec/src/migrations/registry.ts (2d7384c35ce5e14ba76f) — generated but NOT os-regen-routed, so git text-merged it: main's #19234 rewording of the tool-requires-confirmation-retired entry plus this PR's action-engine-facade-find-query-envelope entry. Its head blob equals the merge commit's (the regen commit did not touch it), equals the driverless bare-clone merge-tree result, and check:migration-registry on the built head reads registry.ts is current (231 semantic, 195 retired-key, 181 retired-def) — the text merge IS what the generator writes. The PR-side patch on it against main is the one 43-line entry object and nothing else (read by me). Nothing executable moved; no hand-written file is judged fresh.

Evil-merge check. A driverless bare clone's merge-tree --write-tree ee8c41133e cb005e06ae differs from the committed merge tree b35c02554d on exactly the four os-regen paths and nothing else — the committed merge carries the BRANCH-side blob for each (the driver's deferral, as in lap 3), and the regeneration commit is what carried main's rows in. No hand edit rode the merge commit; .gitattributes is identical to main's.

Smuggling. The PR's three-dot contribution NOW (merge-base cb005e06ae) is the same 21-path set as at ee8c41133e (merge-base 847e5773a4): a diff of the two sorted --name-status lists is empty. No path appeared or disappeared. Per path, the PR's side is the same blob as before except the four regenerated files, whose PR-side patch against MAIN's current blob I characterised rather than eyeballed: data.txt 115/115, root.txt 4/4, system.txt 294/294, and security / automation / integration / kernel.txt — every line's token multiset is preserved (a normalisation that sorts the tokens within each line reads the two blobs equal), i.e. union-member, Exclude-key and enum-member ORDER churn within lines plus block reorders, the declaration-emit class all three prior records named; ui.txt 118/67 — with doc-comment lines excluded on both sides, the entire non-comment delta is the ONE signature line find(object: string, query: EngineQueryOptions) replacing the FilterCondition one, plus drawerSide / tabPosition union-order churn; the comment delta is the member doc. The sole semantic line in 12 MiB of regenerated declarations is still the ruled signature.

Both sides survive, at file grain, paths not counts. Main's 35 paths in the merged range 847e5773a4..cb005e06ae: 30 carry main's exact blob at this head (git rev-parse cb005e06ae:path equals HEAD:path — among them authorable-surface/ui.json, authorable-defaults/ui.json, api-surface/ui.json, export-origins/ui.json, spec-changes.json, src/ui/view.zod.ts, src/ui/component.zod.ts, src/data/field.zod.ts, src/ai/tool.zod.ts, the four changesets, the four content/docs/** pages and the knowledge-ragflow files); the five that differ are the four regenerated declarations and the text-merged registry.ts above. Whole-tree git grep -l -F at the head commit by quoted exact name — PR side: find(object: string, query: EngineQueryOptions) in api-surface-declarations/ui.txt + src/ui/action-params.zod.ts; ACTION_ENGINE_FIND_ENVELOPE_PRESCRIPTION in 4 files; action-engine-facade-find-query-envelope in 5 (changeset, ui.txt, entry, registry.ts, action-params.zod.ts); assertActionEngineFindEnvelope in action-execution.ts. #19226: DEFAULT_VIEW_ROW_LIMIT in 6 files (ui.txt, api-surface/ui.json, export-origins/ui.json, view.zod.ts, view.test.ts, its changeset); KanbanConfigParsed in 6; ui/GalleryConfig:limit, ui/KanbanConfig:limit, ui/TimelineConfig:limit in authorable-defaults/ui.json + authorable-surface/ui.json ONLY. #19235: RecordRelatedListProps.columns[number] in content/docs/references/ui/component.mdx ONLY; z.array(ListColumnSchema) in component.zod.ts, page.zod.ts, view.zod.ts + its changeset. #19234: tool-requires-confirmation-retired in 7 files incl. registry.ts and spec-changes.json. #19251: adapterConfig.datasetId in 4 knowledge-ragflow files. #19219 carried forward: ObjectTimelinePropsSchema in 6 files; ui/ObjectKanbanProps:navigation / ui/ObjectCalendarProps:navigation in authorable-surface/ui.json ONLY. ⚠️ Range, stated so nobody re-reads it as loss: the three Config:limit keys, the two navigation keys and the related-list row live in NO declaration file, so a grep scoped to api-surface-declarations/ reads 0 for them out of RANGE. Dark control ObjectNoSuchPropsZZZ: rc 1, 0 files.

The regenerated artefacts, re-derived with firing controls, and the MM trap read from the COMMITTED tree. gen:api-surface-declarations on the built head tree: 17 files, 0 hash mismatches against HEAD; the four drifted files explicitly — data.txt 045d3b05f6, root.txt 9fb0bf0687, system.txt a3618198c6, ui.txt e48b030fe5 — each equal to its HEAD blob, and each equal to NEITHER main's side (7a7306c331 / 7c52259156 / 9d5cf3f899 / 2cfe91bb48) NOR the branch's (37b5637bec / 26a7f085f5 / fdc7017a4d / 06a82c7d8b). What landed is the regeneration, not the staged main-side bytes; the implementer's MM reading is confirmed from the commit, not the report. Firing control: junk lines appended to ui.txt and data.txt → status M on both (blobs d6a450496c / 048d7102ed); regenerated → e48b030fe5 / 045d3b05f6 again, status empty. gen:skill-refs: both skills/** indexes hash-equal to HEAD; firing control on skills/objectstack-ui/references/_index.md M, blob 3bcfba8fe9; regenerated → 9be8ae689a, status empty. check:generated: all 16 artefacts up to date; whole-tree git status --porcelain empty afterwards.

The red that caused this round. pnpm --filter @objectstack/spec check:api-surface-declarations on the built head tree: declaration text unchanged ✓ (17 entry points, 5364 declarations), exit 0 — run by me, not read from CI.

os-regen routing. git check-attr merge asked for each of the 21 paths at head: 10 merge: os-regenpackages/spec/api-surface-declarations/{automation,data,integration,kernel,root,security,system,ui}.txt, skills/objectstack-data/references/_index.md, skills/objectstack-ui/references/_index.md; 11 unspecified — the changeset, actions.mdx, task.handlers.ts, action-body-identity.test.ts, action-engine-facade-find-envelope.test.ts, action-execution.ts, the semantic entry, registry.ts, action-params.test.ts, action-params.zod.ts, engine-double-contract.pinned.json.

The contract itself on this head — unchanged by identity from the two PASSed heads, and re-exercised on the MERGED tree rather than assumed. Spec pins (action-params.test.ts, migrations.test.ts, spec-changes-surface-scope.test.ts, data-engine.test.ts): 4 files / 274 tests green; check-test-typecheck --package packages/spec: OK, 54 files / 259 errors / 144 pinned signatures held, no action-params row, so the 14 @ts-expect-error refusal directives are live. Runtime pins (action-engine-facade-find-envelope.test.ts, action-body-identity.test.ts, action-engine-facade-delete-partial-failure.test.ts, action-engine-facade-nullish-id.test.ts): 4 files / 32 tests green. The envelope key set the arm reads off EngineQueryOptionsSchema.shape on this tree (main's range touches neither data-engine.zod.ts nor retired-key.ts): 12 keys — context, cursor, distinct, expand, fields, limit, offset, orderBy, search, searchFields, top, where — exactly the list the changeset quotes. My own matrix through the real facade over a real ObjectQL + sqlite :memory:, seeded t1 / t2 / t3 (t3 the only non-null deleted_at): { where: { status: 'completed' } }[t2]; {}[t1, t2, t3]; { status: 'completed' } → throws naming 'status' with the where prescription; { deleted_at: null } → throws naming 'deleted_at', no rows; positive control { where: { deleted_at: null } }[t1, t2]; { tenantId: 'x' } → refused naming 'tenantId'; envelope plus a caller context[t2] (overridden, resolves); firing control — the ENGINE called directly with { deleted_at: null }[t1, t2, t3], the silent drop the arm exists to stop, still real one layer down. Caller census unchanged from 5747584567: main's 4 commits add no engine.find(, ctx.engine or buildActionEngineFacade line (grep over the range diff: 0).

② Semver level

  • (a) Level. minor on @objectstack/spec and @objectstack/runtime; Clause-②: yes (narrowing) — BREAKING, at least minor under the no-major rule. check-changeset-no-major --base cb005e06ae: no major introduced (level axis N/A locally, by construction); check-empty-changeset --base cb005e06ae: green, no base changeset modified or deleted; PM_SWEEP_REPO=objectstack-ai/objectstack check-clause2-carriers --pair 19223 at origin/main: exit 0 — card claim Clause-②: yes, PR body Clause-②: yes, head 1e391fe8b5; CI Check Changeset success on this head. Consistent.
  • (b) Migration in the text that ships as CHANGELOG. The changeset is blob-identical to both PASSed heads (ef93af3ad4): FROM → TO table (three rows, {} unchanged), the one-line fix, tsc --noEmit discovery for handlers typed with the published context, and the quoted runtime refusal for the untyped channel — the same sentence and 12-key list the arm printed on my real-engine run on THIS merged tree. Present and true on this head.
  • (c) ADR-0087. Exactly one marker in the changeset (adr-0087: registered action-engine-facade-find-query-envelope). check-adr-0087-registration --base cb005e06ae --head HEAD: green, [BREAKING+clause-②-narrowing] registered action-engine-facade-find-query-envelope (new here). The entry is in registry.ts at head; check:migration-registry current. Consistent.
  • (d) Tombstone. No authorable spec key is retired, so retiredKey() has no slot; the rejection-carries-the-prescription half is delivered by the runtime arm on the only channel the type does not reach, null-valued keys included — measured above on this head. Owed and present.

Nothing in the sync moved the level, the migration, the marker or the tombstone-equivalent; ② stands as adjudicated in 5747325899 and re-affirmed in 5747584567.

③ Boundary flags

Tier H — the two skills/** paths (the highest-stakes check, verified first and independently, on a tree where the generator runs). Head worktree, dependencies installed, real build: pnpm --filter @objectstack/spec gen:skill-refs exit 0 ("Generated 9 files"); git hash-object of skills/objectstack-data/references/_index.md = 6601f4589d and of skills/objectstack-ui/references/_index.md = 9be8ae689a, both equal to the HEAD blobs; git status --porcelain -- skills/ empty. Firing control: junk line appended to the ui index → M, blob 3bcfba8fe9; regenerated → 9be8ae689a, whole tree clean. The generator writes these paths and the committed bytes are its output. Instrument at origin/main cb005e06ae with --pr 19223 --root pointed at the head worktree (so it recomputed on THIS tree, not main's): 0 of 21 path(s) hit the register after 2 generated-artifact lift(s), both PURE REGENERATION — byte-equal to gen:skill-refs recomputed on THIS tree, size 2072 changed lines under the 5000 human-merge threshold; head tree clean after its recompute. No other path in the diff hits the register — none of docs/adr/**, .claude/**, AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md is among the 21. Not Tier H, not Tier S: ordinary queue landing by the owning seat on this record. check-skills-token-ratchet on the head tree: authored 129392 / 145656, 10 generator-owned files measured not ratcheted — the PR body's figures.

The four items of record 5746635882, item by item, on THIS head — a sync can undo a fix as easily as a hand edit.

  1. Silent drop on the untyped channel — still fixed. action-execution.ts is blob 769ce08253 at head, the blob 5747325899 ablated; assertActionEngineFindEnvelope runs before ql.find and refuses any key outside EngineQueryOptionsSchema.shape whatever its value; my null-valued row throws with the where prescription and returns no rows; the counting pin (engine never reached) is among the 32 green.
  2. Census — still fixed. action-body-identity.test.ts blob 5c03cc400a: :129 / :146 on the envelope, :153 asserts toEqual({ status: 'open' }); main's 4 commits added no facade caller.
  3. False sentences — still fixed. never a runtime surprise and no runtime run needed: 0 hits at head across the changeset, the semantic entry (e3344f3b06), the member doc (356a7decf1) and actions.mdx (56545f994c); each states typed → compile error, untyped → runtime refusal, and each is true on this head as measured.
  4. context — still routed out, not folded in. Card spec: ActionEngineFacade.find accepts a caller-written context that type-checks and the runtime does not honour — declared-but-unenforced on an identity/tenant key (ADR-0049 class) #19237 is open (read by me); this head neither folds it in nor changes it; the context-order pin is one of the 32 green and my caller-context row confirms the override. ⛔ Not re-litigated here.

What still stands from each prior record. From 5746635882: the ①-level judgments of the signature, the arm, the pins, the ADR-0087 entry and the docs; the Tier H lift; the routing of context to its own card (now #19237); ruling item 4's #14175 CHANGELOG.md erratum correctly NOT done in a code PR; the #16960 carrier for the check-spec-docblock-symbol-anchors shrink-only contradiction; ruling item 3's hotcrm half, still evidenced nowhere on the card thread and unverifiable from this repo — still the dispatching seat's. From 5747325899: the PASS on the same evidence — every source blob it judged is the blob at this head; the ② adjudication (a)–(d); the six-key refusal as ① fail-closed territory (my tenantId row); the two gate-forced deviations (sqlite :memory:check:driver-memory-census OK on this tree; tracker id out of the runtime string — check:doc-authoring OK, 44 skill files / 15844 strings clean); the primitive-leg follow-up, non-blocking; its landing precondition, discharged by lap 3. From 5747584567: the sync-lap method itself (blob comparison of every path, driverless merge-tree, both-sides at file grain with paths, regeneration with firing controls) — repeated here on the new range with the same results; its note that the MERGE commit records the branch's blob and the REGEN commit is the record of both sides holds again, now for all four files; its landing note about registry.ts on 9059a9472a (#19234) is now realised — that commit is inside this merged range and the text merge is generator-current, as measured above.

Is this head WORSE than the head that passed, in any respect? No. Every hand-written and generated PR file is byte-identical except the four os-regen declarations, now current for the MERGED tree where before they were current only for lap 3's base, and registry.ts, which carries both sides and is generator-current; the gate that goes red on drift is green when run by me; the pins pass on a tree that includes main's four further commits; the contribution set is unchanged; the sync is a merge, not a rebase or force-push (parents verified: b35c02554d = ee8c41133e + cb005e06ae); no .gitattributes row moved; the merge commit carries no non-driver change. One correction to the round's framing, not a defect: the merged range holds FOUR main commits, not two — #19234 (registry.ts text-merged, judged above) and #19251 (knowledge-ragflow only, disjoint) alongside #19226 and #19235.

Pinned sibling / Console Pin Gate skipped. .objectui-sha is 53ded82bf7 at head, at origin/main and at ee8c41133e — unchanged. Re-probed by me with git grep in ../objectui at that commit: ActionEngineFacade, ActionHandlerContext, EngineQueryOptions, buildActionEngineFacade, ctx.engine — 0 files each (rc 1); engine.find( — 1 file, the prose comment in packages/fields/src/widgets/FilterConditionField.tsx; firing control @objectstack/spec 1160 files; dark control rc 1. The sibling imports nothing this PR changes; no sibling fix and no pin bump owed. The gate's skip is ci.yml's console path filter, not a pass.

Merge-sensitive repo gates, all run by me on the head tree, all exit 0: check:merge-driver, check:engine-double-contract (810 pinned rows), check:doc-authoring, check:driver-memory-census, check:cross-package-test-inputs, check:test-source-alias, check:generated, check:migration-registry, check:api-surface-declarations, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-skills-token-ratchet, check-clause2-carriers --pair 19223, check-governed-merges --pr 19223 --root.

Landing state, for the owning seat (not a contract defect). origin/main is cb005e06ae at my read — this head's merge base — so no further sync is owed today. PR #19024 (open, not draft, maintainer-landed by size) still deletes the whole api-surface-declarations/ family this PR regenerates; the serial relay recorded in 5746635882 applies, resolution by regeneration never text. Decision card #19244 (whether a pure-regeneration commit reopens a record) is the seat's, not this record's; this lap is one more data point for it, and the test this record applied — every non-os-regen path blob-identical to the recorded head, plus a driverless merge-tree of the merge commit — is the machine-readable predicate its option B asks for. CI on this head at my last read: TypeScript Type Check (the aggregate that was red at 35aad65994) success, Type Check · consumer gates / workspace / source gates / debt ledger success, Build Core, Build Docs, Dogfood Regression Gate, Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Check Changeset, Check PR Size and Test Core shards 2–6 success; Test Core (1/6) and Lint & Repo Gates still in_progress — not read as a pass.

Taken on the PR's word: nothing load-bearing. Not re-run by me on this head: the repo-wide eslint sweep, the 121-family dispatch-gates sweep, examples/app-todo typecheck and the spec/runtime tsc --noEmit scripts (CI's four Type Check jobs are success on this head), check:dual-build-cjs-loads. No ablation this lap and none owed: the guarded source is the blob the two prior records ablated, and the refusal pins ran green on the merged tree.

Implemented-by: claude/issue-15124-facade-query-envelope
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3

VERDICT: PASS


Generated by Claude Code

os-bill commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Provenance —— 第四轮达档复核已采纳,三条落地前检通过

domain:spec seat 2 执行席(session_01JbZnqu8bt6YqfJsr9vaFb3),2026-09-20T06:43Z,与剥标、ready、arm 同一动作。

Record of record: 本 PR 评论 5748188067,Head-sha: 1e391fe8b516d0721847d9cf7daa6d720c873e8f,VERDICT: PASS。⛔ 逐字采纳。

四份记录的链条(全部留档,⛔ 无一被改写):5746635882 head c5f0855f44 FAIL(四项)→ 5747325899 head 35aad65994 PASS(带 CI 红的落地前置)→ 5747584567 head ee8c41133e PASS → 5748188067 head 1e391fe8b5 PASS。中间三次 head 移动全部是生成物基线漂移引发的同步,⛔ 没有一次是代码返工。

档位证据(读自子代理自己的转录,常量现场 import):transcript lines=254 / assistant 102 / 102 条全部 claude-fable-5-1 ✅ AT TIER

三条落地前检:

# 读数
达档条款②复核 PASS 在案 ✅ 5748188067,形状七项全真,零尖括号
双载体已清,机读 ✅ PR 与卡 #15124 同笔剥标;--pair 19223(仪器取自 origin/main)→ exit 0,双肢皆 yes
全绿,⛔ 非必查子集 ✅ 35 个 check:33 绿、2 skip、0 红、0 未完

受管面:复核在装了依赖的树上跑生成器并以 --root 指向 head 工作树,读到 0 of 21 path(s) hit the register after 2 generated-artifact lift(s),两条 skills/** 均 PURE REGENERATION,并配了污染→重生成→回到 HEAD blob 的亮对照。2072 改动行 < 5000。

⭐ 本轮最硬的一条读数:那个静默丢弃被带对照地演示了

复核自己搭真引擎跑六行矩阵,并加了一条亮对照:

经 facade:  envelope → [t2] · {} → 3 行 · 裸 filter → 抛并点名 'status'
            {deleted_at:null} → 抛并点名 'deleted_at' · {where:{deleted_at:null}} → [t1,t2] · {tenantId} → 拒
[FIRING CONTROL] 直接 engine.find({deleted_at:null}) → 全部 3 行   ← 这个臂守着的那次静默丢弃

⇒ 「这个臂防住了什么」不再是论证,是并排的两个读数

MM 陷阱的结果,从已提交的树上验

复核重跑 gen:api-surface-declarations → 17/17 与 HEAD 哈希相等;那四个漂移文件在 head 上等于它自己的新重生成,且既不等于 main 的 blob、也不等于分支的 blob落下去的是重生成的结果,不是暂存区里 main 那一侧。另有 evil-merge 检查:无驱动 merge-tree 与已提交的合并树只在那 4 条 os-regen 路径上不同,其余一致。

⛔ 一处本席的框架被更正

本席先前说这一圈的漂移源是「两张已落地 PR」。复核实测:合并区间里是四个 main 提交 —— #19234#19226#19235#19251。⇒ 计数本身不变(漂移的仍是那 4 个声明文件),但「两张」是本席数错了。已照此更正。

入队前的 os-regen 漂移,在 arm 这一刻重测

merge-base = cb005e06ae      origin/main = 0f42d36ffc   ← 主干在复核读数之后又动了 1 个文件
本 PR 的 os-regen 路径中,自合并基以来在主干上变动过的:  0
[firing control] 同区间主干变动过的文件数: 1      [dark control] 0
git merge-tree --write-tree origin/main <head>  →  纯树 sha,零冲突路径

⇒ 亮对照在响(主干确实动了),而那 1 个文件不在本 PR 的生成物面上 ⇒ 那个 0 是读数。

串行接力:本席此刻队列里没有其它 PR(#19226#19235 已落地)。⚠️ #19024 仍与本 PR 共用 api-surface-declarations 家族,它是维护者落地的那张,⛔ 不归本席放行。

一件四轮复核都答不了、本席据实升级的

ruling item 3 的 hotcrm 普查/转达在本卡线程上没有任何证据,且从本仓不可验证。第二、三、四轮各自点名一次。⇒ 它既不在本 PR 范围内,也无法由本仓任何读数关闭。⛔ 不以「没找到」当作「不存在」。

落地路径:ready → auto-merge → 合并队列。⛔ 队列外不合并。


Generated by Claude Code

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 protocol:ui size/xl tests tooling

Projects

None yet

2 participants