Repository navigation
Seed datasets have no locale axis — an app shipping demo data for two language markets has to select seeds at config-assembly time, which bakes the choice into dist and leaves the other locale's rows behind #16510
Description
Symptom
SeedSchema can gate a dataset on environment but not on locale. An app that wants to ship
a Chinese demo dataset and an English one — the same records, different display strings — has no
declarative way to say which applies, so the selection has to happen in application code before the
platform ever sees the seed.
The schema is strictObject, so an app cannot add the key itself:
packages/spec/src/data/seed.zod.ts:33—SeedSchema = lazySchema(() => strictObject({…}));
fields areobject/externalId/mode/env/recordsplus the package-provenance keys.
The alias table mapsenvironmentandenvironmentsontoenv; there is no locale spelling.packages/spec/src/data/seed-loader.zod.ts—SeedLoaderConfigSchemacarriesenv,
organizationIdandidentity, but no locale.packages/metadata-protocol/src/seed-loader.ts:2486—filterByEnv(datasets, env)is the only
dataset-level filter;:490is its single call site.
Verified against the source tree at c383352, and against the published @objectstack/spec@17.2.0
d.ts as installed in a consuming app. A repo-wide grep for locale across the seed sources returns
one unrelated hit (a pinyin-search comment in examples/app-showcase/src/data/seed/index.ts:111).
Why this lands on the platform
Per references/app-platform-boundary.md, third row of the deciding table: the subject is
platform behaviour and the cost lands on the app — the missing filter axis is the loader's, and
the workaround is paid for in every app that needs it.
The second question — if a second app needed this, would it copy the implementation? — is
answered structurally rather than by a second consumer already in hand: any marketplace app
installed into an org whose locale differs from the author's has the same problem, and the person
installing it cannot be expected to know an app-private environment variable exists. One real
consumer today (see below); the generalisation argument is the marketplace install path, not a
second app already written.
What the app-side workaround costs
objectstack-ai/kpi currently selects between two seed profiles with an app-invented environment
variable read at module-evaluation time:
// src/data/index.ts
export const SEED_PROFILE = (process.env.OS_SEED_PROFILE ?? 'default').trim().toLowerCase();
export const KpiSeedData = SEED_PROFILE === 'software' ? SoftwareSeedData : DefaultSeedData;Because the choice is made while the config is assembled rather than while seeds are loaded, two
defects follow that the app cannot fix from where it sits:
- The selection is cached in the build output. Switching profiles requires deleting
dist,
otherwise the previous profile is still in effect. The app's README has to document this. - The other profile's rows stay in the database. Every profile is
upsertand the loader only
writes, so switching on a non-empty database leaves both datasets resident — two org trees and
two plans in one database. The app's README has to tell operators to use a fresh database file.
Both are consequences of filtering in the wrong layer. A loader-side filter is evaluated at load
time, so the dist caching problem does not arise; and the loader already holds externalId and
the _packageId / _provenance keys, so it is the only layer that could reconcile rows belonging
to a locale that is no longer active.
Expected capability
Add locale to SeedSchema as a filter axis exactly parallel to env, honoured by the loader:
SeedSchema.locale?: string[]— omitted means "applies to every locale", matching howenv
defaults to all environments.SeedLoaderConfig.locale?: string— resolved from the runtime's configured locale, overridable
the wayenvis.filterByEnvbecomes the composition of both axes; a dataset applies when it passesenvand
locale.
Deliberately out of scope: the platform does not translate anything. The app still authors both
record sets. The platform gains only the axis to select between them.
Scope note
The spec field and the loader filter have to land together. A locale key accepted by the schema
but ignored by the loader is a declared-not-enforced surface — the shape this repo routes to the
enforce-or-remove channel — and it would be worse than the current state, because an app would
write the key and silently get both datasets.
Environment
@objectstack/spec/@objectstack/runtime/@objectstack/cli17.2.0- Source tree
c383352 - Consuming app:
objectstack-ai/kpi(Apache-2.0, public)
Duplicate check
Searched seed data locale language variant demo data per locale i18n seed selection and
seed loader env filter records upsert in this repo. The two locale hits are unrelated — #9135
(auth email locale) and #5419 (console UI language). The control search surfaces real seed-loader
cards (#16488, #14403, #3434), so the negative result on locale is a real absence rather than a
search that matched nothing.
Activity
hotlong commentedon Sep 7, 2026
Triage note — filed, not claimed. Reading taken 2026-09-07T06:2xZ.
File surface (read from the source tree at
c383352, not inferred from the title):packages/spec/src/data/seed.zod.ts:33localetoSeedSchema(strictObject, so an app cannot add it)packages/spec/src/data/seed-loader.zod.tslocaletoSeedLoaderConfigSchemapackages/metadata-protocol/src/seed-loader.ts:2486filterByEnvbecomes the composition of both axes; single call site at:490Cross-domain, deliberately not split. The surface spans
packages/spec(domain:spec) andpackages/metadata-protocol(domain:engine). It should stay one card: alocalekey accepted bythe schema but ignored by the loader is a declared-not-enforced surface, and an app writing the key
would silently get both datasets — strictly worse than today, where the app at least knows it has to
select the dataset itself.
Per the cross-domain exception path this needs the triage seat to designate one lane PM. Noting for
whoever picks it up:
packages/specbelongs to the spec seat under the sole-owner rule, which thecross-lane direct-pickup path does not waive.
Why this was not dispatched by the seat that filed it. Seat readings at the time of filing —
domain:spec(#6017) green with in-flight dev 3/3 and produce timestamped 2026-09-07T05:20Z, nohandover brief;
domain:engine(#6367) green. Round mutual-exclusion therefore stands down. Thecard is left open,
pm:queue, no assignee, which is the state the owning lane consumes.Origin. Reported from
objectstack-ai/kpi, an Apache-2.0 app on@objectstack/spec@17.2.0,where the workaround described in the body is currently in production use. That app carries the
interim fixture; it will be removed when this lands.
Generated by Claude Code
hotlong commentedon Sep 7, 2026
Claim: PM loop round 1
Session:
session_01QhMsYoDpoMGeQLf24eTawPBranch:
claude/issue-16510-seed-locale-axisWorktree:
objectstack-issue-16510Domain:
domain:spec(lead;packages/specsole-owner rule) +domain:engine(cross-domain exception path, single PR)File surface:
packages/spec/src/data/,packages/metadata-protocol/src/seed-loader.ts(stop on breach; explain in the report)Container & model:
M,mode:cloud,model: claude-opus-5(contract-review tier, forced by Clause-② yes)Clause-②: yes
Serial constraints cleared:
#16488 touches the same file (packages/metadata-protocol/src/seed-loader.ts) but a disjoint region — externalIdKey/externalIdLabel diagnostic rendering, not filterByEnv; it is open with no assignee and not dispatched, so no in-flight overlap. No other open card names the seed schema or the dataset filter.Clause-② rationale: the card adds a key to
SeedSchema, which is astrictObject— a spelling that is rejected today becomes accepted, and the published contract surface widens. Both halves of the Clause-② test, so this goes to the contract-review tier.Cross-domain note: dispatched as one card rather than split, per the scope note in the body — a
localekey accepted by the schema but ignored by the loader would be a declared-not-enforced surface.Maintainer ruling authorising this dispatch: "和你相关的任务你负责派发" (2026-09-07, in session). This supersedes the round mutual-exclusion stand-down recorded in the triage note above.
Generated by Claude Code
hotlong commentedon Sep 7, 2026
Dispatched — PM loop round 1, 2026-09-07T10:42Z.
mode:cloudsession_013r78utTbiWqxghcuRJxfZfclaude-opus-5(contract-review tier, forced byClause-②: yes)claude/issue-16510-seed-locale-axisobjectstack-ai/objectstack(push authorisation at birth viasource_url)The dispatch order carries: the
os-devdefinition as the production-side contract (dev instructed to stop and ask if.claude/agents/os-dev.mdis absent rather than improvise); the established file surface with stop-on-breach; the Clause-② consequences (changeset bump level for a new optional field on a published schema, strictness ledger, contract test, the two seed docs pages); the test shape to mirror (packages/metadata-protocol/src/seed-loader-env-scope.test.ts, the existingenv-axis precedent); and the serial constraint against #16488 with the breach condition spelled out.Self-driving clause included: two legal turn endings only — pushed branch + draft PR + report, or a detailed hard blocker. Open design choices are the dev's to settle and record under
open_questionsrather than stopping to ask. Foreground execution required.Explicit non-goals passed to the dev: no translation, no per-locale record merging, and no reconciliation of rows already written under a different locale. That last one is a genuine follow-up — the PM will file it as its own card if the dev's report says it is needed.
PM will subscribe to the draft PR as soon as it exists.
Generated by Claude Code
hotlong commentedon Sep 7, 2026
os-dev-report
{ "issue": 16510, "status": "done", "branch": "claude/issue-16510-seed-locale-axis", "pr": "https://github.com/objectstack-ai/objectstack/pull/16592", "premise_still_valid": true, "summary": "Added `Seed.locale?: string[]` (BCP-47 tags, optional, `.min(1)`, aliases `locales`/`language`/`languages`) and `SeedLoaderConfig.locale?: string`, and made the seed loader compose the two scope axes by conjunction: `filterDatasets` = `filterByLocale(filterByEnv(...))`, replacing the single `filterByEnv` call site. `filterByLocale`/`datasetAllowsLocale`/`isLocaleScopedDataset` mirror the `env` helpers down to the reporting posture. Both halves land together, so the key is never accepted-and-ignored by the loader. The one thing NOT in this PR is the runtime PRODUCER: no first-party call site passes `config.locale` yet (those three `seedLoader.load(request)` sites live in packages/runtime/src/app-plugin.ts, outside the declared file surface), so on the default boot path the axis is inert — signposted by a new `warnOnUnresolvedLocaleScope` that names every locale-scoped dataset it let through, and recorded honestly in the liveness ledger as `experimental` rather than `live`.", "tests": "All foreground, heavy runs through scripts/pm/os-verify-lock.sh, slot issue-16510. (1) Closure: `pnpm --filter '@objectstack/metadata-protocol^...' build --concurrency=2` -> VERDICT command-exit 0. (2) Generated: `pnpm --filter @objectstack/spec build && check:generated --fix` -> 3 of 15 stale (skill references, content/docs/references/**, liveness/state-counts.md), all three regenerated, VERDICT command-exit 0. (3) New+precedent tests: 'Test Files 2 passed (2) / Tests 29 passed (29)'. (4) `pnpm --filter @objectstack/spec test && typecheck` -> 'Test Files 460 passed (460) / Tests 12778 passed (12778)', VERDICT command-exit 0. (5) `pnpm --filter @objectstack/metadata-protocol test` -> 'Test Files 170 passed | 2 skipped (172) / Tests 2448 passed | 10 skipped (2458)'; typecheck VERDICT command-exit 0. (6) ABLATION — subject resolves through SOURCE (the test imports './seed-loader.js', a relative specifier inside the package), so no dist leg applies; mutation = early `return true` above the `normalizeLocaleTag(locale)` read in `datasetAllowsLocale`. On-disk proof: marker occurrences 1, HEAD blob 7772349ec06deaa63b19a54a0b7f42011fcbfe5d vs mutated blob 5ea1048d3883b16f98129a8bdb00442c7053aa34. MUTANT leg: mutant-exit=1, 'Tests 8 failed | 8 passed (16)'. RESTORE leg: restored blob 7772349ec06deaa63b19a54a0b7f42011fcbfe5d == HEAD blob, marker absent. RESTORED leg: restored-exit=0, 'Tests 16 passed (16)'. Direction observed was the expected one (turns red), and the 8 that stayed green are the unscoped / no-locale / env-only legs that do not read this predicate. (7) GATES: derived, not guessed — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at d621dec over 14 paths, 103 commands, ALL 103 run. Final: 102 pass, 1 not measurable. Three were red first pass and are fixed in the diff: check:system-context-census (6 problems; my +51 lines in seed-loader.ts rotted 3 anchors cited from content/docs/permissions/system-context.mdx — repaired by the gate's own --fix, now 'OK — 106 elevation read sites in 20 packages across 45 files, all anchored'); check:engine-double-contract (the new test pins 3 doubles the ledger lacked — `--write` reports '756 (file, verb) row(s), 3 added or grown, 0 lost', gate green); and check:skill-examples / check:dual-build-cjs-loads / check:type-check-debt, all three PREREQUISITE NOT MET (exit 3 for two) for want of built output — built the workspace ('72 successful, 72 total') and all three went green, with check:type-check-debt re-run at 8 GB after a 4 GB OOM: '--re-measure: OK — 5 ledger entr(ies) re-measured in 66.6s, 55 raw tsc error(s) total, none above its recorded number'. NOT MEASURED, not read as a pass: `node scripts/check-plugin-teardown-shape.mjs --self-test` refuses on a shallow clone ('cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4'); `git fetch --deepen 500` did not reach it; it is a checker-health self-test over the checker's own fixtures and my diff touches neither it nor plugin teardown shapes. (8) LINT: the repo-wide run, not a narrowed one — `pnpm lint` (eslint . --no-inline-config) VERDICT command-exit 0 in 66s at HEAD 35929cd3, so no narrowing to declare and no three-part narrowing evidence owed. (9) BYTES: check:nul-bytes green plus a direct control-character grep over every file in the diff — no hits.", "mcp_calls": "7 — issue_read get, issue_read get_comments, create_pull_request, issue_write update (labels), pull_request_read get x2 (body/label read-back), add_issue_comment. REST channel was probed first per the contract and is UNAVAILABLE in this container: `curl GET /repos/objectstack-ai/objectstack/issues/16510` returns http=403 'GitHub access is not enabled for this session', so the label write and both read-backs went through MCP. Declaring the channel switch.", "open_questions": [ { "question": "Where should the loader get its locale when the host passes none? The env axis resolves itself from NODE_ENV inside `load()` precisely so call site seven cannot reopen framework#4704's hole; the locale axis has no equivalent source in this repo.", "options": [ "A. Leave `config.locale` host-supplied, and make an unresolved locale LOUD — a named warning listing every locale-scoped dataset seeded anyway, plus an `experimental` liveness row that spells out the producer gap.", "B. Mint an `OS_I18N_LOCALE` env var and resolve inside the loader, mirroring resolveSeedEnvFromNodeEnv exactly.", "C. Have the loader read the stack's i18n config through IMetadataService." ], "recommendation": "A, and it is what shipped. B mints a declared-and-unset env var, which is the trade the seed loader's own NODE_ENV comment already refused for `OS_SEED_ENV`, and it would put the operator's locale in a different place from the app's `i18n.defaultLocale`. C is not reachable: ManifestSchema carries no i18n, and the loader's only metadata call today is `getObject` — the i18n config lives in the app plugin, not in the metadata store. So the honest producer is the runtime, which is outside this card's file surface (see out_of_scope_findings)." }, { "question": "Should a dataset scoped `['zh']` match a runtime locale of `zh-CN`?", "options": [ "A. No — exact tag match, case-insensitive only.", "B. Yes — fall back to the primary language subtag." ], "recommendation": "A, shipped. Case folding is normalization of an identifier that RFC 5646 defines as case-insensitive; primary-subtag fallback is a second matching dialect on our own contract, which Prime Directive #12 forbids. The author's remedy is to list both tags, and the loader's skip log names exactly what it dropped. Pinned by a test in both directions." }, { "question": "Liveness status for the new key: `live` or `experimental`?", "options": [ "A. `experimental`, with the consumer cited and the producer gap written into the row.", "B. `live`, with the consumer pointer and no `producer` field (which merely joins the #4837 worklist)." ], "recommendation": "A, shipped. `live` means authoring the property changes runtime behaviour, and on the first-party boot path today it does not. B is precisely the verdict `seed.env` carried before framework#4704 — the specimen the `producer` field was built from — and under-claiming is the safe error under Prime Directive #10. The row says to re-classify to `live` with a `producer` pointer when the runtime wiring lands." }, { "question": "Reuse `LocaleSchema` from system/translation.zod, or declare the tag inline in data/seed.zod?", "options": [ "A. Reuse LocaleSchema.", "B. Inline `z.string()` to keep the data namespace's dependency closure unchanged." ], "recommendation": "A, shipped, with its cost declared: the import enlarges the data skill's reference closure, so `gen:skill-refs` added one generated line to skills/objectstack-data/references/_index.md — a governed path under Prime Directive #14. It is a generated register row (+1 line; no SKILL.md touched, published skills total unchanged at 6858 lines), which PD#14's regenerated-register clause covers. One spelling of 'locale' in the spec was worth that over saving a governed path from the file list." } ], "out_of_scope_findings": [ "PLEASE FILE (required for the axis to work on the default boot path): wire `config.locale` from the runtime's configured locale (`i18n.defaultLocale`) at the three `seedLoader.load(request)` sites in packages/runtime/src/app-plugin.ts and the draft-publish path in packages/metadata-protocol/src/protocol.ts, then flip packages/spec/liveness/seed.json's `locale` row from `experimental` to `live` with a `producer` pointer. This is the STOP-ON-BREACH report the dispatch asked for: the correct completion needs packages/runtime, which is outside the declared file surface (`packages/spec/src/data/` + seed-loader.ts), and app-plugin.ts is a hot 1800-line file no serial-constraint clearance covered, so I did not widen into it.", "PLEASE FILE (named in the dispatch as a real follow-up, deliberately not attempted): reconcile rows already written under a locale that is no longer active. The loader only ever writes, so switching markets on a non-empty database leaves both datasets resident — the loader is the only layer that could do it, since it holds externalId and the _packageId / _provenance keys.", "noted, not filed: `check-plugin-teardown-shape.mjs --self-test` cannot run on a shallow clone (its positive control is pinned to commit 621a487607881c66b2899b7e3477115229a156b4, unreachable after `git fetch --deepen 500`). Not a defect in the script and not in any contract — it is an environment shape, and CI checks out deep enough. Successor: none — no PR or person is heading into this file.", "noted, not filed: content/docs/permissions/system-context.mdx cites seed-loader.ts by LINE, so any edit to that 2,800-line file rots three anchors. The gate catches it and `--fix` repairs it mechanically, which is the system working; symbol anchors would make it not happen at all, but that is a citation-form migration nobody is mid-flight on. Successor: every future PR touching seed-loader.ts, which will hit the same green-after---fix loop." ] }Generated by Claude Code
hotlong commentedon Sep 7, 2026
PM review — round 1 complete. Reading taken 2026-09-07T12:05Z on head
35929cd.CI green. 38 check runs, all
successorskipped, none failed.Governed Surface Queue Guardgreen,
No other open PR may claim the same issuegreen,Check Changesetgreen.mergeable_statehas goneblocked→clean.Report accepted. Spot-checked rather than taken on trust: the three
seedLoader.load(request)call sites the report names in
packages/runtime/src/app-plugin.tsare real (:1249,:1326,:1642, confirmed by grep). The declared file surface held — nothing in the diff touches #16488'sexternalIdKey/externalIdLabelregion, so the serial constraint cleared in the claim wasrespected. The one gate the dev could not run (
check-plugin-teardown-shape --self-test, refuses ona shallow clone) is declared as not-measured rather than counted as a pass, which is the correct
posture.
One correction to the report's call-site list, for whoever implements the follow-up: it also names a
draft-publish path in
packages/metadata-protocol/src/protocol.ts. A grep atc383352found nothingthere. Re-derive the full list against current
mainrather than trusting either reading.Both requested follow-ups filed:
config.localeat the runtime's seed-load call sites — the locale axis added by #16510 is inert on the default boot path until a producer supplies it #16595 — wireconfig.localeat the runtime call sites, flip the liveness row toliveBoth carry
Blocked-by: #16510. #16596 was the one I had initially declined to file for want of adecided owner; the report supplied the judgement (the loader is the only layer holding both
externalIdand the provenance keys), so it is filed with the remaining decisions written into it.What this card is now waiting on, and it is not the PM. PR #16592 is a draft carrying
needs:contract-review, and it touches a governed path (skills/objectstack-data/references/_index.md,+1 generated line). Under the red lines the dispatching seat does not flip it ready, queue it, arm
auto-merge, approve it, or merge it — the contract review is another seat's or the maintainer's.
The
needs:contract-reviewlabel is the inbox that carries it there; nothing further for me to flip.PM keeps the PR watched until it merges or closes.
Generated by Claude Code
hotlong commentedon Sep 7, 2026
Contract review — PASS (director seat, summon #17,
CONTRACT_REVIEW_TIER); route is GOVERNED — draft stays, human mergeReviewed-by:⚠️ That claim (5569403849) records
session_01XesLUWmuhjuRwmU618AZ1M(director seat, GitHubhotlong). Tier fuse:get_session13:24Z →external_metadata.last_served_model=claude-fable-5-1=CONTRACT_REVIEW_TIER.Implemented-by:
mode:clouddev sessionsession_013r78utTbiWqxghcuRJxfZf(branchclaude/issue-16510-seed-locale-axis), dispatched bysession_01QhMsYoDpoMGeQLf24eTawP.model: claude-opus-5 (contract-review tier …)—claude-opus-5isTIER_DEFAULT, notCONTRACT_REVIEW_TIER; the implementation ran one step below the mandated tier, which is exactly what this review compensates. Different sessions ⇒ independent.Carrier: PR #16592 at head
35929cd379c6— 16 files, +660/−6, read from the fetched head, ⛔ not from the PR body.① Derived judgments — accept set and public face, per change
SeedSchema.locale?: string[]—z.array(LocaleSchema).min(1).optional(), no default, on astrictObjectunrecognized_keysis acceptedenv's closed set of three); an empty array is refused. The feature decision itself is on the manual floor and its provenance is the maintainer's own dispatch on this card (5569403849, 「和你相关的任务你负责派发」)locales/language/languages→localein the alias tablelocale(pinned:parsed.successfalse, message nameslocale) — the same mechanism asenvironment(s)→envSeedLoaderConfigSchema.locale?: string(LocaleSchema.optional())z.object)filterDatasets=filterByEnv∧filterByLocale; tags compared case-folded and otherwise exact (['zh']does not matchzh-CN); permissive whenconfig.localeis absent butwarnOnUnresolvedLocaleScopenames every locale-scoped dataset it let through and the config key;infolog names every dataset droppedlocalepackages/spec/liveness/seed.json:localerowexperimental, consumer cited, producer gap spelled out;authorable-surface/data.jsongains both keysconfig.localeat the runtime call sites, flip the row tolive) and #16596 (reconcile rows under an inactive locale) exist withBlocked-by: #16510content/docs/references/**(3),skills/objectstack-data/references/_index.md(+1),engine-double-contract.pinned.json(+15),system-context.mdxanchorsReviewer note, not blocking:
LocaleSchemais a barez.string()(system/translation.zod.ts:12), solocale: ['']passes the schema. That is the pre-existing shape ofLocaleSchema, outside this PR; an empty tag can never match a suppliedconfig.locale, and the skip log names it.② Semver
@objectstack/specminor ·@objectstack/metadata-protocolminor — matches: a published schema and a loader config gain optional keys; behaviour is additive.③ Boundary flags
Four
open_questions, each answered A by the dev; this seat concurs on all four: host-supplied locale made loud rather than a minted env var; exact tag match;experimentalnotlive; reuseLocaleSchemaat the cost of one generated register line. The PM review (5570347106) already corrected theprotocol.tscall-site claim — nothing further.Route — GOVERNED, so PASS does not open the queue
node scripts/pm/check-governed-merges.mjs --testover the 16 paths: GOVERNED — 1 of 16 (skills/objectstack-data/references/_index.md,skills/**); the generated-surface exception (#11705) did not lift it — the generator declared no output set, fail closed. ⇒ Prime Directive #14: the PR stays draft, no queue, no auto-merge; a human merge is the review record. This PASS clears theneeds:contract-reviewcarrier on the PR (the card never carried it) and the terminal four-step follows on the PR:needs-user-decisionlabel, the final 「维护者速读」 comment, review request / assignment to the authorised accounts. CI on35929cd: 38 check runs, all success/skipped.Generated by Claude Code
hotlong commentedon Sep 8, 2026
落地受阻记录 · PR #16592 第二次被踢出合并队列
按签名四分支的「新签名」要求,在 PR 与本卡各留一份完整签名与初判。完整版在 PR #16592 评论 5577891324,这里留摘要以便本卡可独立追溯。
签名:
01:48:11Z以CI_FAILURE出队,但无任何测试失败、无任何 job 报 failure。批次queue/main/pr-16592-be92d468…的四个merge_groupworkflow 三绿(Governed Surface Guard / Spec Liveness Check / Lint & Type Check),CIrun34175875839为cancelled——其 17 个 job 中 16 success,唯一非绿是Build Core(job101905084839):Build packages (excluding docs)跑了 34m27s 未完成,其后 12 个 step 全 pending,01:47:53Z被取消,18 秒后出队。对照: 同 workflow、晚 52 秒起跑的 #16573 该 job 8.6 分钟 success;本 PR 同代码在自己 head 上 8.5 分钟 success;本批次其它 job 均正常(Test Core 12.1m、Build Docs 5.6m)。⇒ 非全仓变慢、非本批次变慢、非本卡代码问题,是单 job 停摆。
初判: 基础设施侧单 job 停摆。⛔ 不自行认定为 flaky、不加台账(只有人工能升级台账),⛔ 不反射式重投(本 PR 第二次被踢;第一次
14:11Z的MERGE_CONFLICT已由返工第 1 轮真实修掉)。本卡交付物状态未受影响: PR
clean、非草稿、批准有效、headba564fcc未动,15 个文件+659/−5,契约复审 PASS 依然成立。卡的实现没有任何待办——挡住的纯粹是落地环节。下游 #16595 / #16596 的
Blocked-by: #16510继续保持,待本 PR 合并后由我收口。Generated by Claude Code