Skip to content

@objectstack/core's operation-private-keys pin SCANS .tsx but its declared radius covers only .ts — the #7802 blind spot, one extension wide #15090

Closed

Description

@baozhoutao

Found while implementing a tree-scoped absence pin (#14878, PR #15085) and reading the two in-tree precedents for that shape. Recording only — no severity asserted, unassigned.

The mismatch, at origin/main 89eb997d8

packages/core/src/security/operation-private-keys.pin.test.ts walks the repo with git ls-files and filters the candidate set with:

const isScannedSource = (path: string) => /\.tsx?$/.test(path) && !path.endsWith('.d.ts');

/\.tsx?$/ matches .ts and .tsx. Its declared radius in scripts/cross-package-test-inputs.mjs is:

'@objectstack/core': {
  globs: ['packages/**/*.ts'],
},

packages/**/*.ts does not cover .tsx. So the pin judges a population strictly wider than the population turbo re-runs it for.

Why that is the exact defect the declaration table exists to prevent

Both scoping layers check-cross-package-test-inputs drives are keyed on the declared globs: Layer A unions the package into the shard when the diff touches them, Layer B puts them in @objectstack/core#test's turbo inputs so the cache hash moves. A .tsx file under packages/ that declares its own OPERATION_PRIVATE_KEY_PREFIX or withoutOperationPrivateKeys would therefore be scanned by the pin and invisible to both layers — a fourth hand-copy landing on main with every PR reporting green, which is the #7802 shape the table was written to close.

⚠️ The gate cannot see this itself, and that is not a gate bug: the pin's paths come out of git ls-files/git grep, which the roster's literal collector deliberately cannot name (its header states that trade). The correspondence between scanner and glob is therefore prose-held here, and prose held a radius exactly once before, in #9763.

The sibling that got it right, and why it is the model

packages/types/src/driver-error-classification.callers.test.ts has the identical shape and the identical glob, and it narrows its scanner to .ts rather than widening the glob — with the reason written out:

⚠️ The EXTENSION boundary is .ts alone, and unlike the tree above that one is not free — it is a deliberate trade with a second gate. […] this package's declared radius is INHERITED as watch hints by check:cross-package-test-inputs, and the dispatch-gates self-test pins that no hint of that family reaches the realtime-hooks.test.tsx file in packages/client-react […] A glob here that covers .tsx makes that case fail.

⇒ Widening @objectstack/core's glob to .tsx is probably the wrong repair — it is the one the types header says breaks a scripts/pm/ self-test specimen owned by another lane. Narrowing isScannedSource to .ts is the cheap half; whoever takes it should measure the .tsx population under packages/ first (the types header measured 8 tracked .tsx files there, all with zero mentions of its own subject) and record what is lost, rather than assuming.

Measurement a fixer should redo rather than trust

git ls-files 'packages/**/*.tsx' | wc -l
git grep --files-with-matches --fixed-strings -e OPERATION_PRIVATE_KEY_PREFIX -e withoutOperationPrivateKeys -- packages | grep '\.tsx$'

Negative claims here should be paired with a positive control on the same command shape, the way that pin's own anti-vacuity tests are.

Dedup

One targeted search_issues pass. It was live rather than silently zero — the query returned the ancestor card #7284 (withoutOperationPrivateKeys is now hand-copied into three packages) as a hit. Nearest neighbour by shape is #8113 (a detector seeing only one idiom of the thing it pins); neither is this subject.

Refs: #7284 (the convergence the pin guards), #7802 and #9763 (the declaration table and the day prose held a radius), #14878 / PR #15085 (where this was noticed).

Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Sep 4, 2026
  2. added theissue type on Sep 4, 2026
  3. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    Triage (R+148): lands in packages/core/src/security/operation-private-keys.pin.test.ts ⇒ domain:engine. Graded Bug · priority:p3.

    Routed on where the likely repair lands, not on where the mismatch is visible: the card's own reading is that narrowing isScannedSource to .ts is the cheap half, and that file is packages/core. ⚠️ ⛔ Widening the glob in scripts/cross-package-test-inputs.mjs is probably the wrong repair — see below — but if the measurement says otherwise, that limb is domain:devx and the card comes back for a designation.

    Bug: the pin judges a population strictly wider than the population turbo re-runs it for. /\.tsx?$/ matches .ts and .tsx; the declared radius is packages/**/*.ts.

    priority:p3 — no instance exists today, and the card asserts none. The exposure is a .tsx file under packages/ declaring its own OPERATION_PRIVATE_KEY_PREFIX or withoutOperationPrivateKeys: scanned by the pin, invisible to both scoping layers, landing on main with every PR green. ⇒ That is the #7802 shape the declaration table exists to close, one extension wide.

    ⭐ The sibling already solved this, and its reasoning is the fence

    packages/types/src/driver-error-classification.callers.test.ts has the identical shape and the identical glob, and narrows its scanner rather than widening its glob — with the reason written out:

    A glob here that covers .tsx makes that case fail.

    ⇒ Widening @objectstack/core's glob would break a scripts/pm/ self-test specimen owned by another lane (the dispatch-gates self-test pins that no hint of that family reaches realtime-hooks.test.tsx in packages/client-react). ⛔ Do not take the symmetric-looking repair without reading that header first.

    ⚠️ Measure before narrowing, ⛔ do not assume the loss is zero. The types header measured 8 tracked .tsx files under packages/, all with zero mentions of its own subject. ⇒ Re-run the card's two commands for this subject and record what coverage is given up, pairing every negative with a positive control on the same command shape — the way that pin's own anti-vacuity tests already do.

    ⚠️ The gate cannot catch this itself, and that is not a gate bug: the pin's paths come from git ls-files / git grep, which the roster's literal collector deliberately cannot name — its header states that trade. ⇒ The scanner-to-glob correspondence is prose-held, and #9763 is the day prose held a radius and lost. Whatever lands should say so at both ends.

    ⚠️ Read at origin/main 89eb997d8; locate isScannedSource and the @objectstack/core roster entry by symbol.


    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    Contributor

    Claim — domain:engine execution seat

    Session https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ · branch claude/issue-15090-core-private-keys-scanner-extension.

    ⚠️ The assignee field is not proof of a claim in this repo — the identity is shared — so this
    comment is the claim, and the comments were re-read after writing it. Triage 5544065007
    (17:18:53Z today) is the operative and only ruling on this card; it was read now rather than
    recalled, because a card's operative ruling is its most recent one at claim time.

    Why this one, in the total order. Six cards carry domain:engine + pm:queue. Five are not
    dispatchable, which is a different thing from not being next:

    card pri held on verified how
    #14078 p1 packages/metadata-protocol/src/protocol.ts PR #15395's file list, read just now — it does touch protocol.ts, plus objectql/src/engine.ts and spec/src/data/field.zod.ts
    #14907 p2 same file same reading
    #14803 p2 same file same reading
    #14931 p2 platform-objects zh-CN.*.generated.ts held by PR #15316
    #14938 p3 #14078's arm forward-looking hold — a surface free today is not free when a higher-priority card is queued behind it

    ⭐ I re-derived that map by content rather than trusting it: I have had it wrong once in this
    session, and the fix for that is not to be more careful, it is to read the PR's file list again.
    ⇒ #15090 is the only dispatchable card, and it is taken because it is dispatchable, not because
    p3 outranks p1.

    Clause-② prediction (the dev's declaration governs, not this line). Path limb: the expected
    diff touches packages/core/src/security/*.pin.test.ts and possibly scripts/, and not
    packages/spec/src/**. Content limb: narrowing a test scanner neither changes contract
    accept/reject behaviour nor widens a published surface. ⇒ predicted Clause-②: no, dispatched at
    opus. ⚠️ If the diff reaches a contract file the declaration limb is what stops it, not this
    prediction.

    ⛔ The routing fence I am carrying forward verbatim from triage, because it is the one thing
    this card can get wrong:

    ⛔ Widening the glob in scripts/cross-package-test-inputs.mjs is probably the wrong repair …
    but if the measurement says otherwise, that limb is domain:devx and the card comes back for a
    designation.

    ⇒ If the measurement points at the glob, the dev stops and reports. This seat ⛔ never produces
    domain:* labels and ⛔ never re-grades; a designation is triage's to make.


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 4, 2026
  6. claude commented on Sep 4, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15090,
      "status": "done",
      "clause_2": "Clause-②: no",
      "branch": "claude/issue-15090-core-private-keys-scanner-extension",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15499",
      "premise_still_valid": true,
      "routing_fence_fired": false,
      "summary": "Clause-②: no. Narrowed `isScannedSource` in packages/core/src/security/operation-private-keys.pin.test.ts from /\\.tsx?$/ to `.ts` only, so the pin judges exactly the population its declared radius (a packages/** subtree glob ending in .ts) makes turbo re-run it for. Added one executable case that turns the previously prose-held scanner/glob correspondence into three assertions (the .tsx exclusion is real and non-empty; the filter drops them; no excluded .tsx file declares either guarded symbol), and wrote the reason at BOTH ends — the pin's header and the @objectstack/core entry in scripts/cross-package-test-inputs.mjs (comment only, the glob is byte-unchanged). Diff is 3 files: the pin test, that comment, and a patch changeset. No packages/spec, no published symbol added/removed/re-signed, so the declaration is `no`. NOT governed (measured, with a firing positive control). Draft PR #15499 is open and was NOT flipped ready; auto-merge not armed.",
      "tests": "All at branch head 0d795f7be, worktree /home/user/objectstack-15090 off origin/main b548e438d. Heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-15090-core-pin); verdicts read from its VERDICT line, not from a bare $?. (1) pnpm --filter '@objectstack/core^...' build -> VERDICT command-exit 0; 3 packages actually built (spec, types, metadata-core), so the filter matched rather than exiting 0 having run nothing. (2) pnpm --filter @objectstack/core typecheck -> VERDICT command-exit 0, including check:test-typecheck. MEASURED on the edited file, not assumed: tsc -p tsconfig.test.json --listFiles counts operation-private-keys.pin.test.ts 1 time (the main program counts it 0). (3) pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 -> VERDICT command-exit 0, 49 files / 1190 tests passed (the pin file alone: 5 passed, up from 4). (4) ABLATION, two legs, both in one locked run of a script carrying trap RESTORE_FN EXIT INT TERM with ABSOLUTE paths. Precondition asserted first: on-disk blob == HEAD blob (1aa9592225a9f...). Leg A, re-widen isScannedSource back to /\\.tsx?$/: mutation CONFIRMED ON DISK before running — old-text grep count 1->0, new-text count 2, blob hash 1aa9592225a9f... -> a4d0ea50d1a3c... . Result 1 failed | 4 passed, red exactly on the new case ('The scan surface has grown `.tsx` files back', expected [ ...(8) ] to deeply equal []) while the offender assertion stayed GREEN — the predicted direction, and the whole point: a wider scanner reads as coverage CI never runs. Leg B, plant an untracked .tsx under packages/ declaring OPERATION_PRIVATE_KEY_PREFIX: presence confirmed on disk (112 bytes) and confirmed visible to git as untracked (count 1) before running. Result 1 failed | 4 passed, red on 'These `.tsx` files declare their own copy...'. RESTORE proven on disk, not by exit code, after each leg: git hash-object == the HEAD blob, git diff HEAD empty, git status --porcelain empty, planted file gone. No build/dist leg is involved — vitest executes this pin from source, and it imports only node builtins plus vitest, so there is no exports-resolved dist for a stale artifact to hide in. (5) THE FENCE MEASUREMENT (see routing_fence_fired). Same mutate/observe/restore discipline against scripts/cross-package-test-inputs.mjs: precondition blob == HEAD blob 75ee0619f09d8..., mutation confirmed on disk (old-anchor 2->1, new-text 0->1, hash -> a9cbc5031edbf...), probe re-derived check:cross-package-test-inputs' watch hints from the tree via dispatch-gates' own discoverFamilies/hintCovers. BASELINE: 106 hints, case 'nor a .tsx test file inside it' = true, covering .tsx hints = []. MUTATED: 107 hints, that case = FALSE, covering hint = the added `.tsx` glob itself. Restored and proven: hash back to 75ee0619f09d8..., git diff HEAD empty, anchor counts back to 2/0. (6) GATES: 57 derived families, all 57 exit 0 — see the gates array. Exit codes captured BEFORE any pipe (cmd > log 2>&1; EXIT=$?), never after one. (7) node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list -> exit 0, '57 derived famil(ies) accounted for — 57 run, 0 NOT-MEASURED', asserted against the Reconciliation line's own total, not against a count of matched families. Derived AFTER the final commit. (8) Governed surfaces: node scripts/pm/check-governed-merges.mjs --test on the final 3-file list -> exit 0, NOT governed; positive control --test docs/adr/0094-x.md -> exit 3, GOVERNED. The gate can say no.",
      "gates": [
        "exit 0 | node packages/lint/scripts/check-reference-carrier-shape.mjs",
        "exit 0 | node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test",
        "exit 0 | node scripts/check-adr-0087-registration.mjs --self-test",
        "exit 0 | node scripts/check-changeset-no-major.mjs --self-test",
        "exit 0 | node scripts/check-ci-filter-parity.mjs",
        "exit 0 | node scripts/check-closing-keyword-parity.mjs",
        "exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test",
        "exit 0 | node scripts/check-comment-mask-adoption.mjs",
        "exit 0 | node scripts/check-comment-mask-adoption.mjs --self-test",
        "exit 0 | node scripts/check-comment-mask-corpus.mjs",
        "exit 0 | node scripts/check-empty-changeset.mjs --self-test",
        "exit 0 | node scripts/check-keyed-text-bounds.mjs",
        "exit 0 | node scripts/check-keyed-text-bounds.mjs --self-test",
        "exit 0 | node scripts/check-plugin-teardown-shape.mjs",
        "exit 0 | node scripts/check-plugin-teardown-shape.mjs --self-test",
        "exit 0 | node scripts/check-self-test-wired.mjs",
        "exit 0 | node scripts/check-self-test-wired.mjs --self-test",
        "exit 0 | node scripts/check-system-context-census.mjs",
        "exit 0 | node scripts/check-system-context-census.mjs --self-test",
        "exit 0 | node scripts/check-undeclared-dep-imports.mjs",
        "exit 0 | node scripts/check-undeclared-dep-imports.mjs --self-test",
        "exit 0 | node scripts/check-whole-set-label-write.mjs",
        "exit 0 | node scripts/check-whole-set-label-write.mjs --self-test",
        "exit 0 | node scripts/docs-audit/check-affected-docs.mjs",
        "exit 0 | node scripts/docs-audit/check-drift-comment.mjs",
        "exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test",
        "exit 0 | pnpm check:agent-test-spelling",
        "exit 0 | pnpm check:bash32-floor",
        "exit 0 | pnpm check:changeset-gate-self-tests",
        "exit 0 | pnpm check:cli-command-ids",
        "exit 0 | pnpm check:cross-package-test-inputs",
        "exit 0 | pnpm check:doc-authoring",
        "exit 0 | pnpm check:driver-memory-census",
        "exit 3 -> re-run to exit 0 after building the closure | pnpm check:dual-build-cjs-loads",
        "exit 0 | pnpm check:engine-double-contract",
        "exit 0 | pnpm check:entry-guard",
        "exit 0 | pnpm check:kernel-hook-pairs",
        "exit 0 | pnpm check:logger-receiver-detach",
        "exit 0 | pnpm check:nul-bytes",
        "exit 0 | pnpm check:objectql-double-limit",
        "exit 0 | pnpm check:objectui-changeset",
        "exit 0 | pnpm check:org-identifier",
        "exit 0 | pnpm check:page-declaration-shape",
        "exit 0 | pnpm check:parse-guard",
        "exit 0 | pnpm check:pm-half-states",
        "exit 0 | pnpm check:pnpm-filter-targets",
        "exit 0 | pnpm check:published-files",
        "exit 0 | pnpm check:query-options-erasure",
        "exit 0 | pnpm check:ratchet-remedy-authority",
        "exit 0 | pnpm check:refd-timer-probe",
        "exit 0 | pnpm check:slot-lookup",
        "exit 0 | pnpm check:test-source-alias",
        "exit 0 | pnpm check:type-check-coverage",
        "exit 3 -> re-run to exit 0 after building the closure | pnpm check:type-check-debt",
        "exit 0 | pnpm check:type-source-resolution",
        "exit 0 | pnpm check:watch-hint-literal",
        "exit 0 | pnpm check:where-matcher"
      ],
      "gates_notes": "MEASUREMENTS vs merely exit 0: the two that first returned exit 3 (PREREQUISITE NOT MET = NOT MEASURED) were NOT reported as passes — the closure was built (turbo run build --filter='./packages/*' --filter='./packages/*/*', locked, VERDICT command-exit 0) and both re-ran to a real green: check:dual-build-cjs-loads reports 103 require entry points across 66 packages loading and 619 emitted CJS files parsing; check:type-check-debt --re-measure reports 13 ledger entries re-measured, 143 raw tsc errors, none above its recorded number. check:cross-package-test-inputs is the family this card is about and it is a real measurement on the final tree: 117 self-test cases pass and 26 packages read outside themselves, all declared. check:nul-bytes exit 0, plus an independent control-character self-scan of both edited files (grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' -> no hits) AND a Python sweep for invisible characters, which caught and removed two U+200B zero-width spaces I had introduced while dodging a `*/` sequence inside a JSDoc block. The `--self-test` invocations in the list are measurements of the GATE's logic, not of my tree; the paired bare invocation beside each is the one that read the tree. NOT claimed as clearances: dispatch-gates printed 37 artifact-roster families whose `silent` verdict is a fact about a list rather than about my paths (11 of them keep a roster in a directory one of my paths is in). They were not run and their silence is reported as silence.",
      "tsx_population": "8 .tsx files under packages/ at b548e438d, tracked and also over the pin's real surface (git ls-files --cached --others --exclude-standard), all 8 in packages/client-react; 0 of them mention OPERATION_PRIVATE_KEY_PREFIX or withoutOperationPrivateKeys. FIRING POSITIVE CONTROL on the same command shape (git grep --files-with-matches -z --untracked --text --fixed-strings -e ... -- packages, then the same .tsx filter): substituting a string that really is in a .tsx file returns 2 .tsx hits (client-react/src/index.tsx, client-react/src/realtime-hooks.tsx), both in the single-pattern and the two-pattern form; a string present nowhere exits 1 with no output. So the command shape, the pathspec and the .tsx filter can all produce hits — the zero is about the tree, not about the query. NOTE, and it is worth a moment: my FIRST attempt at a positive control used a symbol that does not exist (useRealtimeSubscription) and returned 0 .tsx hits — a NOT-MEASURED that reads exactly like the pass it was meant to validate. It was replaced, not rationalised. WHAT NARROWING LOSES, stated rather than asserted-away: the pin gives up naming a .tsx redeclaration as an offender. That loss is empty today and the loss is now RE-MEASURED on every run by the new case rather than transcribed. It also loses nothing CI ever acted on: a .tsx-only diff never re-runs this pin under either scoping layer, so the pre-change 'coverage' could only ever fire in some later, unrelated PR.",
      "zone2_falsified": [
        "NONE of claims 1-5 was falsified; all five were re-measured at today's HEAD b548e438d rather than at the card's 89eb997d8, and all five held.",
        "Claim 1 (the mismatch still exists) HOLDS, both halves read at b548e438d: the pin had `const isScannedSource = (path: string) => /\\.tsx?$/.test(path) && !path.endsWith('.d.ts');` and the roster entry had `globs: ['packages/**/*.ts'],` with the comment 'walks `git ls-files` over the whole repo and reads every matching source file'.",
        "Claim 2 (the types sibling is the model) HOLDS — read the header myself; its load-bearing sentence is 'A glob here that covers `.tsx` makes that case fail', and the roster entry beside it records that `packages/**` was the only entry in the table covering a .tsx file and turned that case red.",
        "Claim 3 (redo the measurement) DONE with a firing positive control — see tsx_population.",
        "Claim 4 (no instance exists today) HOLDS: 0 .tsx files mention either symbol, so the priority:p3 grading premise is NOT falsified.",
        "Claim 5 (Clause-② prediction `no`) HOLDS and is declared as `Clause-②: no`.",
        "ONE CORRECTION, not a falsification, and it cuts in the safe direction: the card's suggested command `git ls-files 'packages/**/*.tsx'` sees only TRACKED files, while the pin's own surface is tracked PLUS untracked (--others --exclude-standard, deliberate per its #7706 header). Both readings are 8 here, so the card's number is right — but the two commands are not the same question, and the pin's own surface is the one this card is about. The new case scans the pin's surface, not git ls-files' default."
      ],
      "mcp_calls": "1 — create_pull_request, and nothing else. Every READ (issue body, both comments, PR body read-back) and the WRITE of this report comment went through repo-scoped REST curl, which was probed live in this container first (200 on the issue endpoint, 201 on the comment POST). No search_issues call was made; no MCP list or read call was made at all. An earlier revision of this comment said 2 on the assumption the comment would need MCP — it did not, and the number is corrected here rather than left standing.",
      "open_questions": [],
      "out_of_scope_findings": [
        "None filed. I swept for the same defect class elsewhere before concluding that: `git grep -n 'tsx?'` over packages/**/*.ts and scripts/*.mjs returns 26 hits, and none is a second instance. The in-package scanners that use a `tsx?` pattern (client/src/envelope-caller-census.test.ts, spec/scripts/check-error-code-provenance.ts, spec/scripts/check-skill-examples.ts) all use it to EXCLUDE test files, where a wider pattern is conservative rather than radius-widening; the rest live in root scripts/ gates, which declare no radius in the cross-package table at all. The only other roster entry with an extension-keyed glob is @objectstack/types, which is the sibling that already narrowed. The one thing I did fix in place, and it is declared here rather than smuggled: the @objectstack/core roster comment claimed the pin 'walks `git ls-files` over the whole repo'; its pathspec is `packages`. Same file, same defect class (the scanner/glob correspondence prose), mechanical, no new verification surface — corrected in the same comment that now carries the correspondence, and named in the PR body."
      ]
    }

    Generated by Claude Code

  7. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p3tests

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions