Case-insensitive or Unicode-normalizing volumes reinstate the refresh-staging sweep's self-delete for a case- or NFC-variant planned dest

Open
#2,492 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, node.js
Domain
backend

Research direction

Start with src/core/runtime/client_assets.js at the referenced statShape and refresh-staging sweep locations, then read src/core/runtime/contribution_names.js and the source commit cited in the issue. First add a deterministic regression test for a case-fold or NFC-variant planned destination; the acceptance condition also calls for a case-insensitive-volume installer test, skipped when its fixture probe fails. Done means both tests establish that an installed contribution is not swept as its own leftover.

Written by the indexing model from the issue text.

Description

Source: #2489 at feedea0800a0800af42cea1840a668f1da278a6e
Location: src/core/runtime/client_assets.js:788
Severity: minor

Evidence

The guard planned.some((entry) => entry.dest === stage) is byte equality, but statShape (client_assets.js:674) uses fs.stat, which resolves names under the volume's identity rules, and isSafeContributionName (src/core/runtime/contribution_names.js) applies no case or Unicode folding, so demo.HYP-REFRESH is a legal contribution name. On a case-insensitive volume (APFS and NTFS defaults; the release checklist mandates a manual macOS host), installing demo derives stage /demo.hyp-refresh, fs.stat resolves it onto the freshly installed demo.HYP-REFRESH, the byte comparison is false, and fs.rm then deletes the copy this same run made and reported installed, on every run, while the ledger goes on naming it. That is round 1 finding 1 restored for a case-variant name: a destructive behavior this PR introduces on supported platforms. Mechanism verified in source at feedea08; not executable in this container or on CI (ubuntu-latest for all three jobs, case-sensitive container, no root, loop device, or mkfs).

Expected behavior

A contribution this run installed is never swept by its own neighbour under the volume's own name-identity relation, the same invariant c43e26cb established for byte-identical names: demo.HYP-REFRESH keeps its own body across repeated installs on a case-insensitive volume, and no 'removed leftover refresh staging' line or client_assets.refresh_staging_removed event names it.

Why deferral is safe

Safe to defer rather than block: reachability is adversarial-third-party only (a plugin must register, for one client, both X and a case- or normalization-variant of X.hyp-refresh, mimicking an internal reserved staging suffix; no bundled contribution is anything close), the delete stays at a derived direct child of the asset directory (no containment escape) and destroys only the plugin's own freshly copied contribution, never user-authored content, and the candidate repairs carry a real design trade: naive case-fold plus NFC widening declines genuine leftovers whenever the plan holds a case-variant, which is issue 2407's own symptom in an equally contrived case but in the non-destructive direction, while true file identity (dev/ino) is unavailable for plan entries not yet copied. Blocking would hold a real recovery fix hostage to an adversarial name on platforms this repository's CI cannot exercise.

Acceptance condition

Two parts, each testable. (1) Deterministic on existing ubuntu CI: a regression test that fails at feedea08 and passes after the repair, pinning the repair's comparison semantics for a case-fold or NFC-variant planned dest; for example, with a widened comparison, a genuine leftover at demo.hyp-refresh is declined, not removed, when the plan contains demo.HYP-REFRESH on a case-sensitive volume. (2) End-to-end on a case-insensitive volume: register X and X.HYP-REFRESH for one client, run the installer twice, and assert X.HYP-REFRESH keeps its own body and no removed-leftover line or refresh_staging_removed event names it, in a test that probes its fixture directory for case-insensitivity (create a lowercase probe, fs.stat the uppercase spelling) and reports a skip where the probe fails, exercised on a macos-latest CI leg or the release checklist's mandatory manual macOS gate.

Evidenced behavioral defect; eligible for automatic repair.

Dominant language
JavaScript
Stars
11
Forks
0
Avg merge
2h 55m
Merged PRs (30d)
294

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from hyparam/hypaware

All issues in hyparam/hypaware

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.