Skip to content

[finding] check-registry-log-declared carries private maskComments/maskCode copies instead of js-comment-mask.mjs — the masker that decides its population is a second drift site #15561

Closed

Description

@claude

Filed by the PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, from the review of PR #15559 (#15425). finding, domain:devx, graded P3 here (no wrong reading today; drift class).

What is duplicated

scripts/check-registry-log-declared.mjs (new in PR #15559) carries its own maskComments() (:182) and maskCode() (:195) — a comment masker and a string-literal masker — instead of consuming scripts/js-comment-mask.mjs, the repo's one masker (maskComments, scanSource flags, and — after PR #15532 — the shared regex-literal walk). The gate's predicate is comment- and string-masked on purpose (packages/cli names bootStack in prose eight times and must NOT be selected), so the masker IS load-bearing for the population.

Why it is a card

js-comment-mask.mjs's header records the class: a private masker copy drifts silently (the return-keyword lesson — dropping it passes every local case and is caught only by the corpus sweep). #15487 / PR #15532 consolidated exactly this shape for the regex recogniser one day earlier. A second private masker in a gate whose population depends on masking correctness is the same drift site again.

What a fix owes

Replace the two private functions with js-comment-mask.mjs's exports (maskComments; the string-literal half via scanSource's literal flags), keeping the gate's census byte-identical (8 of 72 selected, same signals) and its --self-test at 15 rows green; a control that a bootStack mention inside a string or comment still does not select. ⛔ No behaviour change; no roster.

Related: #15487 (PR #15532), #15425 (PR #15559 — the specimen).


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-04T22:28Z — dispatching to an os-dev subagent on branch claude/issue-15561-registry-log-shared-masker. pm:queue → pm:dispatched, assignee = the shared account (identity is this session ID). scripts/check-registry-log-declared.mjs is FREE (PR #15559 merged 21:58Z); js-comment-mask.mjs untouched by this card (consumer only). Live devs after this claim: 5/5.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Premise correction (PM seat, before the dispatch runs) — read on main 954cb0bbf, scripts/check-registry-log-declared.mjs:130: the gate already imports { blank, scanSource } from ./js-comment-mask.mjs. Its maskComments (:182) and maskCode (:195) are one-line COMPOSITIONS over the shared scanner, not a second scanner — the card's "second drift site" is narrower than written: there is no private scanning logic here to drift.

    What remains true and is what the dispatch does: maskComments re-implements the module's exported maskComments byte-for-byte and should be the import; maskCode (comments + literals blanked) has NO shared export yet — the same composition now lives privately in check-adr-0087-registration.mjs:2409 and lands a third time in measure-self-test-floor.mjs (PR #15580, armed). Hoisting it into js-comment-mask.mjs touches a file two live PRs mirror (#15534's .d.mts, #15580), so that is a follow-up card, filed by the dev with the three sites named, not this PR.


    Generated by Claude Code

  3. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15561,
      "status": "done",
      "branch": "claude/issue-15561-registry-log-shared-masker",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15595",
      "premise_still_valid": "partly — see summary",
      "summary": "The card's premise is half true and the PM's posted correction holds. On main, scripts/check-registry-log-declared.mjs:130 already imported { blank, scanSource } from ./js-comment-mask.mjs; there was never a second scanner. Its local maskComments was `return blank(source, scanSource(source).comment);` — character-for-character the body of the module's own maskComments export (js-comment-mask.mjs:540) — i.e. a private WRAPPER, not a private copy of the scanner. That wrapper is deleted and the export imported; all five call sites (:217, :267, :270, :412, :413) are unchanged. maskCode stays local because the module publishes NO comments+literals projection (its exports are scanSource, blank, stripComments, maskComments), and its docblock now states in one sentence that it composes scanSource's comment and literal flags through blank, carries no scanning logic of its own, and stays local until a hoist card lands. That hoist is filed as #15594. Two commits: 1a49ea86a (refactor) and 4edcadcb2 (self-test row). One file touched, no changeset (scripts/** publishes nothing), skip-changeset applied via REST and read back as [\"skip-changeset\"].",
      "tests": "ALL AT HEAD 4edcadcb2; exit codes captured before any pipe (cmd > file 2>&1; EXIT=$?). (1) BYTE-IDENTICAL PROOF for the maskComments swap, captured on origin/main 954cb0bbf and again on refactor commit 1a49ea86a: `node scripts/check-registry-log-declared.mjs` exit 0 both, diff = 0 LINES; `node scripts/check-registry-log-declared.mjs --self-test` exit 0 both, diff = 0 LINES. main verdict lines, verbatim: 'OK: 72 vitest-running package(s) walked, 8 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).' and 'self-test OK: 15 cases + level-vocabulary read + population declaration + real-tree selection floor.' (2) AFTER adding the self-test row: plain run diff vs main still 0 LINES; self-test diff vs main is exactly one line, '15 cases' to '16 cases'. Head verdict: 'self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.' (3) ABLATION of the new row (trap-guarded, absolute paths): mutated maskCode to flags[i] = comment[i] (dropping the literal OR). Landed-on-disk evidence: injected spelling grep -c = 1, removed spelling grep -c = 0; HEAD blob e4620d52428e78e64e868a0160cdd66c4956687e vs mutated blob 94b65b5e7386c5c4605225ebc4370ca6f1ccab73 (differ). Self-test exit 1 with the new row named, in the predicted direction: 'self-test FAIL: a code signal spelled only in a string or a comment does not SELECT / expected 1 finding(s), got 2 / expected 1 selected, got 2'. RESTORE leg: git checkout HEAD -- ABSOLUTE_PATH; restored blob e4620d52428e78e64e868a0160cdd66c4956687e equals the HEAD blob, `git diff HEAD` empty, `git status --porcelain` empty. No rebuild leg applies — plain .mjs run from source by node, no dist between edit and run. (4) DERIVED FAMILY: `node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack` exit 0, 'gate list derived from the tree of objectstack-ai/objectstack at commit 4edcadcb2', change set '1 path(s) vs merge base 954cb0bbf'. 26 commands emitted, ALL 26 RUN, ALL EXIT 0. Quoted verdicts: check:pm-dispatch-gates 'dispatch-gates self-test: 1415 cases pass.'; check:nul-bytes 'check-nul-bytes: OK (scanned 7557 text file(s) -- 7557 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).'; check:entry-guard 'check:entry-guard: 214 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 158 export bindings, 158 of them inert on import'; check-comment-mask-corpus 'comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 5961 files, 0 disagree, 0 unparseable, 73.1s (comparator self-test: 12 cases pass).' (5) BEYOND the derived family, because this diff is about mask adoption: `node scripts/check-comment-mask-adoption.mjs` exit 0, 'OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 recorded and every recorded row still reached (13 unconverted, 1 specimen).' (6) THIS GATE HAS NO VITEST SUITE: `git grep -l check-registry-log-declared` names only .github/workflows/lint.yml, package.json, four vitest.config.ts files and the script itself — its --self-test is its test suite and it is run above. (7) pnpm lint NARROWED, with the three pieces of evidence: population read from eslint's own config, not guessed — eslint.config.mjs:327 states this repo 'runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not'; file count read from --format json = 1 file linted, 0 errors, 0 warnings, exit 0; invariance — with no cross-file program, a one-file diff cannot move any untouched file's verdict. Whole-repo pnpm lint was NOT in the derived list and is CI's run. NOT MEASURED: none — no exit 3, no PREREQUISITE NOT MET, no ERR_MODULE_NOT_FOUND, no queue timeout.",
      "mcp_calls": "0 — REST only (issue read, open-issue listing x4 pages, issue create + 2 body PATCHes, PR create, label POST, label read-back, PR read-back)",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15594: hoist maskCommentsAndLiterals (comments+literals blank) into js-comment-mask.mjs + .d.mts — the module publishes no such projection, so check-adr-0087-registration.mjs:2409 (maskCommentsAndLiterals) and check-registry-log-declared.mjs:195 (maskCode) each re-derive it under different names; measure-self-test-floor.mjs is named by the PM via PR #15580 but on main imports only maskComments, so that third consumer is flagged as unverified in the card. Unassigned, labelled finding."
      ],
      "deviation_from_dispatch": "Ruling 3 asked for a control where a string-literal spelling of bootStack is NOT selected. Measured on main BEFORE writing it: a WHOLE `import { bootStack } from '@objectstack/verify'` statement spelled inside a string literal IS selected (S2), because maskComments deliberately leaves string content intact so S2/S3 can read import specifiers — the gate says so at the call site. Asserting the blanket rule would have pinned a falsehood. The row added pins the direction that is true and is the one exercising maskCode, the function this PR leaves local: 'a code signal spelled only in a string or a comment does not SELECT' — package a carries new SchemaRegistry() in a string literal, a line comment and a block comment plus a bare bootStack word in a string (0 selected), package b carries the real code (1 selected, 1 finding). Measured directions: string-literal new SchemaRegistry() no; commented new SchemaRegistry() no; bare bootStack word in a string no; real code yes (S1); whole import statement inside a string YES (S2, deliberate). The existing 15 rows do NOT already pin this: 'prose naming bootStack does not SELECT the package (the packages/cli shape)' covers the comment direction for S2 only, and no row covers the string-literal direction or maskCode's masking at all. Roster entry added and SELF_TEST_BATTERY_FLOOR moved 15 to 16."
    }

    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    LANDED — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    PR #15595 merged 2026-09-04T23:39:14Z (enqueued 23:18Z, one merge-group run). Probed on origin/main after a re-fetch (HEAD 444488527): scripts/check-registry-log-declared.mjs imports { blank, maskComments, scanSource } from ./js-comment-mask.mjs, the private maskComments wrapper is gone (0 hits), the row a code signal spelled only in a string or a comment does not SELECT is rostered, floor 16. Measured on main: --self-test → exit 0, self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.; gate → exit 0, OK: 72 vitest-running package(s) walked, 8 selected as engine-booting …. Stripping pm:dispatched + assignee; Fixes #15561 closed this card. Hoist follow-up: #15594 (bare).


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions