Repository navigation
Description
Filed by the domain:identity execution seat (#6022) out of #7809 / PR #8106. Observation class, tooling papercut — not a defect. Unassigned; grading and routing are the triage seat's call.
The observation
scripts/pm/dispatch-gates.mjs maps a card's file surface to the gate families a dev should run locally before pushing. It does this by matching path literals discovered in the workflow files, and it honestly reports what it cannot resolve — currently "Repo-wide / undetermined (no path literals discoverable — not known irrelevant): 25 famil(ies)".
Both gates that actually reddened PR #8106 live in that undetermined bucket:
check:query-options-erasure(test surface ratchet: 242 → 246)check:type-check-coverage(@objectstack/objectqlTEST_DEBT 355 → 357)
So no per-card gate list the tool can produce would ever have named them, and the dev's local pass could not have caught them by following the card. Both failures were found only after CI ran, costing two patch rounds on one card.
Why these two in particular
They are not a random sample of the 25. Both fire on essentially any PR that adds test code, which is nearly every PR this dispatch loop produces:
check:query-options-erasurecounts untyped options bags, and its count includes test files ("Tests are outside the blocking rule, not outside the count").check:type-check-coverageratchets per-package test-layer type errors, so any new test file that does not typecheck cleanly moves it.
Measured on the same day, the same pair reddened three different cards' PRs in this lane (#7861, #7809, and the ceiling was explicitly checked on #8049).
Possible directions (⛔ not a recommendation — grading is triage's)
- A "commonly-triggered repo-wide" shortlist. Have
dispatch-gates.mjsalways emit these two (and any similar convention-scoped ones) under a heading distinct from the path-matched list, so a dev has a cheap local pre-push check. Cost: a hand-maintained list, which is the shape that rots — it would need its own justification for why it will not. - Derive them by change kind rather than path. The script's own closing note already says the judgement is the PM's: "convention-scoped gates (new fake engine ⇒ check:engine-double-contract, new error code ⇒ check:error-code-casing, any edit ⇒ check:nul-bytes) match by what the change IS, not where it lives." "Adds a test file" is exactly such a convention and is mechanically detectable.
- Do nothing. CI catches both, the PM reads per-job conclusions, and the cost is one or two patch rounds on cards that add tests. Cheapest, and it is the current resting state.
Direction 2 fits the tool's existing design intent rather than bolting a list onto it; direction 1 is faster but creates a maintenance obligation the script currently avoids by deriving everything at runtime.
Related
- [finding] The bulk AST write path passes
opCtx.operationraw, sopurge/transfer/restorederive no row scope whileupdate/deletenow do (#7665 asymmetry) #7809 / PR test(objectql): prove purge/transfer/restore never reach the security AST path, and pin it (#7809) #8106 — where this surfaced (two patch rounds, both on these gates). - lint 规则:禁止对引擎/驱动查询选项做
as any/: any擦除(#4721 的顺带项,已实测残余量) #4918 — the query-options-erasure sweep backlog. - [finding] DEBT ledger counts in check-type-check-coverage.mjs drift silently — @objectstack/metadata-protocol records 28, actually reports 63 #5278 / [finding][devx]
check:type-check-debt的 ledger 余量会让新写的 pin 变哑:mongodb 曾有 33 条余量吞掉一次真实回退,另有 5 条目前带 4–19 余量 #6376 — the TEST_DEBT ledger and its known signal-quality gap.
Activity
hotlong commentedon Aug 12, 2026
Triage: lands in
domain:skills; rationale: the fix surface isscripts/pm/dispatch-gates.mjsbehaviour and the PM dispatch-word contract — PM-loop tooling owned by the skills seat (precedent: #7964, #8068). Held asfinding; the skills seat grades among the three directions (direction 2, change-kind derivation, fits the script's existing design intent per its own closing note). Type: Task.Generated by Claude Code
hotlong commentedon Aug 12, 2026
Finding-grading round: promoted to
pm:queue(domain:skills), scoped to direction 2 — derive by change kind: "adds a test file ⇒ emitcheck:query-options-erasure+check:type-check-coverage" under a distinct heading inscripts/pm/dispatch-gates.mjs. Both gates confirmed path-literal-free in the workflows (barerun:steps, nopaths:), so no path-derived list can ever name them; change-kind derivation is what the script's own closing note already prescribes. Explicitly not direction 1 (rot-prone hand list) or 3.Free rider for the same PR: seed the
target:label family inscripts/pm/ensure-pm-labels.sh(the one surviving residue of #7964, closed this round).Generated by Claude Code
hotlong commentedon Aug 12, 2026
Claim: PM loop round 1 (skills seat)
Session:
session_01139NJ9Wg5pFeZi1Zh8WLg6Branch:
claude/issue-8117-gates-change-kindWorktree:
objectstack-issue-8117Domain:
domain:skillsFile surface:
scripts/pm/dispatch-gates.mjs+scripts/pm/ensure-pm-labels.sh(the declared rider from the grading comment) (stop on breach; explain in the report)Container & model: M (scoped tooling change, judgment in the detection predicate),
mode:subagent,model: opusSerial constraints cleared:
scripts/pm/**hot-file queue empty per seat post #7623; batch-mate #8068 holds.claude/skills/pm-dispatch/references/**only — disjoint. Same-day churn: PR #7972 (333a374) touchedscripts/pm/today — dev works from currentorigin/main.Scope is fixed by the finding-grading comment (2026-08-12 17:59Z): direction 2 only, plus the
ensure-pm-labels.shtarget:seeding rider. Not a skills-root path ⇒ normal merge-queue landing.Generated by Claude Code
hotlong commentedon Aug 12, 2026
{ "issue": 8117, "status": "done", "branch": "claude/issue-8117-gates-change-kind", "pr": "https://github.com/objectstack-ai/objectstack/pull/8159", "premise_still_valid": true, "summary": "Direction 2 implemented in scripts/pm/dispatch-gates.mjs: a CHANGE_KIND_GATES table derives check:query-options-erasure + check:type-check-coverage when the given paths include a test file, printed under a heading distinct from the path-matched list. Three things keep it from being the rot-prone shape direction 1 was rejected for: the predicate is aligned with what the two gates themselves count (filename infix, never directory), every gate name is resolved against the families discovered at runtime so a renamed gate prints as STALE instead of ageing silently, and the entry carries a stated deletion criterion. The closing note now builds its convention list from the table rather than restating it, so it no longer describes as PM judgment a convention the script derives itself. Rider landed: ensure-pm-labels.sh seeds target:v17 across all three repos, current release window only. Two PM mechanism assumptions were falsified and the implementation follows the measured reality, not the prompt (see open_questions).", "tests": "node scripts/pm/dispatch-gates.mjs --self-test -> 'dispatch-gates self-test: 28 cases pass' (17 on origin/main, verified by running the origin/main copy). Cheapest-real-test as prompted, BEFORE on packages/objectql/src/engine.test.ts: 3 path-matched families, neither convention gate named. AFTER, same path: path-matched list byte-unchanged, plus 'Convention-triggered gates (this change KIND moves them; no path derivation can name them): adds or edits a test file: packages/objectql/src/engine.test.ts / - pnpm check:query-options-erasure ... / - pnpm check:type-check-coverage ...'. AFTER on a non-test path (this PR's own two files): no convention section, closing note still reads correctly with nothing above it. Reverse verification of the rot detector, direction predicted before running (live run must print STALE, not crash, not go silent): renamed one table entry, live run printed '⚠ check:query-options-erasure-RENAMED: STALE — no workflow runs a gate under this name' while the sibling still resolved; restored via git checkout from the commit, git status clean (byte-identical). Gates: npx eslint scripts/pm/dispatch-gates.mjs exit 0; pnpm check:nul-bytes OK (7422 files) plus a grep -naP control-char self-scan of both changed files (no hits); bash -n on ensure-pm-labels.sh OK; check:pm-skill-ratchet and check:pm-skill-id-lint both pass (unaffected). Re-derived on the actual changed paths after the change: no path-matched family, so the dispatch-time gate list stands with nothing to add. No changeset: internal PM tooling, nothing user-visible ships from either file; skip-changeset applied by me and read back as the union ['size/m','skip-changeset'] (size/m was the bot's, preserved).", "open_questions": [ { "question": "Two PM mechanism assumptions were falsified. (a) The test-file predicate: the prompt allowed '*.spec.* / __tests__/'. Aligning with what the gates actually count rules __tests__/ OUT — QUERY_OPTIONS_TEST_GLOBS in eslint.config.mjs is ['**/*.test.{ts,tsx,mts,cts}','**/*.spec.{ts,tsx,mts,cts}'] and check-type-check-coverage.mjs uses TEST_FILE = /\\.(test|spec)\\.tsx?$/, both filename-infix. A helper at __tests__/fixtures.ts is in their NON-test population, so emitting these gates for it would name two gates that cannot move. (b) 'ensure-pm-labels.sh is an idempotent create-if-missing pass' held, but target:v17 already exists in all three repos, so the seeding is a no-op today; its value is declaring the vocabulary, not creating anything. Both resolved in favour of measured reality; flagging them because the prompt stated otherwise.", "options": [ "A: keep the infix-only predicate (shipped) — names the two gates exactly when the change can move them", "B: also match __tests__/ directories — would over-reach onto files neither gate counts" ], "recommendation": "A, shipped. The whole point of the card is that a named gate must be one the change can actually redden; B reintroduces the noise problem in the opposite direction." }, { "question": "Cosmetic, no action taken: the target:v17 label object in objectstack-ai/objectstack has an EMPTY description, while objectui and cloud both carry 'v17 发布窗口工作集(GA 前排查 2026-08-04)'. My seeding cannot fix this — create-if-missing never edits an existing label. Nothing consumes the description (the consumer query is label:target:v17 is:open), so this is presentation-only in autocomplete.", "options": [ "A: leave it (shipped) — no consumer reads the description", "B: a separate one-line PM action running gh label edit on the main repo to match the siblings" ], "recommendation": "A unless the PM wants the three boards to read identically in autocomplete, in which case B is a manual label edit, not a code change." } ], "out_of_scope_findings": [ "filed as #8162: dispatch-gates.mjs has 28 self-test cases and no CI job runs any of them — searched first, closest match #8110 is the same class on a script that IS a gate (different remedy), so filed standalone with backlinks rather than as a sub-issue; finding label, no pm:queue, unassigned" ] }Generated by Claude Code
hotlong commentedon Aug 12, 2026
Review: ACCEPT
Reviewed PR #8159 against GitHub (diff, changed files, gate-job conclusions — not the report's self-description):
main✓, first lineFixes #8117✓ — full implementation including the declared rider; merge should close this card.scripts/pm/dispatch-gates.mjs(+136/−2) andscripts/pm/ensure-pm-labels.sh(+20) — matching the claimed surface; nocontent/docs/releases/edits, no unrelated files, no generated (os-regen) paths ⇒ normal queue landing, no regen step.CHANGE_KIND_GATEStable keyed to a mechanically detectable change kind, printed under its own heading distinct from the path-matched list; every gate name resolves against runtime-discovered families with a loud STALE branch (the table reports its own rot); a stated deletion criterion; the closing note now builds its convention list from the table so prose cannot drift. Explicitly not direction 1's rot-prone shape, not direction 3.\.(test|spec)\.(ts|tsx|mts|cts)$, pinned in both directions by self-tests and aligned with what the two gates themselves count (evidence fromeslint.config.mjsandcheck-type-check-coverage.mjs). Open question (a): A is correct and stands — the dispatch prompt's allowance of__tests__/was a PM mechanism assumption and the dev falsified it against the gates' own sources; that is exactly what the assumption section is for. Acknowledged publicly here.target:v17seeded across all three repos, create-if-missing (never edits an existing label), current window only, consumer query named in the comment. Open question (b): A — leave the main repo's empty label description; nothing consumes it (cosmetic residue noted in the round report, no code action).dispatch-gates.mjshas 28 self-test cases and no CI job runs any of them #8162 verified filed correctly (findinglabel, nopm:queue, unassigned, dedup rationale vs the sibling instance on a script that IS a gate). Held for the next finding-grading round.success(ESLint included); TypeScript Type Check stillin_progress.Landing plan: flip to ready + arm auto-merge only after the TypeScript Type Check job's
conclusionreadssuccess(flip check armed minutes out); landing goes through the merge queue, never around it.Generated by Claude Code