Skip to content

fix(user.js): derive SCRIPT_VERSION from GM_info, enforce @version parity - #295

Merged
netravnen merged 5 commits into
dev-nextfrom
fix/script-version-from-gm-info
Aug 19, 2026
Merged

netravnen merged 5 commits into
dev-nextfrom
fix/script-version-from-gm-info

Conversation

@netravnen

Copy link
Copy Markdown
Contributor

Stack created with GitHub Stacks CLI • Give Feedback 💬

The three userscripts are about to read their version from Tampermonkey's
GM_info instead of a hand-maintained literal. The test sandbox in
tests/helpers/browser-shim.js evaluates each whole .user.js through
vm.runInContext and defines no GM_info, so that change would abort every
IIFE before it exports its window.__PDB_TEST__ hooks -- surfacing as a
misleading "did not expose window.<hooksKey>" error across 17 test files.

Land the stub on its own first so the scripts have somewhere to read from
when they switch over. It is inert until then.

Changes:
- Added GM_info: { script: { version: '0.0.0-test' } } to the vm sandbox,
  alongside the existing localStorage, fetch and MutationObserver fakes.
- Used a fixed sentinel rather than the real header version, so a later
  test can assert the GM_info path is genuinely the one being taken.

Security:
- N/A -- test harness only, never inlined into a generated .user.js.

Testing:
- node --test from user.js/: 470 tests, 469 pass, 1 skipped (live tests
  are opt-in). Identical to the pre-commit baseline, as expected for an
  addition nothing reads yet.

Backwards Compatibility:
- No production code touched. The sandbox gains a key no existing test
  references.

Assisted-by: Claude:claude-opus-5
SCRIPT_VERSION was a hand-maintained string constant that had drifted two
patch releases behind the @Version this script actually ships under
(2.0.223 against a 2.0.225 header). That is not cosmetic here: CP stamps
SCRIPT_VERSION into four persisted audit logs -- orgUpdateAuditLog,
ixlanRenumberAuditLog, ixfMemberAuditLog and ixlanConflictResolveAuditLog
-- which are the forensic record of every destructive write the IXLAN
Renumber, IX-F Merge and Conflict Resolver flows perform. Entries were
therefore attributing DELETEs to a build that never ran.

Tampermonkey injects GM_info into every userscript regardless of @grant,
and GM_info.script.version is bound at install time to the @Version
header, so reading it removes the constant as an independent source of
truth rather than merely resyncing it.

Changes:
- Replaced the SCRIPT_VERSION literal with a read of GM_info.script.version,
  guarded by a typeof check that falls back to "0.0.0-unknown" outside
  Tampermonkey (Greasemonkey 4 exposes GM.info, not GM_info).
- Exported SCRIPT_VERSION on window.__pdbCpTestHooks__ so the value can be
  asserted from the test suite.
- Regenerated peeringdb-cp-consolidated-tools.user.js.
- Kept the expression inline rather than extracting a lib/admincom-common.js
  helper: the @include marker sits below the constant, so a shared helper
  would depend on function-declaration hoisting across the inlined block.

Security:
- Improves audit-log integrity. Version attribution on recorded DELETEs is
  now derived from the installed script rather than a constant nobody was
  obliged to update.
- No new grant, no new network destination, no change to what is persisted
  beyond the value of an existing field.

Testing:
- node --test from user.js/: 473 tests, 472 pass, 1 skipped (live tests are
  opt-in), up from 470/469 with the new assertion.
- Added a case asserting hooks.SCRIPT_VERSION equals the sandbox stub, which
  fails if the literal returns or if the fallback branch silently engages.
- Verified the guard is not vacuous: removing GM_info from the shim fails
  that one case with a legible value mismatch rather than erroring out.
- node --check passes on the regenerated .user.js.

Backwards Compatibility:
- Audit-log entries keep the same "version" field and shape; only the value
  changes, and only to the correct one. Existing persisted entries are
  untouched.
- No storage key, cache namespace, module ID or route boundary changed.

Assisted-by: Claude:claude-opus-5
Same defect as the preceding CP commit: SCRIPT_VERSION was a
hand-maintained literal that had drifted four patch releases behind the
@Version this script ships under (1.1.34 against a 1.1.38 header). FP
surfaces the value in its self-check console warning and its init debug
line, so both were reporting a build that never ran -- misleading exactly
when someone is reading them to diagnose something.

Reading GM_info.script.version removes the constant as an independent
source of truth. Tampermonkey injects GM_info regardless of @grant and
binds it to the @Version header at install time.

Changes:
- Replaced the SCRIPT_VERSION literal with a read of GM_info.script.version,
  guarded by a typeof check falling back to "0.0.0-unknown" outside
  Tampermonkey.
- Exported SCRIPT_VERSION on window.__pdbFpTestHooks__.
- Regenerated peeringdb-fp-consolidated-tools.user.js.

Security:
- N/A -- FP reports the version to the console only; it does not persist it
  to an audit log or transmit it anywhere.

Testing:
- node --test from user.js/: 473 tests, 472 pass, 1 skipped (live tests are
  opt-in).
- Added a case asserting hooks.SCRIPT_VERSION equals the sandbox stub.
- node --check passes on the regenerated .user.js.

Backwards Compatibility:
- Console output keeps its existing format; only the version value changes,
  and only to the correct one.
- No storage key, cache namespace, module ID or route boundary changed.

Assisted-by: Claude:claude-opus-5
Completes the version-drift fix across all three scripts. DeskPro's case
differs from CP's and FP's: its SCRIPT_VERSION was declared and then never
read anywhere, so nothing was reporting a wrong version -- it was reporting
none at all. The constant had still drifted (1.7.7 against a 1.7.9 header),
which is what a value nobody consumes tends to do.

Rather than delete it, wire it up the way the other two scripts already do,
so all three answer "which build is this?" the same way when someone turns
diagnostics on.

Changes:
- Replaced the SCRIPT_VERSION literal with a read of GM_info.script.version,
  guarded by a typeof check falling back to "0.0.0-unknown" outside
  Tampermonkey.
- Added a dbg("init", `v${SCRIPT_VERSION}`, { path }) line at the top of
  init(), mirroring the equivalent lines in CP and FP. dbg() is already in
  scope via the lib/admincom-common.js include, and DP defines both
  MODULE_PREFIX and isFeatureEnabled that it depends on.
- Used { path: location.pathname } as context, since DP does no route
  dispatch and has no route-context object to draw CP's entity/entityId or
  FP's type/id from.
- Exported SCRIPT_VERSION on window.__pdbDpTestHooks__.
- Regenerated peeringdb-deskpro-tools.user.js.

Security:
- N/A -- the new output is console-only and gated behind the debugMode
  feature flag plus the pdbAdmincom.debug localStorage flag, both of which
  are opt-in. DP's debugMode defaults to false, matching FP.

Testing:
- node --test from user.js/: 473 tests, 472 pass, 1 skipped (live tests are
  opt-in).
- Added a case asserting hooks.SCRIPT_VERSION equals the sandbox stub.
- node --check passes on the regenerated .user.js.

Backwards Compatibility:
- Silent by default: the init line produces no output unless a user has
  already enabled both debug flags.
- No storage key, cache namespace or DeskPro DOM behavior changed.

Assisted-by: Claude:claude-opus-5
Tampermonkey polls the .meta.js to decide whether an update exists, but
installs the .user.js generated from the .src.js. When those two headers
disagree the update check and the shipped code describe different
versions: admins either never see an update that exists, or install one
whose reported version is wrong. AGENTS.md asked a human to bump both and
nothing enforced it, so all three scripts had drifted -- which is how the
stale SCRIPT_VERSION constants this series started with went unnoticed.

With SCRIPT_VERSION now derived from GM_info.script.version, the header
is the single source of truth and this pair is the last place drift can
hide. All three pairs currently agree, so the check passes on
introduction.

Changes:
- check_version_parity() compares the two headers and returns a message
  rather than raising, so one run reports every bad pair instead of
  stopping at the first. It runs on every invocation, not only --check:
  catching a mismatch only in CI means the person who caused it never
  sees it.
- A missing .meta.js is an error, not a silent skip. A script without a
  manifest never auto-updates, and skipping would make the check quietly
  cover less than it appears to.
- A header carrying no @Version at all gets its own message -- nothing to
  compare is a different problem from two values disagreeing, and a
  shared message would send the reader hunting for a value that is not
  there.
- header_block() limits the search to the ==UserScript== block. A .src.js
  is ~11,600 lines of JavaScript and a stray `// @version` comment below
  it should not be a candidate.
- VERSION_RE tolerates the differing header alignment between the two
  files (6 spaces in CP, 9 in DP); a fixed-width slice would have read
  DP's value as empty, and empty == empty passes.
- AGENTS.md and docs/CONCERNS.md record that the rule is now enforced and
  that SCRIPT_VERSION is derived, never maintained.

Security:
- N/A -- build-time only.

Testing:
- node --test: 481 tests, 480 pass, 1 skipped (live tests are opt-in).
- New tests/build-version-parity.test.js covers matching versions,
  a mismatch, both --check and plain invocation, two bad pairs in one
  run, a missing manifest, a missing @Version line on either side, and a
  stray @Version below the metadata block.
- New tests/helpers/build-script-runner.js holds the spawn harness, so
  the include-marker guard's tests can share it rather than duplicating.
- Verified by hand against the real tree first: a bumped DP manifest, two
  bad pairs at once, a removed manifest and a removed @Version line each
  produced the expected message and exit 1.
- Guards proven non-vacuous by reverting each: no check (6 failures),
  missing-manifest skipped (2), whole-file scan (2). The whole-file case
  initially stayed green -- the first regex match wins and the header
  precedes any stray line, so a fixture keeping its header proved
  nothing. Rewritten to omit the header version, where whole-file
  scanning reports a confident but wrong mismatch.
- verify.yml already runs build_userscripts.py --check, so no workflow
  change is needed.

Backwards Compatibility:
- Generated output is unchanged; all three .user.js files are byte
  identical. Only a previously silent inconsistency now fails the build.

Assisted-by: Claude:claude-opus-5
@netravnen netravnen changed the title fix/script version from gm info fix(user.js): derive SCRIPT_VERSION from GM_info, enforce @version parity Aug 19, 2026
@netravnen
netravnen marked this pull request as ready for review August 19, 2026 22:26
@netravnen
netravnen merged commit e8cbe00 into dev-next Aug 19, 2026
2 checks passed
@netravnen
netravnen deleted the fix/script-version-from-gm-info branch August 20, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant