Repository navigation
qa: os test prints suite and scenario names, filters by tags, and skips on unmet requires (5 keys) #20289
Description
Ruled: 5863822176 · letter B · 2026-09-28T05:06Z
Filing gate: ① a declared≠enforced family, filed as one sweep card per family under ruling A′ item ④ on #18900 (5727134555). This is triage's standing request 5857165909 on the seat post. Family qa-runner, seat verdict ENFORCE.
reach:the declared authoring door.packages/specparses these keys and publishes them in the reference docs. The liveness ledger rows cited below record them as not enforced, and the census re-measured the reader side (§5 cross-checks, each with a lit control).- The criterion is the maintainer's: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」.
- The maintainer's one word, per ruling A′ ④: ENFORCE (the seat's proposal: the mainstream has it, so build the consumer once, correctly) or RETIRE (retire the keys together with their ledger rows).
Census by the domain:spec execution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. Bases: objectstack a9fb83ef, re-checked against 4d7e740d, where no ledger file or cited surface moved; objectui 6fa5f64a1 (pin f8a9d0fb); cloud 96eb092. Ledger instrument: check-liveness.mts --json, whose byStatus equals the committed state-counts.md row for row. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. The ranking is by value, user-visible risk × keys. This family's rank is 7 of 16. The sibling family cards filed so far are #20273, #20274, #20281 and #20282.
Capability: Named test suites and scenarios in reports, tag-based selection, and declared preconditions that skip with a reason
| key | ledger status | ledger row | what the ledger cites |
|---|---|---|---|
qa.name |
dead (verified 2026-08-10) | packages/spec/liveness/qa.json:5 |
note: Parsed and never read. TestRunner.runSuite (packages/core/src/qa/runner.ts:25-31) touches only suite.scenarios, and the one caller prints path.basename(file) as the suite heading (packages/cli/src/commands/test.ts:95) — so renaming a suite changes not… |
qa.scenarios.name |
dead (verified 2026-08-10) | packages/spec/liveness/qa.json:20 |
note: Its describe() says 'Scenario name for test reports' and no report carries it: TestResult (packages/core/src/qa/runner.ts:6-12) has scenarioId and no name field, and the CLI prints result.scenarioId (packages/cli/src/commands/test.ts:104). So an autho… |
qa.scenarios.description |
dead (verified 2026-08-10) | packages/spec/liveness/qa.json:26 |
note: Docs-shaped annotation, no consumer — the same disposition flow.description and hook.label/description carry and for the same reason: an author is not misled by a field that only claims to describe. Exempt from enforce-or-remove (ADR-0033). |
qa.scenarios.tags |
dead (verified 2026-08-10) | packages/spec/liveness/qa.json:32 |
note: The sharpest row in this file. Its describe() promises 'Tags for filtering and categorization (e.g. "critical", "regression", "crm")' and NOTHING filters on it: os test has three flags — --url, --token and --fail-on-empty (#7848) — and not one of th… |
qa.scenarios.requires |
dead (verified 2026-08-10) | packages/spec/liveness/qa.json:59 |
note: Declared environment preconditions — requires.params (environment variables) and requires.plugins (plugins that must be loaded) — that nothing checks. Neither the runner nor the CLI reads scenario.requires; there is no skip path and no precondition fa… |
Mainstream evidence:
- ServiceNow Automated Test Framework (ATF): named tests and test suites with descriptions, and results reported per test. Tag filtering in ATF is UNVERIFIED.
- Power Apps Test Studio: test suites and test cases with names and descriptions, and results by case.
- Odoo's own test framework:
@tagged(...)on test classes and--test-tagson the CLI, which is tag selection in a metadata platform. - Test frameworks the platform's developer audience uses: Playwright tags +
--grep, Cucumber@tags, pytest markers /skipiffor preconditions.
Verdict: ENFORCE — the mainstream has the capability, so build the consumer once, correctly.
Reader that must exist / disposition: objectstack packages/core/src/qa/runner.ts: TestResult carries the scenario name, and a scenario with unmet requires becomes skipped-with-reason. packages/cli/src/commands/test.ts adds a --tags flag (today only url / token / fail-on-empty, :273-275) and prints the names.
User-visible risk (2): tags promises filtering ("critical", "regression") that does not exist, and requires.plugins reads as a guard but never skips. Both are misleading rather than benign.
Acceptance: Every ledger row listed leaves dead/planned/experimental for live, citing the new reader as file#symbol (and a producer where the read depends on a supplied input); pnpm check:liveness green; the family's byStatus in state-counts.md regenerated.
Lane: domain:spec parent + domain:engine-core / cli sub-issue, all in objectstack
File surface: packages/spec/src/qa/testing.zod.ts:63-79 · packages/core/src/qa/runner.ts · packages/cli/src/commands/test.ts · packages/spec/liveness/qa.json
Dedupe: TestSuiteSchema \| qa/runner \| scenario\.tags \| scenarios?\.(tags\|requires) \| os test --tags \| testing\.zod → 1 open hit. None carries a key of this family:
- [PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #6015 — triage seat post; cites the historical spec(qa): TestScenarioSchema/TestSuiteSchema are declared-but-inert — no runtime consumer, no liveness ledger #6247 qa adopt-or-retire (resolved: adopt, 2026-08-08)
四轴:
- 实际业务需求: 测试报告显示可读名称、按标签只跑回归集,是 ATF 与各主流测试框架的基本能力。今天报告里只有 scenarioId。
- 项目长远合理性: QA 协议(spec(qa): TestScenarioSchema/TestSuiteSchema are declared-but-inert — no runtime consumer, no liveness ledger #6247 曾撤回退役)已经选择了「做」这条路。补齐这 5 个键,让声明与执行对齐,不留半截协议。
- 防 AI 写错: AI 写
requires.plugins以为是守卫,实际在缺插件的服务器上会跑出无法解释的 HTTP 错误。执行后改为带原因的跳过。 - 创业阶段不扩散: 全部落在现有 runner 与 CLI 两个文件里,无新机制。
Activity
objectstack-fleet commentedon Sep 28, 2026
Release:
session_01QcAS3qiYYZNezaxZxaUdMV· cause: premise falsified (therequireskey cannot be enforced honestly as declared) · destination:needs-user-decision·domain:specseat 2 · 2026-09-28T03:58Z5a6267f486, an ancestor oforigin/main.os testprints suite and scenario names, and--tagsselects scenarios.qa.name,scenarios.name,scenarios.descriptionandscenarios.tagsarelive.requiresis described as NOT CHECKED and staysdead.5861125164; at-tier PASS 68/685862007793; the landing round and its delta PASS 94/94 at5b15c28fb1; ACCEPT on this card.Part of #20289, so this card stays open.requires, waits on the maintainer. The dev measured (5861105453,5861913911) that neither sub-key has an honest judge fromos testtoday:plugins: no served surface lists loaded plugins.params: "environment variables or parameters" names no process.servicesreplacesplugins; C: retirerequires). An execution seat does not rule contract shape.pm:dispatched→needs-user-decision, and the assignee is released.objectstack-fleet commentedon Sep 28, 2026
待裁:
os test场景的requires(前置条件)怎么兑现 — 执行席落卡 ·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-28T03:59Z一句话问题:测试场景可以声明"需要某插件 / 某参数才跑",但今天没人检查 —— 条件不满足时场景照跑,在后面报一个看不懂的 HTTP 错误;作者以为自己加了保护,其实没有。四个同族键已兑现(PR #20341,已合并为
5a6267f486),只剩这一个,三种兑现方式在公开契约形状上真实分歧,执行席不代裁。背景:本卡是
qa族 ENFORCE(triage5859588706,按维护者 #18900 判据5727134555)。dev 实测(5861105453,round 15861913911同结论):plugins:os test只经 HTTP 够到目标;没有任何服务端表面列出已加载插件(Discovery 只报 services / capabilities,/.well-known/objectstack两者都不报;kernel.getPlugins()仅进程内);插件拼写(npm 名还是plugin.name)也未定义。params:「environment variables or parameters」没说是谁的环境;runner 的 env 看得见但步骤读不到,服务端的 env 看不见。examples/app-showcase/qa/platform-smoke.test.json不声明requires,它唯一的前置条件(鉴权)写在描述散文里。Governing text:
packages/spec/src/qa/testing.zod.ts:71-74:requires: { params: 'Required environment variables or parameters', plugins: 'Required plugins that must be loaded' }(re-check:git grep -n "requires: z.object" origin/main -- packages/spec/src/qa/testing.zod.ts)。packages/spec/liveness/qa.json:59-63行注:「Enforce route: check requirements before running and report the scenario as skipped-with-reason. Remove route: drop the block, since a precondition that is never checked is worse than an absent one.」Seam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #189005727134555):「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役」。isServiceServeable是单一判定)。plugins→services,C 退役整块);A 扩协议(新增 discovery 字段)。选项 × 真实代价:
plugins对一个新增的 discovery「已加载插件」字段判定;params读跑os test那个进程的环境变量;不满足 ⇒ 跳过并写明原因services取代plugins,对 runner 每次已拉取的 discovery services(enabled 且 available)判定;params同 A;plugins立即退役(tombstone 指向services);不满足 ⇒ 跳过并写明原因,并列出目标宣告了哪些服务requires,处方:"用--tags选择;前置条件写进描述"@EnabledIfEnvironmentVariable、pytestskipif、Odoo 按已装模块限定测试),与本族 ENFORCE 判定相左业务含义直译:A = 在门口挂一张"本店所有设备清单"让测试去对;B = 测试只问"你这儿开不开这项服务",用店里已有的服务牌回答;C = 撤掉"开跑前先检查"这一栏,让作者自己用标签挑。
四轴(业务立场):
--token已处理)。拉动弱 —— 这一轴偏向 C。plugins,多一个services读者);A 最差(新增公开字段 + 披露面)。os-decision-facets
Prior rulings read:
requires,skipped-with-reason,plugins that must be loaded,discovery services→ 0 rulings on this key; ADR-0076 D12; thread:5859588706(triage ENFORCE for the family).推荐:B;回退:C。只看①选 B;②③④ 是否翻转:②④ 偏 C,但只调时序分期不翻字母 —— 否。置信缺口:看不见仓外作者(hotcrm / 客户仓)是否写了
requires.plugins;若有且用的是插件包名,B 的退役处方需给出插件→服务的对照。裁后执行段:回一个字母即可。
requires.pluginstombstone(处方指向requires.services)、新增requires.services对 discovery 判定、params读 runner 环境;core 加 SKIPPED 状态(单独计数、从不计为通过、无失败时退出 0);ADR-0087 D3 条目一条;Clause-②: yes (narrowing),BREAKING 横幅。requirestombstone(处方:--tags选择、前置条件写进描述),ledger 行转 tombstone。四个已兑现的键已随 PR feat(qa): os test prints suite and scenario names and selects scenarios by --tags #20341 落地(
5a6267f486),与本裁定无关。objectstack-fleet commentedon Sep 28, 2026
Ruling: batch #232 item 1 · letter B · maintainer 「同意」 2026-09-28T05:05Z
Director seat (objectstack#12708, summon #30 续 2,
session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意」 to batch #232 as presented — this card item 1, recommendation B, fallback C, the full four-axis body in chat.Ruled: B.
requires.pluginsis retired and replaced byrequires.services, judged against the discovery document the QA HTTP adapter already fetches once per run (services[name].enabledandstatus === 'available', ADR-0076 D12).requires.paramsis judged against the environment of the process runningos test. An unmet requirement makes the scenario SKIPPED with a reason that names the unmet key and, forservices, the services the target declares. A (a new discovery field listing loaded plugins) and C (retire the wholerequiresblock) are not taken.Readings that decided it:
@EnabledIfEnvironmentVariable, pytestskipif, Playwright's conditional skip, Odoo's module-gated tests), so the family criterion ([Decision] Route declared≠enforced work by the SEAM, not the layer — aSeam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900,5727134555) says build the consumer.--token's. Cost did not decide; the criterion did.pluginstombstoned) and one reader; A would add a public field and a disclosure surface.Execution parameters (ruled in the same stroke):
domain:spec, under the cross-domain exception the seat's 裁后执行段 sets out (triage confirms the lane):requires.plugins→retiredKey()tombstone whose prescription namesrequires.services;requires.services(array of service keys) judged against discovery;requires.paramsread from theos testprocess environment;packages/coreTestResultgains a SKIPPED status counted separately and never counted as passed, exit 0 when nothing failed;os testprints the skip reason per scenario. ADR-0087 D3 entry forplugins; the ledger rowqa.scenarios.requiresflips toliveciting the reader.Clause-②: yes (narrowing),minorwith the BREAKING banner (nevermajor).servicesentry skips and lists the declared services; a met one runs; an unmetparamsentry skips naming the variable; an all-skipped run is not a pass, and under--fail-on-emptyit exits 1; apluginskey is refused at parse with the prescription.requires.pluginswith package names, the tombstone prescription gives the plugin → service mapping; none is known.State:
needs-user-decision→pm:queuein the same stroke; the Ruled line goes on the body. The four sibling keys landed via PR #20341 (5a6267f486) and are unaffected.feat(qa): os test prints suite and scenario names and selects scenari…
objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 3
Session:
session_01ARcDurZ5j34RdqsGgc4jgHAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20289-requires-servicesWorktree:
objectstack-issue-20289Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface: the fifth key of the
qa-runnerfamily,requires, executing ruling B (5863822176). It is one PR under the cross-domain exception the ruling sets out, with every end declared:packages/spec/src/qa/testing.zod.ts:requires.plugins→retiredKey()tombstone whose prescription namesrequires.services; addrequires.services(an array of service keys); therequiresdescribes. Plustesting.test.ts, and the regenerated JSON schema, reference docs and authorable-surface artifacts.packages/core/src/qa/runner.ts(+runner.test.ts), andhttp-adapter.tsonly where it hands the discovery document it already fetches to the runner.TestResultgains a SKIPPED status, counted separately and never as passed.packages/cli/src/commands/test.ts(domain:cli, the cross-lane surface), plus its tests: it prints each skip reason, and--fail-on-emptytreats an all-skipped run as empty.requires.plugins, plusregistry.tsand the step-18 lists (generated where a tool generates them).packages/spec/liveness/qa.json:qa.scenarios.requires→live, citing the reader. Plusstate-counts.mdfromgen:liveness-countsand the README row..changeset/:minor, with the BREAKING banner (nevermajor).TestResultmust carry the new status.5878604417on PR feat(spec,core,cli)!: a scenario's requires is checked before it runs — unmet params or services skip it with a reason; requires.plugins retires into requires.services #20511): the dev declared these paths outside the surface above, and the record judged each a necessary consequence of the ruled change, not a widening. This amendment admits exactly them:packages/core/src/qa/adapter.ts: the optionalreadTargetServices?()andTargetServices, the one way the runner receives the discovery map without coupling toHttpTestAdapter;packages/core/src/qa/http-adapter.test.tsand thepackages/cli/test/filesqa-requires-skip-run,qa-requires-summaryandqa-suite-schema-load: the ruling's pins;packages/spec/src/qa/requires-plugins-retirement.test.tsand its line inpackages/spec/vitest.repo-tests.json: the retirement kit's tree-scoped absence pin;content/docs/deployment/cli.mdx,docs/qa/platform-checklist/areas/cli.json(revision 5 → 6) and thepackages/spec/scripts/liveness/check-liveness.mtsheader comment: texts this change falsifies;.changeset/20289-os-test-names-tags.md: one bullet of PR feat(qa): os test prints suite and scenario names and selects scenarios by --tags #20341's pending note, the DELIBERATE CORRECTION the record names and judges (Check Changesetred by design).Nothing else is admitted.
(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: default judgment tier(no path-derived mandate).Clause-②: yesper the ruling (its narrowing arm), so an at-tier contract review is owed before enqueue.Clause-②: yes
Thread-read: 5863822176
Serial constraints cleared: read at 2026-09-28T19:04Z on
origin/mainfc0db22b.packages/spec/src/qa/**,packages/core/src/qa/**,packages/cli/src/commands/test.tsorliveness/qa.json. No live claim on the board names them.5a6267f4), this card's earlier four-key round.registry.tsand the liveness counts are ordinary concurrency with PRs feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458, feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 and fix(spec): reword the CurrencyConfigSchema.precision clause in two major-18 D3 entries #20488, and with this seat's rest: the served OpenAPI document'sinfoblock comes fromapi.documentation(9 keys) #20294: regenerate on merge.area:devpath's other in-flight cards have disjoint file surfaces: [finding]@objectstack/spec's DTS pass hit its 6144 MB heap ceiling in CI (ERR_WORKER_OUT_OF_MEMORY, Test Core 6/6) on a PR whose diff adds nothing to that pass #20419, [finding] Two major-18 D3 entries printCurrencyConfigSchema.precision … unchangedthroughos migrate meta, which PR #20251 makes false in the same major #20379 and [finding]os validateruns only the stack schema parse, so a config that exports a plain object (nodefineStack()call) skips every defineStack cross-field refusal and passes #20367 (seat 2), and this seat's os migrate meta prints tracker numbers to the author: ADR-0087 migration entries' reason / replacement / acceptanceCriteria text carries ~2,060 of them, 178 dead, which AGENTS.md's runtime-string rule forbids #20233 stage 4 (migration entry prose).Ruling carried:
5863822176· batch #232 item 1 · letter B · maintainer 「同意」. The measurement it rests on is seat 2's release5863072096and its decision box5863075827.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHobjectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20289, "status": "done", "branch": "claude/issue-20289-requires-services", "pr": "https://github.com/objectstack-ai/objectstack/pull/20511", "session": "session_01ARcDurZ5j34RdqsGgc4jgH (mode:subagent, the PM's id, stamped on every commit's Claude-Session trailer; identity = the branch named by claim 5876608230, verified as the newest Claim on the card)", "premise_still_valid": true, "summary": "Ruling B executed in one draft PR (#20511, Fixes #20289, head 75d17a11). spec: requires.params is judged against the os test process environment (set and non-empty); requires.services is new, z.array(CoreServiceName), judged against the target's discovery services map (enabled true and status available, ADR-0076 D12); requires.plugins is a retiredKey() tombstone whose prescription names requires.services and carries the plugin to service mapping derived from CORE_SERVICE_PROVIDER. ADR-0087: RETIRED_KEYS_BY_MAJOR[18] qa/TestScenario:requires.plugins plus D3 qa-scenario-requires-plugins-retired, no D2 (a suite is a loose JSON file). core: TestRunner judges requires before the first step (setup included), TestResult gains status passed/failed/skipped and a skipped report (reason, unmet[], availableServices), passed stays and is false on a skip; TestExecutionAdapter gains optional readTargetServices, which HttpTestAdapter answers from the SAME memoised discovery probe (the document is now kept instead of read for routes.data and dropped), so no fetch is added. cli: os test prints each skip with its reason, counts skips apart, and an all-skipped run prints no SUCCESS, exits 0, and exits 1 under --fail-on-empty. Ledger qa.scenarios.requires dead to live (qa 9/0). Mechanism assumptions measured: (1) holds with a precision: the probe exists and is memoised once per adapter (= per run) but was lazy, fired only by record actions; requires.services is a second trigger of that one probe, never a second request (pinned: 4 CLI runs, 4 discovery hits). (2) holds: zero requires reads on main, row dead. (3) --fail-on-empty failed only an empty glob and an empty --tags selection; deselected scenarios never reach the runner and appear only on the --tags line, skipped ones were selected and refused by their own precondition, printed with a reason and counted on the summary. (4) non-strict object, retiredKey route; nested key spelled defKey:requires.plugins like api/RestApiConfig:documentation.enabled.", "tests": "All at code head 75d17a11 (the docs-only regeneration commit came after the targeted pass on the same tree). spec test (local): Test Files 572 passed, Tests 16797 passed and 1 todo. spec test:repo: 39 files, 694 passed (includes the new tree-scoped pin requires-plugins-retirement.test.ts). core test: 59 files, 1555 passed; src/qa alone 69 passed. cli os test files: unit tier (qa-suite-schema-load, qa-requires-summary, qa-tags-selection, vitest-tiers-partition) 4 files 46 passed; integration tier (qa-requires-skip-run, qa-names-and-tags-run; they spawn bin/run-dev.js against a node:http stub, run locally because this diff adds a spawn file) 2 files 15 passed; the rest of the cli suite declared to CI; qa-empty-glob-exit-code.e2e.test.ts was named and matched no project here (nightly e2e tier) so NOT MEASURED, reason: not a member of the unit or integration project. typecheck exit 0 for spec, core and cli, each with check:test-typecheck OK. Type-channel reverse check: the ts-expect-error on an authored requires.plugins in spec testing.test.ts is consumed, and core/cli tests typecheck requires.services, a key only the rebuilt d.ts carries. check:generated: first ✗ 1 of 15 stale (check:docs), --fix regenerated content/docs/references/qa/testing.mdx, recheck ✓ All 15 generated artifacts are up to date. check:liveness: qa 9 classified (live 9), state-counts.md is current. Ablation via scripts/ablation-replace.mjs on the committed tree: runner.ts anchor 'if (unmet.length === 0) return undefined;' flipped to a greater-or-equal-zero comparison, anchor 1 to 0, blob 77e3f4c5a76c to 5df21094f9fd; runner.test.ts 7 failed / 23 passed of 30, exactly the seven skip-asserting pins, with the met-service CONTROL, the no-probe pin, the failed-status pin and the 20 pre-existing tests green. Expected direction red, observed red. Restore: blob 77e3f4c5a76c == HEAD, git diff HEAD empty. No dist leg (relative src import). Lint narrowing, measured: the 25 changed paths asked of eslint.config.mjs: 16 linted, 9 ignored (md/mdx/json); eslint --no-inline-config --format json over the 16: 16 results, 0 errors, 0 warnings; parserOptions.project and projectService null for all 16, so untouched verdicts cannot move. Not measured: a booted showcase; the skip path was driven end to end against a stub answering discovery like both producers.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 75d17a11 derived 122 families; all 122 ran, each exit code written to disk; --ran: '122 derived, 122 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). 120 exit 0. First pass: 4 exited 3 PREREQUISITE NOT MET (check:skill-examples, check:dual-build-cjs-loads, check:i18n-coverage, check:type-check-debt); after building the named prerequisites (68 turbo tasks, 67 cached; plus a direct core build because the ablation restore had moved runner.ts's mtime past core's d.ts) all 4 exited 0, type-check-debt: '--re-measure: OK — 4 ledger entr(ies) re-measured, none above its recorded number'. 2 exit 1, neither caused by this diff: node scripts/check-empty-changeset.mjs --base origin/main (the DELIBERATE CORRECTION of the pending note .changeset/20289-os-test-names-tags.md, expected red, runs only in the non-required Check Changeset job; confirmation requested in the PR body); pnpm check:platform-checklist (areas/identity-auth.json ABSENT SYMBOL packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor; the same single problem at base fc0db22b and at main 2b24b8b8, control runs in a detached worktree, removed after; this diff touches neither file). CI read once after the PR: 32 check-runs on 75d17a11, 10 success, 2 skipped, 20 in_progress, 0 failed; in_progress is the honest value, convergence belongs to the PM.", "line_budget": "n/a: no skills/** and no governed surface in the diff", "files_changed": [ ".changeset/20289-os-test-names-tags.md (pending note of PR #20341, one bullet corrected: DELIBERATE CORRECTION)", ".changeset/20289-requires-services-skip.md", "content/docs/deployment/cli.mdx (text this PR falsified)", "content/docs/references/qa/testing.mdx (generated by check:generated --fix)", "docs/qa/platform-checklist/areas/cli.json (text this PR falsified; revision 5 to 6)", "packages/cli/src/commands/test.ts", "packages/cli/test/qa-requires-skip-run.test.ts", "packages/cli/test/qa-requires-summary.test.ts", "packages/cli/test/qa-suite-schema-load.test.ts", "packages/core/src/qa/adapter.ts (optional readTargetServices on the adapter interface)", "packages/core/src/qa/http-adapter.test.ts", "packages/core/src/qa/http-adapter.ts", "packages/core/src/qa/runner.test.ts", "packages/core/src/qa/runner.ts", "packages/spec/liveness/README.md", "packages/spec/liveness/qa.json", "packages/spec/liveness/state-counts.md (generated by gen:liveness-counts)", "packages/spec/scripts/liveness/check-liveness.mts (header comment only)", "packages/spec/src/migrations/entries/retired-keys/18.qa__TestScenario__requires.plugins.ts", "packages/spec/src/migrations/entries/semantic/18.qa-scenario-requires-plugins-retired.ts", "packages/spec/src/migrations/registry.ts (generated by gen:migration-registry)", "packages/spec/src/qa/requires-plugins-retirement.test.ts", "packages/spec/src/qa/testing.test.ts", "packages/spec/src/qa/testing.zod.ts", "packages/spec/vitest.repo-tests.json" ], "deviations": [ "File surface beyond claim 5876608230, each needed or made-false-by-this-change: packages/core/src/qa/adapter.ts (the interface between adapter and runner; the optional method is how the runner receives the discovery services); packages/core/src/qa/http-adapter.test.ts; three cli test files (qa-requires-skip-run new, qa-requires-summary new, qa-suite-schema-load extended); packages/spec/src/qa/requires-plugins-retirement.test.ts plus its line in packages/spec/vitest.repo-tests.json (the kit's tree-scoped absence pin, inside the radius @objectstack/spec already declares); three texts this PR falsifies, the class the #20341 REWORK named: content/docs/deployment/cli.mdx, docs/qa/platform-checklist/areas/cli.json, the check-liveness.mts header comment.", "The pending release note .changeset/20289-os-test-names-tags.md (PR #20341, unreleased) had one bullet saying requires is still checked by nothing; it would ship false beside this PR's note in the same release, so the bullet is rewritten. This is the DELIBERATE CORRECTION class check-empty-changeset.mjs names: that gate is red on purpose, skip-changeset is not applied, and the PR body asks for a person's confirmation. If the release consumes that note before this lands, the edit must be dropped at merge.", "requires.services is closed over CoreServiceName rather than an open string array. The ruling says 'array of service keys'; both discovery producers key services by CoreServiceName members only, so the closed enum loses nothing and turns a misspelling into a parse refusal (contract tightening) while a valid key the target does not declare still skips loudly. Named in the PR's Acceptance notes; a one-line change to z.array(z.string()) if the seat reads the ruling as open.", "The ADR-0087 marker is written, as the gate reads it, in the changeset .changeset/20289-requires-services-skip.md; the PR body names the disposition in words (registered qa-scenario-requires-plugins-retired) because the marker's comment form is sanitizer-poison on GitHub.", "Container restart around 19:43Z: the first check:generated --fix never acquired the lock (the log shows it waiting), so nothing was regenerated then. After the restart it ran in a fresh slot and the recheck read '✓ All 15 generated artifacts are up to date'; the pushed head c8f50d88 was pushed after the restart, then 75d17a11 with the regenerated reference page.", "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude); the harness reminder asked for a model-named Co-Authored-By line, which the pre-push hook refuses and AGENTS.md outranks.", "origin/main (now 2b24b8b8, 6 commits past base) was not merged in: a driver-free merge-tree onto it is clean and the one shared file, migrations/registry.ts, is disjoint prose on main's side. CI validates the merge ref.", "Verify-lock holds (shared-box seconds): cli closure build 269s; the locked targeted pass with --fix 375s; typechecks plus full spec/core suites 971s (16m11s); ablation 2s; prerequisite build 4s; core rebuild 8s. The first build and the first --fix attempt returned exit 99 (queue timeout) behind 9-10 minute holders and were re-queued with the kept slot.", "Cleanup: node_modules is removed after this comment posts and the worktree is then removed with git worktree remove (never --force); the control worktree objectstack-issue-20289-base and the fetched ref refs/os-dev/issue-20289/main are already removed." ], "mcp_calls": "0", "api_writes": "3 REST writes, all through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, 204, then the relay run): (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, draft, run 36481324626, PR #20511, body read back byte-identical at 13429 bytes; (2) label-write assign, POST /repos/objectstack-ai/objectstack/issues/20511/assignees os-warren, run 36481407619, read back MATCHES (no label added; the four labels on the PR were written by others); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20289/comments via post-stamped. Also 5 git pushes to the branch (the empty-branch probe and 4 commit pushes), which are not REST writes.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: named producer — docs/qa/platform-checklist/areas/identity-auth.json:1599 cites packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor, which that file no longer declares (last touched by 7d630889, #20429); `pnpm check:platform-checklist` exits 1 with 'ABSENT SYMBOL' on main 2b24b8b8 and on fc0db22b, so every PR's run of that gate inherits the red · evidence: control runs of node scripts/check-platform-checklist.mjs in a detached worktree at both commits, '1 problem(s)', the same line · dedupe words: platform-checklist ABSENT SYMBOL twoFactor, identity-auth.json auth-plugin.ts, check:platform-checklist red main", "carrier: none (承接者:无) · noted, not filed: a suite whose scenarios array is empty, run without --tags, still prints 'SUCCESS: All 0 scenarios passed.' and exits 0 even under --fail-on-empty; the all-skipped posture this PR adds deliberately does not widen to it. In the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT · PR #20511 at head
75d17a115ee806c21e916caf4c3c3cbce7e32e56· 2026-09-28T21:16Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim5876608230, as amended in place after the fact (its "Amended" line). This seat took the readings below on GitHub or on the tree.main. Body line 1Fixes #20289, andClause-②: yes (narrowing)stands alone.5863822176) end to end.requires.pluginsis tombstoned with the plugin → service mapping.requires.servicesis judged against the discovery document the QA HTTP adapter already fetches (one memoised probe, no new request, field or route).requires.paramsis judged against theos testprocess environment.TestResultgainsstatusandskipped.os testprints skips and gets the all-skipped exit posture. Plus the ADR-0087 D3 entry, the ledger row →live, and threeminorchangesets with the BREAKING banner. The paths outside the original claim are admitted by the amendment, each judged necessary by the record.5878604417, same head. Every execution parameter and all five ruled pins are present and load-bearing.requires.servicesclosed overCoreServiceNameis inside the ruling: both discovery producers write exactly the 16CoreServiceNamekeys, so an open array would only turn a misspelling into a permanent skip.TestResultis additive for every in-repo reader. The ablation reading matches (7 of 30 red, exactly the skip pins).Check Changesetis red on purpose: the one-bullet rewrite of PR #20341's pending note.changeset/20289-os-test-names-tags.md, which would otherwise ship false beside this PR's note in the same release. The record names the note, quotes old and new, and judges the new bullet true. The gate's source prescribes "do not restore it" and stays red; the job runs onpull_requestonly (nomerge_group); the record on the PR names the gate and cause. All three conditions for landing a red of this class hold. Its skipped steps (ADR-0087 disposition, no-major) were run by the dev at this head, exit 0, and the record judged them from the gates' sources.check-expected-skips.mjs --pr 20511, exit 0), 1 failure = the deliberateCheck Changeset.git merge-treeagainstorigin/main9449512ais clean. Since the base,mainhas not movedstate-counts.md, the liveness README, the reference page,spec-changes.jsonor the upgrade guide, so rule A owes no sync.check-governed-merges.mjs --pr 20511: 0 of 25 paths), 1606 changed lines.Out-of-scope findings:
check:platform-checklistABSENT SYMBOL (identity-auth.json→auth-plugin.ts#twoFactor, red onmainsince fix(plugin-auth): a refused auth setting no longer drops the settings saved with it #20429) → carrier: [finding]check:platform-checklistis red onmain:identity-auth.jsonanchorsauth-plugin.ts#twoFactor, which #20429 turned into an inline nested key #20464 (open,domain:devx).scenarios: []suite still printsSUCCESS: All 0 scenarios passed.and exits 0 under--fail-on-empty. It is pre-existing, the flag's help names exactly the cases it fails, and no text is false → Acceptance notes, carrier none; thecli.qa-suite-executionknown gap is where it goes if it is ever filed.Landing order: this PR lands before #20294's PR #20512. Both edit
packages/spec/liveness/state-counts.md(anos-regenpath), so #20512 takes a sync-and-regenerate round after this merges.Landing next: ready + auto-merge through the relay. At MERGED, the
Fixesline closes this card.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHobjectstack-fleet commentedon Sep 28, 2026
Landed: PR #20511 →
main0bbe4005e82fce2058238720cac7ba5cd2f182d5· 2026-09-28T21:49Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim5876608230(as amended). ACCEPT5878795654on the at-tier PASS5878604417. Merged through the merge queue at 21:48Z, with the deliberateCheck Changesetred of the accepted class.Verified on
origin/main:0bbe4005has one parent,9e9bb464, and is an ancestor oforigin/main.z.array(CoreServiceName)inpackages/spec/src/qa/testing.zod.ts0 → 1;judgeRequirementsinpackages/core/src/qa/runner.ts0 → 2. The corrected bullet ("checked in this same release") is in.changeset/20289-os-test-names-tags.md.Close-out: the PR's
Fixesline closed this cardcompleted.pm:dispatchedcomes off in this act, and the assignee stays as the record of who landed it. Theqa-runnerfamily is now whole: the four sibling keys (PR #20341) plusrequireshere. Theqaledger reads 9 live / 0 dead.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHfeat(spec,core,cli)!: a scenario's requires is checked before it runs…