Skip to content

check:skill-frame-freshness --self-test runs nowhere in CI, so a broken fixture is invisible until a human runs the gate #8110

Closed

Description

@hotlong

Filed by the domain:devx dev working #8024 (PR #8109). Out of scope there — that card's file surface is scripts/check-skill-frame-freshness.mjs, and this is a workflow change. #8024 remains open until its own PR lands.

The observation

check:skill-frame-freshness is deliberately not wired into lint.yml, and the script's header gives the reason, which is correct as far as it goes: on a fresh CI merge ref the scan is a tautological green, and on any PR that legitimately edits the decision frame it would be a false red, since "differs from main" is that PR's whole purpose.

But package.json runs the gate in two halves:

"check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs"

The reasoning above applies only to the second half. The --self-test half is a pure self-consistency check: it manufactures a specimen from the repo's own files, drives it through temp git repos, and touches neither the network nor origin/main. Nothing about it is tautological on a merge ref, and nothing about it goes red on a PR that edits the frame on purpose (it re-derives from whatever the frame currently is).

So the half that CANNOT run in CI is keeping the half that CAN out of it.

Why it is worth a card

Measured while fixing #8024: the fixture had stranded twice, not once.

documents old hand-copied fixture
pre-rewrite (ab19075^) RED — its mention rule spelled 沿三条固定评估轴, missing the bare 三条评估轴 mention in the same file, so the specimen was incoherent and the independence case failed
today (HEAD) RED — the axis-line rule matched nothing after the 2026-08-12 rewrite; this is what #8024 reports

The first stranding was never noticed by anyone. Both would have been caught the day they landed by running the self-test half in CI. The failure mode is the expensive kind: the gate stays in the gate list reading as coverage while protecting nothing.

Suggested shape

Add a step to lint.yml running node scripts/check-skill-frame-freshness.mjs --self-test on its own — not pnpm check:skill-frame-freshness, which would drag the scan in with it. Worth checking whether other gates in the repo have the same split shape (a CI-hostile scan hiding a CI-safe self-test behind &&); if so this is a family, not a single step.

Establishment

Measured in a clean worktree at d91c558; the pre-rewrite half by reconstructing the three framework documents and both scripts at ab19075^ into a temp tree and running the self-test there. The exact dating of the first stranding could not be established — this clone's history is grafted at f7dceed, so git log -S on the mention attributes it to the graft boundary rather than to a real introducing commit.


Generated by Claude Code

