Skip to content

qa: os test prints suite and scenario names, filters by tags, and skips on unmet requires (5 keys) #20289

Description

@objectstack-fleet

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/spec parses 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-tags on 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 / skipif for 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:

四轴:

  • 实际业务需求: 测试报告显示可读名称、按标签只跑回归集,是 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

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: the road — verify | 缺项 (no item runs os test with tags or an unmet requires) | P3

    Triage: first grade — enhancement · priority:p3 · domain:spec · area:devpath · pm:queue. Verdict: ENFORCE, by the maintainer's criterion

    Triage: the readers land in packages/core/src/qa/runner.ts (scenario names in TestResult, and unmet requires becomes skipped-with-reason) and packages/cli/src/commands/test.ts (a --tags flag, and names printed) ⇒ domain:spec parent, with a domain:cli sub-issue. Rationale: tags promises filtering that does not exist, and requires.plugins reads as a guard but never skips. Misleading, with no measured author ⇒ p3.

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T20:33Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), the criterion on #18900 (5727134555), and the #20273 / #20274 / #20282 grades that applied it this week.

    Verdict. Named suites, tag selection and precondition skips are mainstream in platform test tools and in the frameworks this audience uses: ServiceNow ATF, Power Apps Test Studio, Odoo --test-tags, Playwright --grep, and pytest skipif. By the criterion ⇒ ENFORCE, 「补消费端(一次做对)」. It is not a decision-box round-trip. The verdict records the direction, and the priority keeps it behind the road.

    Execution notes.

    1. os test prints suite and scenario names, filters by --tags, and reports an unmet requires as skipped with its reason, ⛔ never as passed.
    2. Pin each: a tag filter, a skip with a reason, and the unfiltered run (the control).
    3. Each ledger row flips to live.
  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01QcAS3qiYYZNezaxZxaUdMV
    Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20289-os-test-names-tags-requires
    Worktree: objectstack-issue-20289
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface: dispatched vertically (ruling A′ ②), so both ends are declared.

    • packages/core/src/qa/runner.ts: scenario and suite names in TestResult, and an unmet requires becomes skipped-with-reason. Plus runner.test.ts.
    • packages/cli/src/commands/test.ts (domain:cli): a --tags filter, and suite and scenario names printed. Plus its tests.
    • The packages/spec/liveness/** rows for the 5 keys, flipped to live and citing their readers.
    • packages/spec/src/qa/testing.zod.ts: describe text only, if a describe promises more than the reader does.
    • Generated projections, regenerated only.
    • .changeset/.

    The domain:cli half rides in this PR rather than as a filed sub-issue: one reader, one flight. Stop on a breach and explain it in the report.
    Container & model: M, mode:subagent, model: default judgment tier (dispatch-gates --tier: no path-derived mandate; runtime readers for declared keys)
    Clause-②: no
    Thread-read: 5859588706
    Serial constraints cleared: read at 2026-09-27T22:09Z on origin/main 10ea9eb2ed.

    • Open-PR census (15 open PRs): none touches packages/core/src/qa or packages/cli/src/commands/test.ts. The Version Packages PR touches only packages/qa/* changelogs and manifests.
    • In-flight claims: none names these files.

    Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 Direction: triage 5859588706, ENFORCE, execution notes 1–3. An unmet requires is reported as skipped with its reason, ⛔ never as passed.

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20289,
      "status": "needs_decision",
      "branch": "claude/issue-20289-os-test-names-tags-requires",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20341",
      "session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM's id, stamped on every commit's Claude-Session trailer; identity = the branch)",
      "premise_still_valid": true,
      "summary": "Four of the family's five keys are now live. The fifth, requires, goes to the maintainer. Draft PR #20341 (Part of #20289) makes these changes. (1) core QA.TestResult carries scenarioName and description on both envelopes, and suiteName on every runSuite result. (2) os test heads each suite 'NAME (FILE)' and prints each scenario 'NAME [ID]'. It prints a failed scenario's description. (3) A new --tags flag selects scenarios: comma list, any-of, exact, case-sensitive. Deselected scenarios are counted and never passed. A tag nothing carries is named. An empty entry is refused at parse time. A no-match selection exits 0, or 1 under --fail-on-empty, which is the repo's empty-run posture from Test.description and cli.mdx. (4) Ledger rows qa.name, scenarios.name, scenarios.description and scenarios.tags are live, citing file#symbol readers, and tags also cites a producer. state-counts qa went from 4/5 to 8/1, and the README row was rewritten. The requires describe() now says NOT CHECKED. requires stays dead because neither sub-key has an honest judge. plugins: os test reaches the target over HTTP, and no server surface lists loaded plugins. Discovery advertises services and capabilities, and the plugin spelling is undefined. params: whose environment is undefined. The runner's env is observable, but no step can consume it; the server's is not observable. No speculative SKIPPED machinery was built. The before/after was measured on a stub target. Base: heading 'probe.test.json', lines printed the id only, --tags exited 2 with 'Nonexistent flag: --tags', and an unmet-requires scenario reported PASSED like its control. Branch: names printed, and --tags smoke ran 1 of 4.",
      "tests": "Every run is at HEAD dd6b472c63, the final commit. core: pnpm --filter @objectstack/core test, 56 files and 1483 tests passed. runner.test.ts has 20 tests, 3 of them new. core typecheck exit 0, and check:test-typecheck OK. cli: vitest --project unit first pass, 227 of 230 files passed. Two files needed the cli dist, which was absent. hook-timeout-override-refusal.test.ts timed out at 5000ms under an 861s shared-box run. After the cli build, those 3 files re-ran and passed 33 of 33. New unit pin qa-tags-selection.test.ts, 13 of 13, includes the unfiltered CONTROL. New integration pin qa-names-and-tags-run.test.ts spawns bin/run-dev.js against a node:http stub, 8 of 8. It covers the names printed, --tags smoke exiting 0 because the failing scenario never ran, the unfiltered control exiting 1, no-match exiting 0 or 1 under --fail-on-empty, and malformed refused. The integration layer ran locally because this diff adds a spawn file. cli typecheck exit 0. spec: vitest src/qa, 26 passed. check:generated had check:docs stale, fixed with --fix and clean on re-check. check:liveness green with 'qa 9 classified (live 8, dead 1)'. Ablation, one leg, scripts/ablation-replace.mjs WRAP mode: the return line of selectScenariosByTags' any-of predicate was replaced by 'return true;'. The anchor count went from 1 to 0, and the blob from 774d2436cf4f to cab531acb335. Unit went 3 red of 13 (any-of, exact, no-match) with the CONTROL green. Integration went 3 red of 8 (--tags smoke, regression, no-match) with the unfiltered control and names green. The expected direction held: red. Restore: blob after equals HEAD 774d2436cf4f, and git diff HEAD is empty. The subject resolves through a relative src import, so no dist leg applies. Lint narrowing, measured: eslint --no-inline-config --format json over the 6 changed TS files gave 6 results, 0 errors and 0 warnings. isPathIgnored is false for all 6, and the 5 JSON, MD and MDX files are ignored by the config. No type-aware linting: parserOptions.project and projectService are null for all 6, so untouched verdicts cannot move.",
      "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands at dd6b472c63 derived 114, and all 114 ran with final exit 0. On the first pass, 6 exited 3 with PREREQUISITE NOT MET: check:skill-examples, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity and check:type-check-debt. The first type-check-debt pass died in its own internal build: plugin-pinyin-search missed the objectql d.ts while a prerequisite build rewrote dist. All 6 exited 0 on re-run after their prerequisites were built, and type-check-debt reported '--re-measure: OK, none above its recorded number'. --ran reconciliation, with every line 'COMMAND :: exit CODE', gave '114 derived, 114 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero).",
      "line_budget": "n/a: no skills/** and no governed surface in the diff",
      "files_changed": [
        ".changeset/20289-os-test-names-tags.md",
        "content/docs/references/qa/testing.mdx (generated by check:generated --fix)",
        "packages/cli/src/commands/test.ts",
        "packages/cli/test/qa-names-and-tags-run.test.ts",
        "packages/cli/test/qa-tags-selection.test.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/src/qa/testing.zod.ts (describe text only)"
      ],
      "deviations": [
        "The PR's first line is 'Part of #20289', not the dispatch's 'Fixes #20289'. The contract rule wins: never write Fixes on a card still in the decision box. requires is open, so the card must stay open.",
        "@objectstack/core is minor, not patch. The dispatch said minor for the new flag and patch otherwise, but TestResult gains 3 public fields, which is an additive API. cli is minor and spec is patch. All three are in one changesets fixed group, so the release level is minor either way.",
        "Two of the four named pins are not delivered: 'skip with its reason' and the requires half. They wait on the decision below.",
        "origin/main was not pulled before opening the PR, which AGENTS section 10 asks for. main is 4 commits ahead, and git diff --name-only against a ref fetched for this purpose shows none of them touch this diff's files. CI validates the merge ref.",
        "content/docs/deployment/cli.mdx and docs/qa/platform-checklist/areas/cli.json were not edited: they are outside the claim's file surface. See out_of_scope_findings.",
        "A malformed --tags exits 1 through oclif's flag-parse error. bin's invocation-error line does not classify it, so it does not get exit 2 like 'Nonexistent flag'. Exit 1 matches this command's own process.exit(1) convention and os diff's usage-error convention.",
        "The verify lock was held long: session A (spec build, check:generated x3, ablation) for 16m35s, session B (full core and cli tests and typechecks) for 16m57s, and session C (prerequisite builds and a 3-file re-run) for 13m37s. These are shared-box seconds.",
        "Cleanup: node_modules was removed before this report posted. The worktree itself (git worktree remove, never --force) is removed right after the comment lands, because the post-stamped tool runs from it."
      ],
      "mcp_calls": "0",
      "api_writes": "3 REST writes, each 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 36360895587, body read back byte-identical, 10912 bytes; (2) label-write, POST /repos/objectstack-ai/objectstack/issues/20341/assignees os-project-manager, run 36360935834, read back MATCHES, with no label added; (3) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20289/comments via post-stamped. There were also 6 git pushes to the branch, which are not REST writes: the empty-branch probe and 5 commits.",
      "open_questions": [
        {
          "question": "How should TestScenario.requires (params, plugins) be honoured? What does 'met' mean for each sub-key, or should the block be retired? The family verdict is ENFORCE, but neither sub-key has an honest judge from os test today. plugins: the runner reaches the target only over HTTP. No server surface lists loaded plugins: DiscoverySchema advertises services and capabilities, and /.well-known/objectstack advertises neither, while kernel.getPlugins() is in-process only. The plugin spelling (npm name or plugin.name) is also undefined. params: 'environment variables or parameters' names no process. The runner's env is observable but unconsumable, because interpolation reads only captured context. The server's env is unobservable. Measured producers: zero. The repo's one suite, examples/app-showcase/qa/platform-smoke.test.json, declares no requires. Its only precondition is auth, written as description prose: 'Requires --token'.",
          "options": [
            "A: Enforce plugins against a NEW discovery field listing loaded plugins, which needs a DiscoverySchema field, a runtime producer from the kernel, and REST/dispatcher parity. Enforce params as env vars set in the process running os test, the JUnit @EnabledIfEnvironmentVariable / pytest skipif reading. An unmet requirement becomes SKIPPED with its reason, counted separately, never passed, and a failure-free run still exits 0. Costs: a new public discovery field; a disclosure call, because discovery is served unauthenticated on many hosts and would publish the stack's plugin inventory; a plugin-name spelling ruling; and a SKIPPED status in core and the CLI. Four axes: (1) business need: speculative, with zero producers, and the one real precondition is auth, which A does not cover. (2) Long-term: adds a public surface. (3) Anti-AI-error: plugin spellings invite guesses, so it needs a closed vocabulary to be safe. (4) Startup focus: the widest expansion.",
            "B: Replace plugins with services, judged against the discovery services map the runner already probes once per run (enabled true and status 'available'). Keep params as runner env, the same reading as A. Retire plugins immediately with a retiredKey prescription pointing to services: zero producers were measured, so there is no staged window. An unmet requirement becomes SKIPPED with its reason, naming what the target advertises. Costs: a spec narrowing (Clause-② yes (narrowing), a BREAKING banner and an ADR-0087 disposition) and the same SKIPPED status. There is no new server surface. Four axes: (1) business need: answers the question authors actually ask, 'is the AI/search surface served here?', from a contract that already exists. (2) Long-term: reuses ADR-0076 D12, 'advertise only what is mounted'. (3) Anti-AI-error: the vocabulary is the server's own service keys, and a misspelling skips with the advertised keys named instead of passing. (4) Startup focus: one reader and no new surface.",
            "C: Retire requires whole: a retiredKey tombstone on TestScenarioSchema, with the prescription 'select with --tags; state preconditions in description'. The ledger rows become tombstones. Costs: the smallest diff, but it drops a capability the mainstream has (JUnit @EnabledIfEnvironmentVariable, pytest skipif, Odoo tests scoped to installed modules). That contradicts the family's ENFORCE verdict for this key under the maintainer's criterion, which asks whether the mainstream has it, not whether anyone in the repo reads it. Four axes: (1) business need: nothing is lost today. (2) Long-term: no half-protocol. (3) Anti-AI-error: the strictest option, since the tombstone refuses the key loudly. (4) Startup focus: the best fit for a zero-pull declared surface."
          ],
          "recommendation": "B. It keeps the maintainer's criterion: the mainstream has declared preconditions, so build the consumer once. Every judgement it makes is against something the runner can observe today: discovery services, which it already fetches, and its own process env. It adds no server surface, which is A's cost. It removes the one sub-key that cannot be judged honestly rather than keeping it. The trade-off, stated plainly: C wins the anti-AI-error and startup-focus axes outright. If the maintainer weighs zero measured producers above the mainstream criterion, C is the call. Either way, the SKIPPED-with-reason leg lands only after this ruling."
        }
      ],
      "out_of_scope_findings": [
        "carrier: the PM seat (a claim amendment, or the requires follow-up PR). content/docs/deployment/cli.mdx, section os test, documents neither --tags nor the new report lines. --help does. This is an omission, not a false statement. Noted, not filed.",
        "carrier: the PM seat or the next checklist-author sweep. In docs/qa/platform-checklist/areas/cli.json, item cli os test, a knownGap reads 'no scenario SELECTION exists ... os test has exactly three flags'. After PR #20341 that is false for tags and still true for requires. Noted, not filed.",
        "carrier: whoever lands the requires decision, who touches the same ledger. The qa paragraph of the header comment in packages/spec/scripts/liveness/check-liveness.mts lists suite.name and scenario.tags as unread. That was true at seeding and is stale now. Noted, not filed."
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK round 1 (texts this PR falsifies) — PR #20341 at head dd6b472c63 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-28T00:12Z

    Seat review against GitHub and the dev's report 5861105453:

    • Delivered so far: four of the family's five keys are live.
      • TestResult carries scenario and suite names, and os test prints them.
      • --tags selects scenarios (any-of, exact). Deselected scenarios are counted and never passed. A no-match run exits 0, or 1 under --fail-on-empty.
      • The ledger rows qa.name, scenarios.name, scenarios.description and scenarios.tags are live.
      • requires's describe now says NOT CHECKED, and its row stays dead.
      • The ablation went red in both pins, then was restored. Part of #20289 is correct: the card cannot close while requires is undecided.
    • Why a round (说明书脱节, a no-escalation class): landing this PR as it stands makes three texts false or stale. The PR that changes the truth corrects its text in the same change.
      1. docs/qa/platform-checklist/areas/cli.json:456 (knownGap on the os test item): "no scenario SELECTION exists: os test has exactly three flags …". This becomes false. Rewrite it to what stays true: --tags selects, and requires is not honoured (NOT CHECKED, pending the decision on this card).
      2. content/docs/deployment/cli.mdx, the os test section: document --tags (any-of, exact, how deselected scenarios are counted, no-match and --fail-on-empty) and the new report lines.
      3. packages/spec/scripts/liveness/check-liveness.mts:368 (the header comment): it names suite.name and scenario.tags as unread. Only scenario.requires stays unread.
    • File-surface amendment to claim 5860293954 (recorded here, ⛔ not a second claim): those three files, text only.
    • The requires question: it is a real contract-shape divergence (A: a new discovery field; B: services replaces plugins; C: retire). The execution seat does not rule it. After this PR lands, the card moves pm:dispatched → needs-user-decision, with the four-axis analysis and the dev's recommendation B. The execution half waits on the maintainer's word.
    • Round 1 of the 2-round cap.
  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20289,
      "status": "needs_decision",
      "branch": "claude/issue-20289-os-test-names-tags-requires",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20341",
      "session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM's id, stamped on every commit's Claude-Session trailer; identity = the branch)",
      "premise_still_valid": true,
      "summary": "REWORK round 1 is done, per the seat's order 5861125164. The three texts that PR #20341 falsified are corrected in 5d31b4d587, a new commit on top of dd6b472c63 with no rebase, amend or force-push. This round is text only, with no code and no requires work. (1) docs/qa/platform-checklist/areas/cli.json, item cli.qa-suite-execution: the knownGap now says what stays true. --tags selects scenarios (any-of, exact, case-sensitive). Deselected scenarios are counted and never passed. requires is declared but NOT CHECKED, pending the maintainer's decision on #20289. The item's qa.json#tags source note said the same false thing and was corrected in the same edit. revision went from 4 to 5 with a history entry. (2) content/docs/deployment/cli.mdx, os test section: documents --tags (comma list, any-of, exact, case-sensitive; deselected counted and never passed; an unknown tag named; an empty entry refused; no-match exits 0, or 1 under --fail-on-empty), the report lines (suite 'NAME (FILE)', scenario 'NAME [ID]', a failed scenario's description), and that requires is not checked. It agrees with os test --help, read at this head. (3) packages/spec/scripts/liveness/check-liveness.mts, the qa paragraph of the header comment: suite.name and scenario.tags now have readers, and scenario.requires is the one key still unread. The PR body gained '## Rework round 1' naming 5861125164, directly under the 'Part of #20289' and Clause-② header lines so both stay the body's opening lines. The patch read back byte-identical. The round-0 delivery is unchanged: four of the family's five keys are live, and requires stays with the maintainer (open_questions, unchanged).",
      "tests": "Round 1, at HEAD 5d31b4d587. This round edited a checker script (a comment only), so its own suite ran: the 8 files that name check-liveness, 6 of them beside it in scripts/liveness plus zod-graph.test.ts and metadata-form-zod-reconciliation.test.ts. 8 files, 262 tests passed. check:platform-checklist: 'OK — 15 areas, 266 items (264 active, 0 planned)'. The --help agreement was read from bin/run-dev.js test --help at this head. No code changed, so no package test or typecheck is newly owed. Round 0 is unchanged, at dd6b472c63. core test: 56 files and 1483 tests. core and cli typecheck: exit 0. cli unit: 227 of 230 files on the first pass, and the 3 remaining files 33 of 33 after the build. New pins: qa-tags-selection 13 of 13 and qa-names-and-tags-run 8 of 8. spec src/qa: 26. check:liveness: 'qa 9 classified (live 8, dead 1)'. Ablation on the any-of predicate turned 3 of 13 unit and 3 of 8 integration red, with the controls green, and the restore was proven (blob == HEAD, git diff HEAD empty). The eslint narrowing measured 6 files, 0 errors, and no type-aware linting.",
      "gates": "Round 1, at 5d31b4d587. dispatch-gates --repo objectstack-ai/objectstack --commands was re-derived for the widened diff: 118 families, the 114 of round 0 plus check:cli-examples-parity, check:pm-dispatch-gates, check:pm-governed-merges and bare-root-worklist.mjs --self-test. The recreated worktree had no dist, so the prerequisites were rebuilt under the lock: 68 turbo tasks, exit 0, lock held 21m05s. That is a shared-box figure. The first pass overlapped that rebuild. Twelve families that read built output answered exit 3, and check:dts-closure answered exit 1 with 'declarations missing'. A clean second pass after the rebuild ran every family except check:pm-dispatch-gates: 117 of 117 exited 0, including api-surface, dts-closure, dual-build-cjs-loads, the i18n trio, skill-examples, cli-examples-parity and type-check-debt. check:pm-dispatch-gates reads no built output and exited 0 on the first pass after 1270s. --ran reconciliation, with every line 'COMMAND :: exit CODE', gave '118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero).",
      "line_budget": "n/a: no skills/** and no governed surface in the diff",
      "files_changed": [
        ".changeset/20289-os-test-names-tags.md",
        "content/docs/references/qa/testing.mdx (generated by check:generated --fix)",
        "packages/cli/src/commands/test.ts",
        "packages/cli/test/qa-names-and-tags-run.test.ts",
        "packages/cli/test/qa-tags-selection.test.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/src/qa/testing.zod.ts (describe text only)",
        "content/docs/deployment/cli.mdx (round 1, text)",
        "docs/qa/platform-checklist/areas/cli.json (round 1, text; revision 4 to 5)",
        "packages/spec/scripts/liveness/check-liveness.mts (round 1, comment only)"
      ],
      "deviations": [
        "The order named the knownGap at cli.json:456. The same item's source note, 'packages/spec/liveness/qa.json#tags (… its dead tags/requires rows are why no scenario selection exists)', was false for the same reason, so it was corrected in the same edit. It is in the same file and item, so it stays inside the amended surface.",
        "The '## Rework round 1' section sits under the 'Part of #20289' and 'Clause-②: no' lines rather than above them, so the closing-keyword guard and Check Changeset keep reading the same opening lines. The same single issue_patch also replaced the round-0 Acceptance-notes bullet ('outside the claimed file surface, not edited'), which this round made false.",
        "The first gate pass overlapped the prerequisite rebuild, because the unlocked gates ran beside a locked build. Every family that could read dist was therefore re-run in a clean second pass. Only check:pm-dispatch-gates, which reads no built output, keeps its first-pass reading.",
        "Carried from round 0: the PR opens with 'Part of #20289', not 'Fixes'; core is minor rather than patch because TestResult gains 3 public fields, and all three packages share one changesets fixed group; the skip-with-reason pin and requires wait on the decision; origin/main is not merged in, and main's newer commits do not touch this diff's files.",
        "The verify lock was held long: round 1 held it for 21m05s, 68 turbo tasks rebuilt from scratch because the worktree was re-created. Round 0 sessions held it for 16m35s, 16m57s and 13m37s.",
        "Cleanup: node_modules was removed before this report posted. The worktree (git worktree remove, never --force) is removed right after the comment lands, because post-stamped runs from it."
      ],
      "mcp_calls": "0",
      "api_writes": "Round 1: 2 REST writes through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, 204, then the relay run). (1) issue_patch, PATCH /repos/objectstack-ai/objectstack/issues/20341 body, run 36367615540, read back byte-identical at 13549 bytes. (2) This os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20289/comments via post-stamped. There was also 1 git push, 5d31b4d587, which is not a REST write. Round 0 had 3 relay writes: pr_create run 36360895587, the assignees label-write run 36360935834, and the os-dev-report comment 5861105453. It also had 6 git pushes.",
      "open_questions": [
        {
          "question": "How should TestScenario.requires (params, plugins) be honoured? What does 'met' mean for each sub-key, or should the block be retired? The family verdict is ENFORCE, but neither sub-key has an honest judge from os test today. plugins: the runner reaches the target only over HTTP. No server surface lists loaded plugins: DiscoverySchema advertises services and capabilities, and /.well-known/objectstack advertises neither, while kernel.getPlugins() is in-process only. The plugin spelling (npm name or plugin.name) is also undefined. params: 'environment variables or parameters' names no process. The runner's env is observable but unconsumable, because interpolation reads only captured context. The server's env is unobservable. Measured producers: zero. The repo's one suite, examples/app-showcase/qa/platform-smoke.test.json, declares no requires. Its only precondition is auth, written as description prose: 'Requires --token'.",
          "options": [
            "A: Enforce plugins against a NEW discovery field listing loaded plugins, which needs a DiscoverySchema field, a runtime producer from the kernel, and REST/dispatcher parity. Enforce params as env vars set in the process running os test, the JUnit @EnabledIfEnvironmentVariable / pytest skipif reading. An unmet requirement becomes SKIPPED with its reason, counted separately, never passed, and a failure-free run still exits 0. Costs: a new public discovery field; a disclosure call, because discovery is served unauthenticated on many hosts and would publish the stack's plugin inventory; a plugin-name spelling ruling; and a SKIPPED status in core and the CLI. Four axes: (1) business need: speculative, with zero producers, and the one real precondition is auth, which A does not cover. (2) Long-term: adds a public surface. (3) Anti-AI-error: plugin spellings invite guesses, so it needs a closed vocabulary to be safe. (4) Startup focus: the widest expansion.",
            "B: Replace plugins with services, judged against the discovery services map the runner already probes once per run (enabled true and status 'available'). Keep params as runner env, the same reading as A. Retire plugins immediately with a retiredKey prescription pointing to services: zero producers were measured, so there is no staged window. An unmet requirement becomes SKIPPED with its reason, naming what the target advertises. Costs: a spec narrowing (Clause-② yes (narrowing), a BREAKING banner and an ADR-0087 disposition) and the same SKIPPED status. There is no new server surface. Four axes: (1) business need: answers the question authors actually ask, 'is the AI/search surface served here?', from a contract that already exists. (2) Long-term: reuses ADR-0076 D12, 'advertise only what is mounted'. (3) Anti-AI-error: the vocabulary is the server's own service keys, and a misspelling skips with the advertised keys named instead of passing. (4) Startup focus: one reader and no new surface.",
            "C: Retire requires whole: a retiredKey tombstone on TestScenarioSchema, with the prescription 'select with --tags; state preconditions in description'. The ledger rows become tombstones. Costs: the smallest diff, but it drops a capability the mainstream has (JUnit @EnabledIfEnvironmentVariable, pytest skipif, Odoo tests scoped to installed modules). That contradicts the family's ENFORCE verdict for this key under the maintainer's criterion, which asks whether the mainstream has it, not whether anyone in the repo reads it. Four axes: (1) business need: nothing is lost today. (2) Long-term: no half-protocol. (3) Anti-AI-error: the strictest option, since the tombstone refuses the key loudly. (4) Startup focus: the best fit for a zero-pull declared surface."
          ],
          "recommendation": "B. It keeps the maintainer's criterion: the mainstream has declared preconditions, so build the consumer once. Every judgement it makes is against something the runner can observe today: discovery services, which it already fetches, and its own process env. It adds no server surface, which is A's cost. It removes the one sub-key that cannot be judged honestly rather than keeping it. The trade-off, stated plainly: C wins the anti-AI-error and startup-focus axes outright. If the maintainer weighs zero measured producers above the mainstream criterion, C is the call. Either way, the SKIPPED-with-reason leg lands only after this ruling."
        }
      ],
      "out_of_scope_findings": [
        "none new. The three round-0 carrier items (cli.mdx, the platform-checklist knownGap and the check-liveness.mts comment) are corrected in this round, after the seat amended the surface in 5861125164."
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20289,
      "status": "needs_decision",
      "branch": "claude/issue-20289-os-test-names-tags-requires",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20341",
      "session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM's id, stamped on every commit's Claude-Session trailer; identity = the branch)",
      "premise_still_valid": true,
      "summary": "The landing round after contract review 5862007793 (PASS at 5d31b4d587) is done. The new head is 5b15c28fb1. There is no behaviour change and no requires work. (1) origin/main at d498113b50 is merged in with scripts/pm/os-regen-merge.sh as merge commit 9056455134, with no rebase, amend or force-push. The one conflict was packages/spec/liveness/README.md, and it keeps main's translation and rest_api rows plus this PR's qa row. Each row was verified byte-equal to its source side. The script's step 2 took main's side of the generated state-counts.md. It was regenerated with gen:liveness-counts in 76e9a48821, never hand-resolved, and gives qa 8/1 on top of main's totals, 940/5/1/162/9/1117. That commit discharged the merge's regen deferral. Step 4 ran on a fresh spec build: 'All 15 generated artifacts are up to date', so nothing else needed regenerating. git diff d498113b50 HEAD touches only this PR's 14 files, and main's lines in the three shared files survive with 0 removals in cli.mdx. (2) Commit 5b15c28fb1 carries the reviewer's nits, text only. (a) cli.mdx, the checklist knownGap and the changeset now say the unmet requirement 'surfaces only as whatever failure it causes, if any'; the scenario can pass. The liveness README qa row carried the same inaccuracy ('fails as an unexplained HTTP error') and got the same fix. (b) The checklist revision-5 history notes the id is bare when the name equals it. (c) The README qa row uses qa.json's wording: suiteName 'on every TestResult the suite produces'. (3) One PR-body issue_patch, because the merge falsified one sentence: 'Branch base: the branch sits on 10ea9eb2ed … The merge ref is left to CI.' now names the merge. It read back byte-identical at 13777 bytes. mergeable_state went from dirty to unstable, with CI still running. requires stays with the maintainer, and open_questions is unchanged.",
      "tests": "Landing round, at HEAD 5b15c28fb1, on the merged tree. check:liveness is green with 'qa 9 classified (live 8, dead 1)' and 'state-counts.md is current'. check:platform-checklist: 'OK — 15 areas, 266 items'. spec check:generated after a fresh build: 'All 15 generated artifacts are up to date', exit 0. main also moved packages/cli and packages/spec, so the qa pins re-ran on the merged tree: core src/qa/runner.test.ts 20 of 20; cli unit qa-tags-selection plus qa-suite-schema-load 19 of 19; cli integration qa-names-and-tags-run 8 of 8. This round changed no code. Package-wide test and typecheck on the merge ref belong to CI. Earlier rounds are unchanged: round 1's check-liveness own tests 262 of 262; round 0's core 1483 tests, the cli unit tier, typechecks and ablation, as reported in 5861105453 and 5861913911.",
      "gates": "Landing round, at 5b15c28fb1. dispatch-gates --repo objectstack-ai/objectstack --commands for the merged diff: 14 paths against merge base d498113b5, the same 118 families as round 1 with none added or dropped. The recreated worktree had no dist, so the prerequisites were rebuilt under the lock with the same 13 filters: exit 0, lock held 18m51s, a shared-box figure. All 118 then ran with nothing building in the tree, and all 118 exited 0. That includes the doc gates (doc-anchors, docs-*, doc-frontmatter, doc-route-spelling, docs-section-name, doc-authoring, affected-docs, drift-comment, cli-examples-parity), check:platform-checklist, check:liveness, check:generated, api-surface, dts-closure, dual-build-cjs-loads, the i18n trio and type-check-debt. check:pm-dispatch-gates took 1356s. --ran reconciliation, with every line 'COMMAND :: exit CODE', gave '118 derived, 118 run, 0 NOT-MEASURED, 0 UNRUN' (a derived zero). CI, read once after the push: 32 check-runs on 5b15c28fb1, 19 success, 2 skipped, 11 in_progress, 0 failed. That is in_progress, and the convergence wait 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",
        "content/docs/references/qa/testing.mdx (generated by check:generated --fix)",
        "packages/cli/src/commands/test.ts",
        "packages/cli/test/qa-names-and-tags-run.test.ts",
        "packages/cli/test/qa-tags-selection.test.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/src/qa/testing.zod.ts (describe text only)",
        "content/docs/deployment/cli.mdx (round 1, text)",
        "docs/qa/platform-checklist/areas/cli.json (round 1, text; revision 4 to 5)",
        "packages/spec/scripts/liveness/check-liveness.mts (round 1, comment only)"
      ],
      "deviations": [
        "The merge commit 9056455134 carries git's default message, 'Merge remote-tracking branch origin/main into claude/issue-20289-os-test-names-tags-requires', and no Claude-Session / Co-Authored-By trailer pair. I committed the resolution with --no-edit, as the regen script prescribes. The order forbids amend, so it stands. The pre-push trailer check passed: no model identifier appears anywhere. The two commits after it carry the model-free pair.",
        "One nit reached a fourth file. The reviewer listed cli.mdx, cli.json and the changeset for the 'fails on whatever the missing plugin causes' inaccuracy. The liveness README qa row, which this PR rewrote and which nit (c) already touched, said the same thing as 'fails as an unexplained HTTP error', so it got the same correction.",
        "The PR body was patched once. The order allows that only if the merge made a sentence false, and the 'Branch base … sits on 10ea9eb2ed … merge ref is left to CI' bullet was false after the merge. Only that bullet changed.",
        "Following AGENTS' push-before-minutes rule, I pushed the three commits (9056455134, 76e9a48821, 5b15c28fb1) once, before the 18-minute rebuild. Their gates and step-4 check:generated then ran at that same head, so no second push was needed.",
        "Carried from earlier rounds: the PR opens with 'Part of #20289'; core is minor; the skip-with-reason pin and requires wait on the maintainer's decision.",
        "Cleanup: node_modules was removed before this report posted. The worktree (git worktree remove, never --force) is removed right after the comment lands, because post-stamped runs from it."
      ],
      "mcp_calls": "0",
      "api_writes": "Landing round: 2 REST writes through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, 204, then the relay run). (1) issue_patch, PATCH /repos/objectstack-ai/objectstack/issues/20341 body, correcting one bullet, run 36373561351, read back byte-identical at 13777 bytes. (2) This os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20289/comments via post-stamped. There was also 1 git push of 3 commits, head 5b15c28fb1, which is not a REST write. Earlier rounds: round 0 had 3 relay writes and 6 pushes; round 1 had 2 relay writes (issue_patch run 36367615540, comment 5861913911) and 1 push.",
      "open_questions": [
        {
          "question": "How should TestScenario.requires (params, plugins) be honoured? What does 'met' mean for each sub-key, or should the block be retired? The family verdict is ENFORCE, but neither sub-key has an honest judge from os test today. plugins: the runner reaches the target only over HTTP. No server surface lists loaded plugins: DiscoverySchema advertises services and capabilities, and /.well-known/objectstack advertises neither, while kernel.getPlugins() is in-process only. The plugin spelling (npm name or plugin.name) is also undefined. params: 'environment variables or parameters' names no process. The runner's env is observable but unconsumable, because interpolation reads only captured context. The server's env is unobservable. Measured producers: zero. The repo's one suite, examples/app-showcase/qa/platform-smoke.test.json, declares no requires. Its only precondition is auth, written as description prose: 'Requires --token'.",
          "options": [
            "A: Enforce plugins against a NEW discovery field listing loaded plugins, which needs a DiscoverySchema field, a runtime producer from the kernel, and REST/dispatcher parity. Enforce params as env vars set in the process running os test, the JUnit @EnabledIfEnvironmentVariable / pytest skipif reading. An unmet requirement becomes SKIPPED with its reason, counted separately, never passed, and a failure-free run still exits 0. Costs: a new public discovery field; a disclosure call, because discovery is served unauthenticated on many hosts and would publish the stack's plugin inventory; a plugin-name spelling ruling; and a SKIPPED status in core and the CLI. Four axes: (1) business need: speculative, with zero producers, and the one real precondition is auth, which A does not cover. (2) Long-term: adds a public surface. (3) Anti-AI-error: plugin spellings invite guesses, so it needs a closed vocabulary to be safe. (4) Startup focus: the widest expansion.",
            "B: Replace plugins with services, judged against the discovery services map the runner already probes once per run (enabled true and status 'available'). Keep params as runner env, the same reading as A. Retire plugins immediately with a retiredKey prescription pointing to services: zero producers were measured, so there is no staged window. An unmet requirement becomes SKIPPED with its reason, naming what the target advertises. Costs: a spec narrowing (Clause-② yes (narrowing), a BREAKING banner and an ADR-0087 disposition) and the same SKIPPED status. There is no new server surface. Four axes: (1) business need: answers the question authors actually ask, 'is the AI/search surface served here?', from a contract that already exists. (2) Long-term: reuses ADR-0076 D12, 'advertise only what is mounted'. (3) Anti-AI-error: the vocabulary is the server's own service keys, and a misspelling skips with the advertised keys named instead of passing. (4) Startup focus: one reader and no new surface.",
            "C: Retire requires whole: a retiredKey tombstone on TestScenarioSchema, with the prescription 'select with --tags; state preconditions in description'. The ledger rows become tombstones. Costs: the smallest diff, but it drops a capability the mainstream has (JUnit @EnabledIfEnvironmentVariable, pytest skipif, Odoo tests scoped to installed modules). That contradicts the family's ENFORCE verdict for this key under the maintainer's criterion, which asks whether the mainstream has it, not whether anyone in the repo reads it. Four axes: (1) business need: nothing is lost today. (2) Long-term: no half-protocol. (3) Anti-AI-error: the strictest option, since the tombstone refuses the key loudly. (4) Startup focus: the best fit for a zero-pull declared surface."
          ],
          "recommendation": "B. It keeps the maintainer's criterion: the mainstream has declared preconditions, so build the consumer once. Every judgement it makes is against something the runner can observe today: discovery services, which it already fetches, and its own process env. It adds no server surface, which is A's cost. It removes the one sub-key that cannot be judged honestly rather than keeping it. The trade-off, stated plainly: C wins the anti-AI-error and startup-focus axes outright. If the maintainer weighs zero measured producers above the mainstream criterion, C is the call. Either way, the SKIPPED-with-reason leg lands only after this ruling."
        }
      ],
      "out_of_scope_findings": [
        "none new."
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (four of five keys) — PR #20341 at head 5b15c28fb1 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-28T03:30Z

    Seat review against GitHub, not the report:

    • Shape: Part of #20289 (the card stays open for requires), a standalone Clause-②: no line, 14 files, +624/−42. NOT governed.
    • Delivered: triage 5859588706 ENFORCE, for four of the family's five keys.
      • QA.TestResult carries scenarioName / description (on both envelopes) and suiteName (on every result a suite produces). os test prints NAME (FILE) per suite, NAME [ID] per scenario, and a failed scenario's description.
      • --tags selects scenarios: any-of, exact, case-sensitive. Deselected scenarios are counted and never passed. An unknown tag is named. An empty entry is refused at parse. No-match exits 0, or 1 under --fail-on-empty.
      • The ledger rows qa.name, scenarios.name, scenarios.description and scenarios.tags are live, citing their readers.
      • requires' describe says NOT CHECKED, and its row stays dead.
      • Ablation: 3 of 13 unit and 3 of 8 integration went red, with the controls green, then restored.
    • REWORK round 1 (5861125164): corrected the three texts this PR falsified (the cli.json knownGap, cli.mdx os test, the check-liveness.mts header).
    • The landing round: merged main and regenerated state-counts.md, and carried the reviewer's accuracy nits.
    • File-surface amendments to claim 5860293954 (recorded here, ⛔ not a second claim): those three texts, plus the liveness/README.md qa row.
    • At-tier contract review:
      • PASS 68/68 5862007793 at 5d31b4d587;
      • delta PASS 94/94 at this head, posted on the PR in this window.
      • Nothing reads a filtered-out or erroring scenario as passed. No text claims requires does anything.
    • CI: 38 success, 4 roster skips, 0 failures. check-expected-skips is OK.
    • Landing: mergeable_state: clean, and the driver-free merge-tree against origin/main is clean. Flipped to ready with auto-merge in this window.
    • Next on this card: once this lands, the card moves pm:dispatched → needs-user-decision for requires, with the four-axis analysis and the options A / B / C from the dev's reports (5861105453, 5861913911).
  8. 4 remaining items

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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.plugins is retired and replaced by requires.services, judged against the discovery document the QA HTTP adapter already fetches once per run (services[name].enabled and status === 'available', ADR-0076 D12). requires.params is judged against the environment of the process running os test. An unmet requirement makes the scenario SKIPPED with a reason that names the unmet key and, for services, the services the target declares. A (a new discovery field listing loaded plugins) and C (retire the whole requires block) are not taken.

    Readings that decided it:

    Execution parameters (ruled in the same stroke):

    • One PR on this card, domain:spec, under the cross-domain exception the seat's 裁后执行段 sets out (triage confirms the lane): requires.plugins → retiredKey() tombstone whose prescription names requires.services; requires.services (array of service keys) judged against discovery; requires.params read from the os test process environment; packages/core TestResult gains a SKIPPED status counted separately and never counted as passed, exit 0 when nothing failed; os test prints the skip reason per scenario. ADR-0087 D3 entry for plugins; the ledger row qa.scenarios.requires flips to live citing the reader. Clause-②: yes (narrowing), minor with the BREAKING banner (never major).
    • Pins: an unmet services entry skips and lists the declared services; a met one runs; an unmet params entry skips naming the variable; an all-skipped run is not a pass, and under --fail-on-empty it exits 1; a plugins key is refused at parse with the prescription.
    • Confidence gap carried into the PR: if any out-of-repo suite authored requires.plugins with package names, the tombstone prescription gives the plugin → service mapping; none is known.

    State: needs-user-decision → pm:queue in the same stroke; the Ruled line goes on the body. The four sibling keys landed via PR #20341 (5a6267f486) and are unaffected.

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01ARcDurZ5j34RdqsGgc4jgH
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20289-requires-services
    Worktree: objectstack-issue-20289
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: the fifth key of the qa-runner family, requires, executing ruling B (5863822176). It is one PR under the cross-domain exception the ruling sets out, with every end declared:

    Ruling carried: 5863822176 · batch #232 item 1 · letter B · maintainer 「同意」. The measurement it rests on is seat 2's release 5863072096 and its decision box 5863075827.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  11. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  12. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT · PR #20511 at head 75d17a115ee806c21e916caf4c3c3cbce7e32e56 · 2026-09-28T21:16Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim 5876608230, as amended in place after the fact (its "Amended" line). This seat took the readings below on GitHub or on the tree.

    check reading
    Shape Draft, base main. Body line 1 Fixes #20289, and Clause-②: yes (narrowing) stands alone.
    Scope 25 files, +1536/−70: ruling B (5863822176) end to end. requires.plugins is tombstoned with the plugin → service mapping. requires.services is judged against the discovery document the QA HTTP adapter already fetches (one memoised probe, no new request, field or route). requires.params is judged against the os test process environment. TestResult gains status and skipped. os test prints skips and gets the all-skipped exit posture. Plus the ADR-0087 D3 entry, the ledger row → live, and three minor changesets with the BREAKING banner. The paths outside the original claim are admitted by the amendment, each judged necessary by the record.
    Contract review At-tier record PASS 5878604417, same head. Every execution parameter and all five ruled pins are present and load-bearing. requires.services closed over CoreServiceName is inside the ruling: both discovery producers write exactly the 16 CoreServiceName keys, so an open array would only turn a misspelling into a permanent skip. TestResult is additive for every in-repo reader. The ablation reading matches (7 of 30 red, exactly the skip pins).
    Deliberate red Check Changeset is 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 on pull_request only (no merge_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.
    CI at this head 32 success, 2 skipped (roster, check-expected-skips.mjs --pr 20511, exit 0), 1 failure = the deliberate Check Changeset. git merge-tree against origin/main 9449512a is clean. Since the base, main has not moved state-counts.md, the liveness README, the reference page, spec-changes.json or the upgrade guide, so rule A owes no sync.
    Governed / size Not governed (check-governed-merges.mjs --pr 20511: 0 of 25 paths), 1606 changed lines.

    Out-of-scope findings:

    Landing order: this PR lands before #20294's PR #20512. Both edit packages/spec/liveness/state-counts.md (an os-regen path), so #20512 takes a sync-and-regenerate round after this merges.

    Landing next: ready + auto-merge through the relay. At MERGED, the Fixes line closes this card.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  13. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20511 → main 0bbe4005e82fce2058238720cac7ba5cd2f182d5 · 2026-09-28T21:49Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim 5876608230 (as amended). ACCEPT 5878795654 on the at-tier PASS 5878604417. Merged through the merge queue at 21:48Z, with the deliberate Check Changeset red of the accepted class.

    Verified on origin/main:

    Close-out: the PR's Fixes line closed this card completed. pm:dispatched comes off in this act, and the assignee stays as the record of who landed it. The qa-runner family is now whole: the four sibling keys (PR #20341) plus requires here. The qa ledger reads 9 live / 0 dead.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  14. added a commit that references this issue on Sep 29, 2026
    0bbe400
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:specenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions