Skip to content

fix(service-analytics): the draft preview groups a bucketed time dimension and reads a qualified filter member as the live faces do; ObjectQL serves a backed RELATION.id dimension - #28742

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-0o422x
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-0o422x

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#3 · os-project-manager · session_01FEkQ5kbUJTTWAxjV9nqr1P

Requested by Leehom · project thread

Fixes #28690
Fixes #28691
Fixes #28692

Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: none — no file under skills/** or content/docs describes how the draft preview groups a time dimension or reads a qualified filter member, or says the ObjectQL strategy refuses a RELATION.id dimension.

Card thread: session_01FEkQ5kbUJTTWAxjV9nqr1P.

维护者速读

What changes

Why

Measured on origin/main d62d9737 (real SQLite, every face), with a probe printing each face's answer per selection. Dataset over a deal object (region, amount, closed_at datetime, account lookup), rows amer/2026-03-01/100/a1, amer/2026-04-01/50/a2, emea/2026-04-02/30/a1; dimensions region, closed_at (date), zone (field region).

Before / after per selection
selection native ObjectQL preview before preview / ObjectQL after
td closed_at month, no dims 2026-03:100, 2026-04:80 same one row, total 180 preview = live
dim region + td closed_at month (amer,03,100) (amer,04,50) (emea,04,30) same grouped by region only preview = live
td OBJECT.closed_at month keyed OBJECT.closed_at, 03:100, 04:80 same one row, total 180 preview = live
td closed_at year + April window 2026:80 same total 80, no key preview = live
td in dims too; td window only; dateGranularity agree agree agreed already unchanged
filter OBJECT.region / CUBE.region / zone = emea emea 30 same [] preview = live
$or [OBJECT.region $in emea, amount 50] amer 50, emea 30 same amer 50 only preview = live
OBJECT.closed_at $gte 2026-04-01 amer 50, emea 30 same [] preview = live
backed dim account.id a1:130, a2:50 INVALID_QUERY (groupBy 'id' twice on the account object) refused (#28555) ObjectQL = native; preview still refuses
backed account.id + account.name served INVALID_QUERY (groupBy 'account' twice) refused ObjectQL = native
backed account + account.name (either order) served INVALID_QUERY refused ObjectQL = native

Tests

Red first, green, ablations

Acceptance notes

Verification

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 families derived on de8aeea7 (5 paths vs merge base 4bf48a8cc); each run with stdin from /dev/null, exit captured before any pipe, after a full turbo run build --filter=!@objectstack/docs --concurrency=2 (lock VERDICT command-exit 0). --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN, every one exit 0.
  • Hand gates: node scripts/check-platform-checklist.mjs --changed-from origin/main exit 0 (1 anchor judged, 0 lost); node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD exit 0.
  • Dogfood battery: not run. Clause-② is widening only, with no ! and no narrowing arm.
  • Head is 400154ac: de8aeea7 with the changeset's two placeholder spellings changed (RELATION.id), no code change. On 400154ac, the six changeset- and citation-reading gates (check-adr-0087-registration, check-changeset-no-major, check-empty-changeset with --base origin/main, check:pm-changeset-deadline-census, check:issue-citations, check-closing-keyword-parity) were run again, each exit 0.
  • CI families: read from this PR's check-runs.
The 62 derived families and their exits
  • node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
  • node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
  • node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
  • node scripts/check-closing-keyword-parity.mjs :: exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
  • node scripts/check-comment-mask-adoption.mjs :: exit 0
  • node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
  • node scripts/check-comment-mask-corpus.mjs :: exit 0
  • node scripts/check-dts-emitted.mjs --self-test :: exit 0
  • node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
  • node scripts/check-empty-changeset.mjs --self-test :: exit 0
  • node scripts/check-issue-citations.mjs :: exit 0
  • node scripts/check-plugin-teardown-shape.mjs :: exit 0
  • node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
  • node scripts/check-tenant-audit-census.mjs :: exit 0
  • node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
  • node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
  • node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
  • node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
  • node scripts/docs-audit/check-affected-docs.mjs :: exit 0
  • node scripts/docs-audit/check-drift-comment.mjs :: exit 0
  • node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
  • node scripts/release-pending-publish.mjs --self-test :: exit 0
  • pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
  • pnpm check:changeset-gate-self-tests :: exit 0
  • pnpm check:ci-filter-parity :: exit 0
  • pnpm check:closed-ceremony-text :: exit 0
  • pnpm check:cross-package-test-inputs :: exit 0
  • pnpm check:dispatcher-error-vocabulary :: exit 0
  • pnpm check:doc-authoring :: exit 0
  • pnpm check:driver-memory-census :: exit 0
  • pnpm check:dts-closure :: exit 0
  • pnpm check:dual-build-cjs-loads :: exit 0
  • pnpm check:engine-double-contract :: exit 0
  • pnpm check:error-status-conformance :: exit 0
  • pnpm check:gitlink-declared :: exit 0
  • pnpm check:issue-citations :: exit 0
  • pnpm check:keyed-text-bounds :: exit 0
  • pnpm check:lean-entry-closure :: exit 0
  • pnpm check:logger-receiver-detach :: exit 0
  • pnpm check:nul-bytes :: exit 0
  • pnpm check:objectql-double-limit :: exit 0
  • pnpm check:org-identifier :: exit 0
  • pnpm check:page-declaration-shape :: exit 0
  • pnpm check:platform-object-tenancy-census :: exit 0
  • pnpm check:pm-changeset-deadline-census :: exit 0
  • pnpm check:published-files :: exit 0
  • pnpm check:query-options-erasure :: exit 0
  • pnpm check:refd-timer-probe :: exit 0
  • pnpm check:registry-log-declared :: exit 0
  • pnpm check:rest-log-spy-declared :: exit 0
  • pnpm check:slot-lookup :: exit 0
  • pnpm check:sourcemap-no-sources-content :: exit 0
  • pnpm check:swallow-census-controls :: exit 0
  • pnpm check:system-context-census :: exit 0
  • pnpm check:test-source-alias :: exit 0
  • pnpm check:tier-file-adoption :: exit 0
  • pnpm check:type-check-coverage :: exit 0
  • pnpm check:type-check-debt :: exit 0
  • pnpm check:undeclared-dep-imports :: exit 0
  • pnpm check:watch-hint-literal :: exit 0
  • pnpm check:where-matcher :: exit 0

Human Minimum

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

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kbUJTTWAxjV9nqr1P

…eads qualified filter members; ObjectQL serves a backed relation.id

- Draft preview: a timeDimensions entry with a granularity is grouped by its
  bucket whether or not it is also in dimensions, from the projectedDimensions
  set the live strategies read, and the bucket is in `fields`.
- Draft preview: a where member spelled other than its column
  (OBJECT.region, CUBE.region, a dimension zone over region) is read from that
  column with its declared type, resolved by declaredMemberEntry over the
  shared collectFilterLeaves reader.
- ObjectQL strategy: the cross-object path names each groupBy column once
  (resolveFkAttr for `<relation>.id`, a shared FK column across two
  dimensions), so the engine's duplicate-column refusal no longer fires on a
  query the native strategy serves. The refusal itself is unchanged.

Real-SQLite three-face pins in preview-time-filter-fk-parity.test.ts.

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

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 10 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 db357f6da9f787ff6285c20c70bc02a1bc138d35 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 400154ac098acb0cc64c2db977d659d0d660153d
Local-runs: none

Read: cards #28690, #28691, #28692 (bodies, triage records 6558905217 / 6558915201 / 6558925351, claims 6559855795 / 6559874127 / 6559890848, os-dev-report 6560773894; no earlier contract record or ruling on any of the three), PR #28742 body and file list (5 files), the net diff git diff 4bf48a8cc 400154ac (merge base with main; main has not touched packages/services/service-analytics since), and the head's check-runs. The face is the declaration face: the tree carries no packages/spec file, no published schema and no governed path, and the PR body carries Clause-②: yes (widening).

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged:

  1. ObjectQL strategy, executeCrossObject groupBy (analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692). Accept-set widening. Right. Two shapes the strategy used to send the engine with one column named twice now name it once: account.id beside account.name (one FK column for two cross dimensions), and the FK column account beside account.name in either order (a base dimension reuses the FK column already grouped; fkGrouped holds only for a non-bucketed base dimension whose field is a cross dimension's fkField). Each used to fail with INVALID_QUERY / 400 and is now served with native's groups. rebucketCrossObject keys on baseDimFields and each cross dimension's fkField separately, so one grouped column serves both readers. Nothing that was served before is refused now.
  2. ObjectQL strategy, resolveFkAttr (analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692). Accept-set widening. Right. For attr === 'id' the call is now groupBy: ['id'] instead of ['id', 'id']. A backed RELATION.id used to be refused with INVALID_QUERY and is now served. The scope arms (the read-scope asserts and the RESTRICTED bucket for hidden ids) come before the groupBy and do not change.
  3. Engine duplicate-groupBy refusal. Unchanged. Right. packages/objectql is not in the diff, so the engine's guard still refuses every other caller's duplicate. The fix is in the strategy, where the card says the duplicate comes from.
  4. Draft preview grouping set (analytics: the dataset draft preview drops a bucketed timeDimensions entry that is not also in dimensions, so it answers one total instead of the time groups #28690). The answer changes; the accept set does not. Right. evaluateAnalyticsQueryOverRows now groups by projectedDimensions(query) (order-key-door.ts), the set the ObjectQL projection and buildFieldMeta already read. That set is dimensions, then each bucketed timeDimensions entry not already among them. fields reads the same set, so the bucket column carries the cube dimension's type. Queries the preview already served now get the live faces' rows and columns. A window-only time dimension still projects nothing. No refusal is added or removed.
  5. Draft preview where member resolution (analytics: the dataset draft preview answers [] for a runtimeFilter member spelled OBJECT.region that both live strategies resolve to the declared dimension #28691). The answer changes; the accept set does not. Right. The card allowed resolve or refuse. Resolve is the choice that keeps the preview no narrower than the live faces, which the PR measures as serving every spelling. previewWhereColumns reads leaves with the strategies' reader (normalizeAnalyticsFilterTree + collectFilterLeaves) and resolves each with declaredMemberEntry(cube, member, 'any'), the lookup assertWhereFields uses. It lists only members whose resolved column differs and is a bare identifier. The where tree is not rewritten: the match reads a listed member from its column (whereRow). The original row is what gets filtered on, so the time window and the grouping still read columns. The typed reader (whereType) now feeds the where lowering's isDatetimeColumn and the storage-form comparison with the column's declaration. windowLowering keeps the window on declaredType exactly as before. Neither arm of the lowering refuses on a declared type, so a query the preview served before is not refused now. It is read with the live faces' column type.
    • Unnamed but implied: the 'any' arm also falls through to cube.measures, so a where on a measure key whose sql is a bare column (revenue over annual_revenue) is now read from that column in the preview. This matches the strategies' filter resolution as declaredMemberEntry's own doc states it, and it is the same class as the changeset's "a member such as…". Right, but no test covers it (S2).
  6. evaluateAnalyticsQueryOverRows gains an optional trailing whereColumns. No public-surface change. Right. The package's exports map is "." alone, and src/index.ts does not export preview-evaluator.ts. The parameter is optional and last, so in-package callers compile unchanged. previewWhereColumns is module-private. ObjectQLStrategy is exported, but only its private methods' bodies change, not their signatures.
  7. No spec key, export, error code or status added. The changeset's own closing sentence says so, and the diff agrees.

Card "Wanted" lines against the diff:

Tests: preview-time-filter-fk-parity.test.ts, 17 cases on real SQLite (5 for #28690, 7 for #28691, 5 for #28692 including the #28555 preview-refusal control). Each case is asserted on every face it claims. The red-on-base and ablation readings are the dev's (os-dev-report) and are not re-run here (read-only).

② Semver level

  • Changeset .changeset/28690-preview-time-buckets-filter-columns-fk-id.md: '@objectstack/service-analytics': patch, the only package the diff moves. It carries Clause-②: yes (widening) and Metadata-protocol: none, line-initial. The PR body carries both lines the same way. No skip-changeset label (the PR has no labels), which is correct because this is a published package.
  • The level matches what the diff publishes. AGENTS.md Post-Task Checklist step 3 says the level follows the Metadata-protocol kind alone: none takes patch, and a non-protocol widening ships patch. There is no packages/spec file and no authorable key, so Metadata-protocol: none is right, and so is patch.
  • Clause-②: yes (widening): right. Shapes 1 and 2 in ① move INVALID_QUERY / 400 to served on a published package's strategy, which widens the accept set. Shapes 4 and 5 correct answers without moving the accept set. Nothing in the diff refuses what was served, so no (narrowing) arm is owed, nor a BREAKING banner or ADR-0087 marker. The dev's dogfood-battery skip, on the grounds that the change is widening only, holds.

Gate verdicts: the head's check-runs, read 2026-11-01T16:54:21Z: 48 check-runs, all completed — 35 success, 13 skipped (UI, docs, dogfood and opt-in jobs this path set does not trigger), 0 failure / cancelled / timed_out. Build Core, all eight Test Core shards, every Lint and Type Check job and Check Changeset among the successes.

③ Boundary flags

  • open_questions: []. Nothing to answer.
  • flags: the os-dev-report has no flags key. Its three deviations were read as flags, and none bears on the contract:
    • The remote branch was recreated with no force push.
    • The first ablation pass was discarded and every ablation re-run on the committed de8aeea7.
    • The PR was opened with no labels, which is correct with a changeset present.
    • Head 400154ac is de8aeea7 plus a two-spelling change to the changeset text (the diff confirms no code differs), and the dev re-ran the six changeset- and citation-reading gates there.
  • out_of_scope_findings[0] is escalated to the seat (not fixed here, correctly): on the ObjectQL strategy's direct, non-cross-object path, two dataset dimensions over one base column (region and zone over region) still answer INVALID_QUERY / 400 (duplicate groupBy), while native and the preview serve both names. It has the same root as analytics: the ObjectQL strategy refuses a backed account.id dimension with INVALID_QUERY because the cross-object expand groups by the related id twice #28692 on execute()'s groupBy. The diff confirms it is untouched: fkGrouped reuses only a column that is a cross dimension's fkField. It is class a, measured, with reach given, so the seat files or dedupes it as a card. It does not block this PR, which neither causes it nor makes it worse. (Not part of this review's inputs, so not relied on here: an open card analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744 carries that title. The seat confirms it is this finding.)
  • The claim's file surface allowed packages/objectql/src/**. The diff does not use it, which the claim named as a possibility, not a requirement.

Should-fix (non-blocking, not a condition of the verdict):

  • S1. analytics-service.ts now holds three copies of the same guarded leaf read (collectFilterLeaves(normalizeAnalyticsFilterTree({ where }, NO_DATETIME_COLUMNS)) in a try/catch): filterMembers in the named-fields reader, relationshipMembers, and the new previewWhereColumns. Extract one whereLeafMembers(query) and have all three read it. That is the copy-drift this file's own declaredMemberEntry doc warns about.
  • S2. Add one case for a where on a measure key over a bare column (①5, sub-bullet), the one resolution arm previewWhereColumns adds that the suite does not exercise.

Must-fix: none.

Implemented-by: claude/project-thread-0o422x
Reviewed-by: session_01FEkQ5kE5BrPUPNMMyv6oCH

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT · PR #28742 · 2026-11-01T16:55Z

Head-sha: 400154ac098acb0cc64c2db977d659d0d660153d

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #28690/#28691/#28692 各占一行,合并关卡正确,与派单 pm-orders/server/order-28690.md 一致。文件 5 个,在认领 6559855795/6559874127/6559890848 申报面内(仅 service-analytics,未用到申报里可选的 packages/objectql)。
  2. 前提实测:dev 在真实 SQLite 上对三张卡各出口先红后绿,并逐项消融;analytics: the dataset draft preview answers [] for a runtimeFilter member spelled OBJECT.region that both live strategies resolve to the declared dimension #28691 选"解析"而非"拒绝",因两个线上出口对实测的每种写法都能答。
  3. 门禁:本 head check-runs 35 success、13 skipped(路径未命中)、0 失败或待跑。dev 本地:完整构建后 62 条派生门禁全部 exit 0,typecheck 与手动门禁 exit 0。
  4. 接受的偏差:dev 三条偏差均为过程性(远端分支在 fix(service-analytics)!: members read through a relationship path answer one refusal on the native, ObjectQL and draft-preview faces #28686 合并后被删,推送新建;一次消融还原误删未提交修改,已恢复并在 de8aeea7 重跑全部消融;PR 未带标签)。复核两条 should-fix 记为后续,不挡落地:(S1)analytics-service.ts 中同一受保护 where 叶子读取有三份,可抽一个 helper;(S2)缺"筛选落在映射到普通列的度量键"的用例。
  5. 范围外去向:dev 实测的 ObjectQL 直连路径"两个维度同列"仍 400,已立卡 analytics: the ObjectQL strategy refuses two dataset dimensions over one column with INVALID_QUERY (duplicate groupBy), while native and the preview serve both names #28744(p3,pm:blocked 待本 PR 合入后转队列)。Metadata-protocol: none,Skills-docs: none(已核)。
  6. 落地条款:Clause-②: yes (widening),非 spec 路径 ⇒ TIER_DEFAULT 复核 PASS(6560982865,Head-sha 与现 head 一致)。semver:service-analytics patch,与 changeset 一致(非协议扩大取 patch,Check Changeset 本 head success)。check-governed-merges --pr 28742:NOT governed,411 行。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

1 participant