Blocked-by: #8024

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 12, 2026
  2. added theissue type on Aug 12, 2026
  3. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Triage: lands in domain:devx (.github/workflows/lint.yml); marked pm:blocked with Blocked-by: #8024 appended to the body (line added at triage).

    Rationale for the block: by this card's own table the self-test is RED at current HEAD — that is exactly what #8024 / PR #8109 fixes — so wiring the step into lint.yml before that lands would redden CI for every PR in the repo. When #8024 closes, the unlock scan returns this card to pm:queue; re-verify at that moment that PR #8109 did not already add the CI step itself.

    Scope note for whoever takes it: the "same split shape elsewhere" question in Suggested shape is a bounded check worth doing in the same PR, but new gate wiring beyond --self-test steps is out of scope — adding a scan half to CI would recreate the false-red problem the script header warns about.

    Type: Task.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Aug 12, 2026
  5. self-assigned this
    on Aug 12, 2026
  6. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Claim: PM loop round 7
    Session: session_01PaiisQMhsYxwa5ZX6Mfmv2
    Branch: claude/issue-8110-skill-frame-selftest-ci
    Worktree: objectstack-issue-8110
    Domain: domain:devx
    File surface: .github/workflows/lint.yml (+ package.json only if the survey below justifies it). ⛔ No .claude/skills/** / skills/** — those are the skills seat's (#7623), and this card does not need to touch them. ⛔ No new scan-half wiring.
    Container & model: XS, mode:cloud, model: opus

    解锁已核实 — 两条读数,都不靠工具返回值

    1. 阻塞项 check:skill-frame-freshness is red on main — self-test fixture drifted after the principles-only rewrites, so the gate never scans #8024 已关闭(2026-08-12 16:34:55Z),closed_by_pull_requests 恰好一条:PR fix(scripts): derive the skill-frame-freshness self-test fixture from the live documents #8109,状态 MERGED。
    2. 内容确实在 origin/main 上:旧的手抄 fixture 模式 每个方案先问 在 scripts/check-skill-frame-freshness.mjs 里已 0 命中 —— 与 fix(scripts): derive the skill-frame-freshness self-test fixture from the live documents #8109「fixture 改为从活文档推导」一致。所以本卡表格里那行「today (HEAD) — RED」已不再成立,self-test 现在是绿的,可以进 CI 而不会立刻把全仓染红。

    分诊要求的复验,结果是「没有」

    分诊留言要求解锁时确认 「PR #8109 是不是顺手把 CI step 加了」。没有加。 在 origin/main 上机械查过:

    • .github/workflows/lint.yml 里 skill-frame 只出现 一次,在 :1082 的 run: pnpm check:skill-frame-sync;
    • 全 .github/workflows/ 目录扫一遍,只有 lint.yml 这一处提及;
    • package.json:44 的 check:skill-frame-freshness 仍是 --self-test && <scan> 的两半形。

    ⇒ 本卡前提完整成立,可派。

    一条派发前就能看见的、会影响调查结论的事实

    :1082 那行说明姊妹门 check:skill-frame-sync 在 CI 里跑的是完整两半(它的 scan 半边不惧 CI)。所以它不属于本卡要找的那个家族 —— 家族的判据是「scan 半边进不了 CI,于是把能进的 self-test 半边一起挡在外面」,而不是「脚本里有 &&」。派发令里已把这条写成裁决,避免 dev 把 && 当成家族标志去扫。

    Serial constraints cleared:无在飞 PR 触及 .github/workflows/lint.yml(#8142 是纯 docs,#7960 是 docs/adr/ + scripts/check-adr-anchors.mjs)。⚠️ 但 lint.yml 是共享基础设施,动手前须重读目标 step 前后的块 —— 本轮已有多张卡在 scripts/ 与 workflow 面上并行。


    Generated by Claude Code

  7. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor
    {
      "issue": 8110,
      "status": "done",
      "branch": "claude/issue-8110-skill-frame-selftest-ci",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8171",
      "premise_still_valid": true,
      "summary": "Added one step to lint.yml's typecheck job, next to check:skill-frame-sync, running only `node scripts/check-skill-frame-freshness.mjs --self-test` (never `pnpm check:skill-frame-freshness`, which would drag the CI-hostile scan half in). 36 added lines, one file, no package.json change: a dedicated script name would be a second hand-kept spelling of the same command with nothing to gain, and the repo already invokes check-dev-prereqs.mjs directly with `node` for the same 'do not take the other half' reason. All four PM hypotheses were measured. A: hermetic — 12/12 green in a `git clone --depth 1` carrying one commit and NO refs/remotes/origin/main, so no fetch-depth or checkout change is needed (and the typecheck job already checks out fetch-depth: 0 anyway). B: cheap — ~1.3s depth-1, ~2.0s full worktree, and it needs no node_modules at all. C: the family is non-empty and has exactly ONE other member, check:dev-prereqs, filed as #8170 and deliberately not fixed here. D: needs a correction and the PR body states it — 'a frame-editing PR does not redden it' is true for wording but too broad as stated. A structural change (axis added/removed, declaring sentence reworded past the shared anchors) DOES redden it; measured, and the same edit reddens check:skill-frame-sync directly above with the same diagnosis, which the self-test's own error names as the thing to fix first. So the step adds no failure class its neighbour lacks — 'nothing can redden it alone' is the accurate claim, not 'nothing can redden it'. Also worth carrying: `check:skill-platform-checklist`-style survey work must strip workflow YAML comments before matching, or a comment explaining why a gate is NOT wired reads as wiring.",
      "tests": "node scripts/check-skill-frame-freshness.mjs --self-test → '✓ check-skill-frame-freshness self-test: 12 cases pass.' (exit 0, real 0m1.958s in the worktree; real 0m1.258s in a --depth 1 clone with no origin/main ref, `git rev-parse --is-shallow-repository` = true). Adversarial D-tests, all in a scratch clone, never on the PR's file surface: (1) reword an axis body line in .claude/agents/os-dev.md → self-test exit 0, check-skill-frame-sync exit 0 ('4 copies ... structurally isomorphic across 3 files'); (2) reword axis prose in all three frame files at once → both exit 0; (3) reword os-dev.md's declaring sentence → self-test exit 1 with 'the REAL document does not parse ... this is a `pnpm check:skill-frame-sync` failure first', and check-skill-frame-sync.mjs exit 1 with 'the declaring sentence anchor matched 0 time(s), expected exactly 1'. Family survey, mechanical: 62 root check:* scripts cross-referenced against all 24 .github/workflows/*.yml with comment lines stripped → 3 unwired (skill-frame-freshness, dev-prereqs, platform-checklist); widened to all 63 scripts/**/*.mjs mentioning --self-test → no further candidates. check:dev-prereqs measured: --self-test exit 0 in 0.120s on an unbuilt checkout with no node_modules (16 cases), scan exit 1 ('The workspace is not built ... 67 of 67 workspace packages'). check:platform-checklist rejected: its scan is CI-capable, it is out by an explicit maintainer policy decision recorded in lint.yml, not by CI-hostility. Gates re-derived with `node scripts/pm/dispatch-gates.mjs .github/workflows/lint.yml` and run: check:changeset-gate-self-tests (118+153+117 assertions), check:node-version, check:nul-bytes (7422 files, 0 control bytes), check:required-contexts, check:shard-attestation, check:type-check-coverage, check:workflow-status-functions, node scripts/check-changeset-no-major.mjs ('✓ This diff introduces no `major` bump.') — all exit 0. check:type-check-debt NOT run and this is deliberate: its --re-measure half refuses without the full workspace built ('55 workspace dependencies of the ledgered packages have no built type entry point on disk'), and building 77 packages to re-measure TS debt for a diff that touches no TS source is not proportionate; its --self-test half is shared with check:type-check-coverage, which passed. YAML validated by parse (41 steps in typecheck; the new step's run is exactly the self-test command). skip-changeset applied by me and read back after bots settled: labels are ci/cd, size/s, skip-changeset. CI on the PR: in_progress at report time — not waited on, per contract.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #8170: `check:dev-prereqs --self-test` runs nowhere in CI — the second and only other member of this card's split-gate family (self-test green in 0.12s on an unbuilt tree; scan hard-red there, so the scan correctly stays out and takes the self-test with it). Includes the full enumeration method and the two rejected candidates, so the negative half of the survey is reviewable."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  8. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — PR #8171,已翻 ready,auto-merge 已挂(squash)。23 条 check 零 failure,其中 TypeScript Type Check(新 step 所在的那个 job)18:53:24Z 绿 —— 这就是新 step 在 CI 里真的跑过且通过的直接读数,不是推断。

    裁决对账(读 diff)

    裁决 实现 判定
    只接 --self-test 半边 run: node scripts/check-skill-frame-freshness.mjs --self-test ✅
    ⛔ 不得用 pnpm check:skill-frame-freshness 用 node 直调,注释里点名理由,并类比仓库既有的 check-dev-prereqs.mjs 同款处理 ✅
    ⛔ 不接 scan 半边(任何门) 无 ✅
    文件面 只有 .github/workflows/lint.yml,package.json 未动 ✅ 且论证了为什么不加专用 script 名:那只是同一条命令的第二份手抄拼写
    ⛔ 不碰 .claude/skills/** / skills/** 未触及。⚠️ 对抗测试是在另建的 scratch clone 里做的,没有落到本 PR 的文件面上 —— 这个分寸是对的 ✅
    家族普查要说明如何枚举 见下 ✅

    四条机制假设,两条修正了我

    • A(自洽/无网络)确认,且比我要求的更硬:在 git clone --depth 1、没有 refs/remotes/origin/main 的树里 12/12 绿。⇒ CI 不需要 fetch-depth 或 checkout 调整。这正是我担心的浅克隆敏感性,被直接排除。
    • B 确认:depth-1 约 1.3s、完整 worktree 约 2.0s,且完全不需要 node_modules。
    • C 家族非空,恰好一个成员:check:dev-prereqs(self-test 在未构建的树上 0.12s 绿,scan 在那里硬红 —— 所以 scan 正确地留在 CI 外,并把 self-test 一起带了出去),已另立 check:dev-prereqs --self-test runs nowhere in CI — the second and only other member of #8110's split-gate family #8170,⛔ 未在本 PR 内顺手修。枚举方法可复核:62 个根 check:* × 24 份 workflow(剥掉注释行后匹配)→ 3 个未接线;再放宽到 63 个提及 --self-test 的 scripts/**/*.mjs → 无新候选。两个被排除的候选各写了排除理由(check:platform-checklist 的 scan 其实能在 CI 跑,它在外面是维护者的明确政策决定,不是 CI-hostility)。负结论也可审,这是我要的那一半。
    • D 我说得太宽,被修正。 我写「改 frame 文档不会让它变红」;实测:措辞类改动(改一行 axis 正文 / 三份文件同时改)两个门都绿;结构性改动(改掉声明句、越过共享锚点)会红 —— 但同一处改动会让正上方的 check:skill-frame-sync 以同样的诊断先红,而 self-test 的失败信息本身就写着「this is a pnpm check:skill-frame-sync failure first」。⇒ 准确的说法是「它不会单独把 PR 变红」,不是「它不会变红」。本 PR 不引入邻居没有的失败类别 —— 这个结论比我原来的假设更强,也更真。

    一条值得带走的普查陷阱

    dev 顺带记了一条:做 workflow 接线普查时必须先剥掉 YAML 注释再匹配 —— 否则一段「解释某门为什么没有接线」的注释会被读成「已接线」。本 PR 自己就加了 30 行这样的注释,是这条陷阱的活体样本。已记进座位贴 pitfalls。

    一处我看了但不拦的

    30 行注释配 1 行 step,比例悬殊。但这里的非显然决定恰恰是「省掉哪一半」 —— 而 #8024 的教训正是「门留在门清单里读起来像覆盖、实际什么都不保护」。把省略写成决定而不是遗漏,值这个篇幅。


    Generated by Claude Code

  9. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Note for the next surveyor (from #8229, recorded by the triage seat) — 本评论来自分诊座位 Routine.

    The survey method described on this card (enumerate root check:* scripts, grep .github/workflows/** with comment lines stripped, treat unmatched as unwired) has a measured false-positive now that split gates land as direct node scripts/<file> --self-test steps rather than pnpm check:<name> invocations: re-running the survey verbatim reports check:skill-frame-freshness (wired since PR #8171) and check:dev-prereqs (wired in PR #8227) as unwired.

    Refined criterion: a script counts as wired if EITHER its npm script name OR scripts/<its file> appears in an uncommented workflow line. Deliberate exclusions as of 2026-08-12: check:platform-checklist (out by policy), and the scan halves of the two split gates above (their workflow steps intentionally invoke only --self-test).

    Full analysis: #8229 (closed as delivered by this note).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions