Skip to content

scripts: a local Spec Main Shape Gate reproduction writes main-spec type-check verdicts into turbo's worktree-shared cache, so a later unforced installed-spec type-check in any worktree replays them (a false green) #12114

Description

@objectstack-fleet

Filing class: a mechanizable guard, measured by a dispatched dev and asked for by the maintainer. The maintainer asked for it in the domain:ui seat 3 session (session_01B1gHb9baeX7oioD5sHVm7z), verbatim, 2026-10-11: 「要开卡:发现一个可以做成机制的问题:在本地复现这个闸门时,turbo 的跨 worktree 共享缓存会把基于 main 版 spec 的结果,回放给基于已安装版 spec 的类型检查,得到假绿。复现时需要加 TURBO_FORCE=true。」 ⛔ Not graded or routed here: triage grades.

The measurement

From the os-dev-report 6100892115 on objectui#12085 (the dispatch that folded objectui#12093's gate fix, PR objectui#12100):

  • The dev reproduced Spec Main Shape Gate locally. They built @objectstack/spec from objectstack main, packed it, and injected it with node scripts/spec-main-shape-gate.mjs inject into a sibling git worktree, then ran the type-check there.
  • turbo 2.10 here uses a cache shared across worktrees (its log line: "using shared worktree cache").
  • A later unforced pnpm type-check against the INSTALLED spec, in another worktree with equal task hashes, reported 81/81. 80 of those were cache hits replayed from the main-spec run. The dev discarded the run as NOT MEASURED, and a TURBO_FORCE=true re-run (81/81 forced, 0 cache hits) gave the real verdict.

Why the script's existing guard does not cover it

scripts/spec-main-shape-gate.mjs's header section "The turbo cache is a FALSE GREEN here, and the workflow must bypass it" covers the gate's own run: turbo's type-check hash covers sources, the lockfile and a declared env list, but not the content of node_modules. So a run after an injection can replay the pre-injection verdict. The workflow runs TURBO_FORCE=true pnpm type-check (.github/workflows/spec-main-shape-gate.yml), and scripts/__tests__/spec-main-shape-gate.test.ts pins that.

This finding is the reverse direction, and local only:

  • the injected run's verdicts are written to a cache other worktrees read;
  • the next ordinary type-check in any worktree — a dev's normal pre-push gate against the installed spec — can replay them.

That is a green (or red) answer to a question about a spec the worktree does not have. CI is unaffected: it forces, and it shares no cache with a local worktree.

Direction (for triage to grade, not a ruling)

Make the local reproduction unable to poison the shared cache. Some candidate options:

  • inject refuses to finish without stating the guard, or the script's local recipe runs the type-check itself with TURBO_FORCE=true (or --force) and never writes cache entries for an injected tree;
  • an injected worktree gets its own cache directory (--cache-dir / TURBO_CACHE_DIR), so it never shares entries;
  • the injection marker becomes part of turbo's hash (for example a globalDependencies file that inject writes), so injected and installed trees can never share an entry.

Each option needs a pin: a local reproduction followed by an installed type-check in a sibling worktree must not report a cache hit for type-check.

Dedupe: an objectui issue search for turbo shared worktree cache, replay, Spec Main Shape Gate local reproduction and TURBO_FORCE finds six turbo-input and gate cards: objectui#4178, #4184, #4185, #6577, #7855 and #9944, all closed. None is this direction.

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, enhancement · priority:p3 · domain:devx · area:devpath · pm:queue. Maintainer-directed, so the tooling queue rule's entry lines do not apply

    Triage seat (seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T03:55Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Why it enters the queue: the maintainer asked for this mechanism verbatim (「要开卡:发现一个可以做成机制的问题 …」), which exempts it from the Unblocks: / published-surface entry lines.
    • Why p3: it is local only, and CI is unaffected because it forces and shares no cache. The harm is a false verdict in a developer's own pre-push type-check after a local gate reproduction.
    • Lane: scripts/ and the gate's local recipe are domain:devx in this repository.
    • Direction:
      • The cheapest durable option is an injected worktree that never shares turbo cache entries with an installed one: its own cache directory, or an injection marker inside turbo's hash.
      • Prefer the option that needs no remembered flag.
      • ⛔ No change to the CI workflow's TURBO_FORCE=true, which stays as pinned.
    • Pin, as filed: a local reproduction followed by an installed type-check in a sibling worktree reports no type-check cache hit.
  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    enhancementNew feature or request
    and removed on Oct 11, 2026
  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 83
    Session: session_01DwLS3LzXyNmyTunMGbpc8W
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee, label-write read-back MATCHES)
    Branch: claude/issue-12114-injected-tree-cache
    Worktree: objectui-issue-12114
    Domain: domain:devx
    Seat: domain:devx#1 (seat post objectui#5748, round-open marker 6106236990)
    File surface: scripts/spec-main-shape-gate.mjs (the inject path and its local recipe), scripts/__tests__/spec-main-shape-gate.test.ts, and turbo.json only if the chosen option puts an injection marker inside turbo's hash; ⛔ not .github/workflows/spec-main-shape-gate.yml (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default
    Clause-②: no
    Responsibility: n/a — not a defect card
    Thread-read: 6105209514
    Serial constraints cleared: objectui's 5 open PRs (#12122, #12089, #11845, #11844, #11600) touch none of the three paths; the newest origin/main touch to them is f88a900e (2026-10-02T01:47Z). The 6 open pm:dispatched objectui cards (#12121, #12109, #12098, #12082, #12081 in domain:ui; #7611 pm:epic) declare packages/** surfaces or none. The sibling dispatched this round, objectui#5250, works in scripts/check-doc-snippet-types.mjs, content/docs/** and examples/**, which is disjoint. Reading time 2026-10-11T06:28Z.

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12114,
    "status": "done",
    "branch": "claude/issue-12114-injected-tree-cache",
    "pr": "#12125",
    "session": "session_01DwLS3LzXyNmyTunMGbpc8W (subagent run; the id is the parent PM seat session)",
    "premise_still_valid": true,
    "summary": "The premise holds and is now measured. A linked worktree's turbo 2.10.9 writes to the main checkout's .turbo/cache ('using shared worktree cache'). --force skips cache reads but still writes (turbo help: '--cache=local:w,remote:w'), so even the CI recipe run locally poisons the cache. inject left every hash unchanged: the whole type-check graph had 83/83 task hashes equal before and after. Fix (option a): turbo.json globalDependencies names node_modules/@objectstack/spec/.spec-main-shape-gate.json. That is the marker inject already writes into every replaced copy, reached by a LITERAL path through the root link to the spec; a wildcard inside node_modules hashes nothing, silently (measured). Installed trees hash exactly as before. An injected tree hashes apart in both directions with no flag to remember (whole graph 0/83 equal), and 'pnpm install --force' removes the marker together with the injection. CI's TURBO_FORCE=true and the workflow are untouched.",
    "tests": "Live two-worktree reproduction, turbo run type-check --filter=@object-ui/types, with an identical untracked probe importing SpecGateProbe12114 from the injected spec. BEFORE (29b9949): A inject + TURBO_FORCE=true wrote e02dab71f230fcc0. B (installed) unforced: 'cache hit, replaying logs e02dab71f230fcc0', Cached 1/2, exit 0, a FALSE GREEN. B forced: TS2305 (true verdict). AFTER (81dea3f): installed A and B both hash 95eb3fecb9111eb5. A inject logs 'turbo hashes node_modules/@objectstack/spec/.spec-main-shape-gate.json' and the hash becomes 67c390de3fefe78f. A forced: green. B unforced: 'cache miss, executing 95eb3fecb9111eb5' then TS2305, Cached 0/2 (true verdict). A unforced: hits only its own 67c390de3fefe78f. 'pnpm install --frozen-lockfile --force' in A restores 95eb3fecb9111eb5. New pins: 3 (wiring; a real turbo --dry=json over a fixture git repo copying this turbo.json and driving the real inject CLI; a firing control without the root link). Red on main's script and turbo.json: 'Tests 3 failed | 28 passed (31)'. Ablation of the turbo.json entry only: 'Tests 2 failed | 29 passed (31)', with "expected [ 'package.json' ] to include 'node_modules/@objectstack/spec/.spec-...'" and the firing control green. Restore was proven by an empty git diff HEAD plus blob hashes equal to HEAD. No dist is involved (scripts run from source). Final head a1b9bd6: 13 derived test files 'Tests 625 passed (625)'; lint:root 0 errors; --self-test 26/26.",
    "mcp_calls": "0",
    "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, landed as objectstack-fleet[bot]). (1) pr_create = POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12125/assignees (zhuangjianguo); read-back: 9381 bytes sent, 9381 stored, identical. (2) this os-dev-report = POST /repos//issues/12114/comments. Plus 4 git pushes (not REST). 0 label writes (per the PM correction).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed. content/docs/guide/ci-cd-pipeline.md (Spec Main Shape Gate section) does not describe a local reproduction; the recipe (inject, then pnpm type-check --continue, then pnpm install --frozen-lockfile --force) lives only in the script header. Its existing claim that turbo does not hash the content of node_modules stays true. Not class a/b/c: a doc gap, no wrong answer at a public door."
    ],
    "gates": {
    "pnpm exec vitest run (13 files: git grep -l -e turbo.json -e spec-main-shape-gate under scripts/tests, helpers excluded) @a1b9bd68": 0,
    "pnpm exec vitest run scripts/tests/spec-main-shape-gate.test.ts @81dea3ff (31 passed; also in the 13-file run above)": 0,
    "pnpm lint:root @a1b9bd68 (0 errors, 34 warnings, none in touched files)": 0,
    "pnpm check:new-line-citations @a1b9bd68 (0 new citation(s))": 0,
    "pnpm check:control-bytes @a1b9bd68": 0,
    "node scripts/check-changeset-presence.mjs @a1b9bd68 ('7 file(s) changed, 0 of them published source ... no changeset is owed')": 0,
    "node scripts/spec-main-shape-gate.mjs --self-test (26/26)": 0,
    "node scripts/check-governed-queue-guard.mjs --test (7 paths: NOT GOVERNED)": 0,
    "CI on PR 12125 at report time": "in_progress (13 completed, 17 in progress; not waited on)"
    },
    "line_budget": {
    "scripts/spec-main-shape-gate.mjs": "+60 -7",
    "scripts/tests/spec-main-shape-gate.test.ts": "+161 -0",
    "turbo.json": "+1 -0",
    "scripts/tests/turbo-build-inputs.test.ts": "+1 -1",
    "scripts/tests/turbo-lint-inputs.test.ts": "+1 -1",
    "scripts/tests/turbo-test-inputs.test.ts": "+1 -1",
    "scripts/tests/turbo-type-check-inputs.test.ts": "+1 -1"
    },
    "deviations": [
    "FILE SURFACE: four comment lines in scripts/tests/turbo-{build,lint,test,type-check}-inputs.test.ts said 'globalDependencies is unset'. That became false with this change, so each now reads 'holds no source file'. Comment-only, under the charter rule that prose a change makes false must be fixed. No open PR touches these files. The seat should append them to the claim's file surface.",
    "The injected tarball was the installed published spec plus one probe export, not a spec built from objectstack main. The mechanism under test is turbo cache keying, which never reads the spec bytes, and the probe makes the false verdict visible. --upstream-checkout was the read-only shared /home/user/objectstack, which needed no dependency substitution.",
    "The live reproduction executed a filtered graph (@object-ui/types), not the full 'pnpm type-check'. The whole 83-task graph was covered by dry-run hash comparisons. The card's 80/81 figure was not re-run.",
    "Deleting the two inert cache entries this run wrote in /home/user/objectui/.turbo/cache (e02dab71f230fcc0, 67c390de3fefe78f; 3 files each) was refused by the auto-mode classifier as a shared-resource write. They remain. The first dry run had also created that gitignored directory (turbo's own behaviour).",
    "A git fetch into my own ref (deleted afterwards) auto-followed one remote tag into the shared .git: refs/tags/@object-ui/types@17.7.0.",
    "Commit trailers use the AGENTS.md model-free pair (Co-authored-by: Claude) rather than the harness's model-named Co-Authored-By line, per AGENTS.md precedence.",
    "No skip-changeset label, per the PM correction: the presence check passed with no changeset. The PR carries a 'tests' label that the labeler applied, not this run."
    ],
    "files_changed": [
    "scripts/spec-main-shape-gate.mjs",
    "scripts/tests/spec-main-shape-gate.test.ts",
    "turbo.json",
    "scripts/tests/turbo-build-inputs.test.ts",
    "scripts/tests/turbo-lint-inputs.test.ts",
    "scripts/tests/turbo-test-inputs.test.ts",
    "scripts/tests/turbo-type-check-inputs.test.ts"
    ]
    }

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT, PR objectui#12125 (2026-10-11T07:02Z)

    domain:devx @ objectui seat 1 (seat post objectui#5748), session_01DwLS3LzXyNmyTunMGbpc8W. Checked against GitHub at head a1b9bd688c, not against the report 6106471070.
    Implemented-by: claude/issue-12114-injected-tree-cache · Reviewed-by: this seat (no contract face is touched, so CI plus the seat's read is the review of record).

    Checklist

    • Shape: draft ✓ · base main ✓ · first line Fixes #12114, and it is the only closing keyword in the body ✓ · PR assignee zhuangjianguo ✓. Fixes is right: the PR delivers the card's pin as filed, both as a live two-worktree reproduction (before: cache hit, replaying logs, a false green; after: cache miss and the true TS2305) and as a pinned real turbo --dry=json run over a fixture.
    • Surface: 7 files. 3 are in the claim (scripts/spec-main-shape-gate.mjs, its test, turbo.json). The other 4 are one comment line each in scripts/__tests__/turbo-{build,lint,test,type-check}-inputs.test.ts (「globalDependencies is unset」, false once this lands). This is a bounded in-place fix (same change, comment only, no open PR holds those files), and this comment adds them to the claim's file surface. ⛔ .github/workflows/spec-main-shape-gate.yml is untouched, so CI's TURBO_FORCE=true pin stands.
    • Governed: check-governed-merges --pr objectstack-ai/objectui#12125 reads NOT governed, 0 of 7 paths, 237 changed lines (≤ 3000).
    • Changeset: none owed. check-changeset-presence reads 「7 file(s) changed, 0 of them published source … no changeset is owed」. Root scripts/, turbo.json and tests publish nothing. No label is needed.
    • Diff read by the seat: one literal globalDependencies entry (node_modules/@objectstack/spec/.spec-main-shape-gate.json). The marker inject already writes into each replaced copy is now in turbo's hash. inject logs whether the root link exists. The header's old sentence was rewritten so it stays true. The path relies on the root manifest declaring the spec: on objectui origin/main 29b99490, root package.json:145 reads "@objectstack/spec": "^17.0.0", and the new wiring pin fails if it ever stops.
    • Tests (report, not re-run by the seat): 3 new pins, red on main's script and turbo.json (3 failed | 28 passed). Ablating the turbo.json entry alone gives 2 failed, with the firing control green. 13 derived test files: 625 passed. --self-test 26/26.
    • CI at review: 39 runs on a1b9bd688c, 23 success, 3 skipped, 13 in progress (8 test shards, Type Check, Lint, Spec Main Shape Gate and 2 others). Landing waits for every check to be green.

    Findings

    • content/docs/guide/ci-cd-pipeline.md does not describe the local reproduction recipe: Acceptance notes. No wrong answer at a public door, carrier none.
    • Two inert entries left in the shared checkout's .turbo/cache (e02dab71f230fcc0, keyed on a deleted probe file; 67c390de3fefe78f, keyed on a unique injection marker): Acceptance notes. Neither can be hit.
    • A tag fetch that followed into the shared .git (refs/tags/@object-ui/types@17.7.0): dropped, since it mirrors the remote tag.

    Next: on all-green, pr_ready + automerge_enable through the relay. The seat follows the PR to MERGED.

  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (2026-10-11T07:39Z): PR objectui#12125 merged through the merge queue at 2026-10-11T07:38:30Z, merge commit a194b4e37. Fixes #12114 closed this card completed. domain:devx @ objectui seat 1, session_01DwLS3LzXyNmyTunMGbpc8W.

    • Landing probe on objectui origin/main a194b4e3: turbo.json:4 carries "globalDependencies": ["node_modules/@objectstack/spec/.spec-main-shape-gate.json"]. TURBO_HASH_MARKER appears in scripts/spec-main-shape-gate.mjs (4 hits) and in its test (9). All 4 turbo-*-inputs.test.ts comments read 「holds no source file」. Control in the same file: "globalEnv" hits 1.
    • Close-out: pm:dispatched and the assignee were removed in this act and read back MATCHES. enhancement, priority:p3, domain:devx and area:devpath stay.
    • No follow-up card. The two findings stay in the PR's Acceptance notes, per the ACCEPT 6106491265.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoenhancementNew feature or requestpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions