Repository navigation
qa: os test prints suite and scenario names, filters by tags, and skips on unmet requires (5 keys) #20289
Description
Activity
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsPath: the road — verify | 缺项 (no item runs
os testwithtagsor an unmetrequires) | P3Triage: first grade —
enhancement·priority:p3·domain:spec·area:devpath·pm:queue. Verdict: ENFORCE, by the maintainer's criterionTriage: the readers land in
packages/core/src/qa/runner.ts(scenario names inTestResult, and unmetrequiresbecomes skipped-with-reason) andpackages/cli/src/commands/test.ts(a--tagsflag, and names printed) ⇒domain:specparent, with adomain:clisub-issue. Rationale:tagspromises filtering that does not exist, andrequires.pluginsreads 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 pytestskipif. 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.
os testprints suite and scenario names, filters by--tags, and reports an unmetrequiresas skipped with its reason, ⛔ never as passed.- Pin each: a tag filter, a skip with a reason, and the unfiltered run (the control).
- Each ledger row flips to
live.
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterateenhancementNew feature or requestNew feature or requestand removed
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01QcAS3qiYYZNezaxZxaUdMV
Account:os-project-manager(the seat's linked user asGET /useranswers 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 inTestResult, and an unmetrequiresbecomes skipped-with-reason. Plusrunner.test.ts.packages/cli/src/commands/test.ts(domain:cli): a--tagsfilter, and suite and scenario names printed. Plus its tests.- The
packages/spec/liveness/**rows for the 5 keys, flipped toliveand 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:clihalf 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 onorigin/main10ea9eb2ed.- Open-PR census (15 open PRs): none touches
packages/core/src/qaorpackages/cli/src/commands/test.ts. The Version Packages PR touches onlypackages/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 unmetrequiresis reported as skipped with its reason, ⛔ never as passed.objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsREWORK round 1 (texts this PR falsifies) — PR #20341 at head
dd6b472c63·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-28T00:12ZSeat review against GitHub and the dev's report
5861105453:- Delivered so far: four of the family's five keys are live.
TestResultcarries scenario and suite names, andos testprints them.--tagsselects 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.descriptionandscenarios.tagsarelive. requires's describe now says NOT CHECKED, and its row staysdead.- The ablation went red in both pins, then was restored.
Part of #20289is correct: the card cannot close whilerequiresis 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.
docs/qa/platform-checklist/areas/cli.json:456(knownGap on theos testitem): "no scenario SELECTION exists:os testhas exactly three flags …". This becomes false. Rewrite it to what stays true:--tagsselects, andrequiresis not honoured (NOT CHECKED, pending the decision on this card).content/docs/deployment/cli.mdx, theos testsection: document--tags(any-of, exact, how deselected scenarios are counted, no-match and--fail-on-empty) and the new report lines.packages/spec/scripts/liveness/check-liveness.mts:368(the header comment): it namessuite.nameandscenario.tagsas unread. Onlyscenario.requiresstays unread.
- File-surface amendment to claim
5860293954(recorded here, ⛔ not a second claim): those three files, text only. - The
requiresquestion: it is a real contract-shape divergence (A: a new discovery field; B:servicesreplacesplugins; C: retire). The execution seat does not rule it. After this PR lands, the card movespm: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.
- Delivered so far: four of the family's five keys are live.
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsACCEPT (four of five keys) — PR #20341 at head
5b15c28fb1·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-28T03:30ZSeat review against GitHub, not the report:
- Shape:
Part of #20289(the card stays open forrequires), a standaloneClause-②: noline, 14 files, +624/−42. NOT governed. - Delivered: triage
5859588706ENFORCE, for four of the family's five keys.QA.TestResultcarriesscenarioName/description(on both envelopes) andsuiteName(on every result a suite produces).os testprintsNAME (FILE)per suite,NAME [ID]per scenario, and a failed scenario's description.--tagsselects 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.descriptionandscenarios.tagsarelive, citing their readers. requires' describe says NOT CHECKED, and its row staysdead.- 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 (thecli.jsonknownGap,cli.mdxos test, thecheck-liveness.mtsheader). - The landing round: merged
mainand regeneratedstate-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 theliveness/README.mdqarow. - At-tier contract review:
- PASS 68/68
5862007793at5d31b4d587; - 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
requiresdoes anything.
- PASS 68/68
- CI: 38 success, 4 roster skips, 0 failures.
check-expected-skipsis OK. - Landing:
mergeable_state: clean, and the driver-freemerge-treeagainstorigin/mainis clean. Flipped to ready with auto-merge in this window. - Next on this card: once this lands, the card moves
pm:dispatched→needs-user-decisionforrequires, with the four-axis analysis and the options A / B / C from the dev's reports (5861105453,5861913911).
- Shape:
4 remaining items
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsRuling: 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:
- Long-term: one reader over one existing contract — discovery's declared services — and no new served surface. It is the shape mainstream test frameworks give preconditions (JUnit
@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. - AI-safety: the vocabulary is the target's own service keys; a misspelling skips loudly and lists the declared services; nothing passes silently. A's open plugin spelling would be guessed.
- Real use: zero producers in the three repos; the one real precondition (auth) is
--token's. Cost did not decide; the criterion did. - Startup scope: net one key fewer (
pluginstombstoned) and one reader; A would add a public field and a disclosure surface.
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 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). - Pins: an unmet
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. - Confidence gap carried into the PR: if any out-of-repo suite authored
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.- Long-term: one reader over one existing contract — discovery's declared services — and no new served surface. It is the shape mainstream test frameworks give preconditions (JUnit
- added a commit that references this issue
on Sep 28, 2026 objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsClaim: PM loop round 3
Session:session_01ARcDurZ5j34RdqsGgc4jgH
Account:os-warren(the seat's linked user asGET /useranswers 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 theqa-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.- The ADR-0087 D3 entry for
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).- ⛔ No new served surface: no discovery field and no route (option A is not taken). ⛔ Not the four sibling keys PR feat(qa): os test prints suite and scenario names and selects scenarios by --tags #20341 landed, except where
TestResultmust carry the new status. - Amended 2026-09-28T21:08Z (seat, after the fact, on the at-tier record
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 onorigin/mainfc0db22b.
- Of the 11 open PRs, none touches
packages/spec/src/qa/**,packages/core/src/qa/**,packages/cli/src/commands/test.tsorliveness/qa.json. No live claim on the board names them. - The newest change on this surface is PR feat(qa): os test prints suite and scenario names and selects scenarios by --tags #20341 (
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 commented
on Sep 28, 2026 ContributorAuthorMore actionsos-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 commented
on Sep 28, 2026 ContributorAuthorMore actionsReview: 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.check reading Shape Draft, base main. Body line 1Fixes #20289, andClause-②: yes (narrowing)stands alone.Scope 25 files, +1536/−70: ruling B ( 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.Contract review At-tier record PASS 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).Deliberate red 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.CI at this head 32 success, 2 skipped (roster, 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.Governed / size Not governed ( 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).- An empty
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 commented
on Sep 28, 2026 ContributorAuthorMore actionsLanded: 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.- Its diff against that parent is 25 files, +1536/−70, the same as the PR.
- Content controls, parent → merge:
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. - No queue branch for 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 remains.
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_01ARcDurZ5j34RdqsGgc4jgH- added a commit that references this issue
on Sep 29, 2026
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 request5857165909on the seat post. Familyqa-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).Census by the
domain:specexecution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. Bases: objectstacka9fb83ef, re-checked against4d7e740d, where no ledger file or cited surface moved; objectui6fa5f64a1(pinf8a9d0fb); cloud96eb092. Ledger instrument:check-liveness.mts --json, whosebyStatusequals the committedstate-counts.mdrow 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 is7of 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
qa.namepackages/spec/liveness/qa.json:5TestRunner.runSuite(packages/core/src/qa/runner.ts:25-31) touches onlysuite.scenarios, and the one caller printspath.basename(file)as the suite heading (packages/cli/src/commands/test.ts:95) — so renaming a suite changes not…qa.scenarios.namepackages/spec/liveness/qa.json:20TestResult(packages/core/src/qa/runner.ts:6-12) hasscenarioIdand no name field, and the CLI printsresult.scenarioId(packages/cli/src/commands/test.ts:104). So an autho…qa.scenarios.descriptionpackages/spec/liveness/qa.json:26flow.descriptionandhook.label/descriptioncarry 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.tagspackages/spec/liveness/qa.json:32os testhas three flags —--url,--tokenand--fail-on-empty(#7848) — and not one of th…qa.scenarios.requirespackages/spec/liveness/qa.json:59requires.params(environment variables) andrequires.plugins(plugins that must be loaded) — that nothing checks. Neither the runner nor the CLI readsscenario.requires; there is no skip path and no precondition fa…Mainstream evidence:
@tagged(...)on test classes and--test-tagson the CLI, which is tag selection in a metadata platform.--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:
TestResultcarries the scenarioname, and a scenario with unmetrequiresbecomes skipped-with-reason. packages/cli/src/commands/test.ts adds a--tagsflag (today only url / token / fail-on-empty, :273-275) and prints the names.User-visible risk (2):
tagspromises filtering ("critical", "regression") that does not exist, andrequires.pluginsreads 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:四轴:
requires.plugins以为是守卫,实际在缺插件的服务器上会跑出无法解释的 HTTP 错误。执行后改为带原因的跳过。