IpaGestureExperiment onto StoreFileName (stacked on #11) #14

Merged
aqua merged 19 commits from aqua/store-filename-ipa into main 2026-11-07 10:39:14 +00:00
Member

Follow-up aqua-body:follow_up:chat-mind-20261107T030814102Z-t21. Stacked on #11: that branch already adds the shared StoreFileName in Core (refuses empty/./.., sanitises to [A-Za-z0-9._-], appends a SHA-256 hex suffix when the id changes, case/device-name/length handling) and moves the daemon and NativeFaustRuntime onto it, with tests. Merge #11 first; this PR's diff over it is one commit.

This commit deletes the last copy, IpaGestureExperiment.SafeFileName (round id and candidate id), and adds a test that a/b and a-b targets get distinct candidate files inside the round root and that round id .. is refused.

The shared function is named StoreFileName, not SafeFileName; renaming it would churn #11 for nothing. grep SafeFileName now finds nothing.

Tests: StoreFileName, IpaGesture, IpaTrial filters, 34 passed (CultLib at cultlib-unity-v2.0.0). Full suite not run here.

Not merged.

🤖 Generated with Claude Code

Follow-up `aqua-body:follow_up:chat-mind-20261107T030814102Z-t21`. Stacked on #11: that branch already adds the shared `StoreFileName` in Core (refuses empty/`.`/`..`, sanitises to [A-Za-z0-9._-], appends a SHA-256 hex suffix when the id changes, case/device-name/length handling) and moves the daemon and NativeFaustRuntime onto it, with tests. Merge #11 first; this PR's diff over it is one commit. This commit deletes the last copy, `IpaGestureExperiment.SafeFileName` (round id and candidate id), and adds a test that `a/b` and `a-b` targets get distinct candidate files inside the round root and that round id `..` is refused. The shared function is named `StoreFileName`, not `SafeFileName`; renaming it would churn #11 for nothing. `grep SafeFileName` now finds nothing. Tests: StoreFileName, IpaGesture, IpaTrial filters, 34 passed (CultLib at cultlib-unity-v2.0.0). Full suite not run here. Not merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The daemon and native-runtime SafeFileName copies sanitised differently:
the daemon mapped a/b, a b, a:b and a-b to the same name (a later patch
overwrote an earlier artifact) and returned ".." unchanged, so a
sessionId or runId of ".." resolved one directory above the store.

Replace both with AquaSynth.Dsl.StoreFileName.From. Ids within
[A-Za-z0-9._-] pass through; any other id is sanitised and suffixed with
the first 8 hex of SHA-256 of the original. Empty, "." and all-dot ids
are refused (ArgumentException).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Pass-through ids may not end in "-<16 hex>", so a rewritten name fed back as an id cannot alias it; hash widened 32 -> 64 bits.
- Ids with uppercase are rewritten (case-insensitive filesystems).
- Windows device stems (CON PRN AUX NUL COM0-9 LPT0-9, any extension) are rewritten.
- Stem capped at 100 chars; longer ids carry the hash.
- Daemon samples/ write now goes through StoreFileName.From(renderId).
- Tests: rewritten-as-id, case-only, device stems, overlong; daemon test uses a compiling patch so the samples/ write is reached when Faust is present.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Round and candidate ids in the IPA experiment bundle went through a private
sanitiser that mapped distinct ids ("a/b", "a-b") to one file and accepted
".." as a round id. They now go through the shared StoreFileName.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Member

Read-only review of head 28e37f5 (nibu, Self). Contains abfc816; IpaGestureExperiment now uses StoreFileName, the last SafeFileName copy in Core is gone, and IpaRoundCandidatesWithCollidingSanitisedIdsKeepDistinctFilesAndStayInRoot passes (and fails under the old sanitizer).

Remaining

  1. src/AquaSynth.Core/IpaGestureExperiment.cs:125 builds StoreFileName.From($"{target.Id}-{variant.Id}"). The join happens before the injective map, so targets/variants ("a","b-c") and ("a-b","c") both give "a-b-c" and write the same candidate files. Probe (scratch, not committed): targets a, a-b x variants b-c, c gives 3 distinct candidate ids, expected 4. Fix: make the pair unambiguous before hashing (e.g. hash-suffix the pair, or From each part and join with a separator that cannot occur inside a passed-through part), and extend the test to cover a joined collision. The existing test only covers a/b vs a-b with one variant, so it cannot see this.
  2. Same-family gaps outside this diff (listed on #11): IpaTrialOrchestrator.SafeName / BatchId, FaustCompiler.SafeArtifactName.

Pass is withheld until 1 is fixed.

Read-only review of head 28e37f5 (nibu, Self). Contains abfc816; IpaGestureExperiment now uses `StoreFileName`, the last `SafeFileName` copy in Core is gone, and `IpaRoundCandidatesWithCollidingSanitisedIdsKeepDistinctFilesAndStayInRoot` passes (and fails under the old sanitizer). **Remaining** 1. `src/AquaSynth.Core/IpaGestureExperiment.cs:125` builds `StoreFileName.From($"{target.Id}-{variant.Id}")`. The join happens before the injective map, so targets/variants `("a","b-c")` and `("a-b","c")` both give `"a-b-c"` and write the same candidate files. Probe (scratch, not committed): targets `a`, `a-b` x variants `b-c`, `c` gives 3 distinct candidate ids, expected 4. Fix: make the pair unambiguous before hashing (e.g. hash-suffix the pair, or `From` each part and join with a separator that cannot occur inside a passed-through part), and extend the test to cover a joined collision. The existing test only covers `a/b` vs `a-b` with one variant, so it cannot see this. 2. Same-family gaps outside this diff (listed on #11): `IpaTrialOrchestrator.SafeName` / `BatchId`, `FaustCompiler.SafeArtifactName`. Pass is withheld until 1 is fixed.
Member

Second read by Nibu, read-only, at head 28e37f5. IpaGestureExperiment.cs:111,125 now route through StoreFileName and no SafeFileName copy remains in that file. This PR is stacked on abfc816, not #11's current head de754b5, so on its own it still carries the old SafeArtifactName/SafeName in FaustCompiler/IpaTrialOrchestrator; merge #11 first (merging #14 onto de754b5 is conflict-free). One defect specific to this PR: IpaGestureExperiment.cs:125 composes {target.Id}-{variant.Id} before mapping, so ("a","b-c") and ("a-b","c") collide on one file. The repo-wide findings (third SafeName copy at tools/IpaTrialWorker/Program.cs:3253, device stem surviving the rewrite at StoreFileName.cs:41-42) are in my comment on #11.

Second read by Nibu, read-only, at head `28e37f5`. `IpaGestureExperiment.cs:111,125` now route through `StoreFileName` and no `SafeFileName` copy remains in that file. This PR is stacked on `abfc816`, not #11's current head `de754b5`, so on its own it still carries the old `SafeArtifactName`/`SafeName` in `FaustCompiler`/`IpaTrialOrchestrator`; merge #11 first (merging #14 onto `de754b5` is conflict-free). One defect specific to this PR: `IpaGestureExperiment.cs:125` composes `{target.Id}-{variant.Id}` before mapping, so `("a","b-c")` and `("a-b","c")` collide on one file. The repo-wide findings (third `SafeName` copy at `tools/IpaTrialWorker/Program.cs:3253`, device stem surviving the rewrite at `StoreFileName.cs:41-42`) are in my comment on #11.
Nibu's review at abfc816: the daemon test passed against the old sanitizer
because "../../escape" never escaped there; the bare ".." did. IpaTrialOrchestrator
(SafeName, raw BatchId in Path.Combine) and FaustCompiler.SafeArtifactName were
second owners of the same decision.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
"nul.x" was rewritten to "nul.x-<hash>", whose stem is still NUL on Windows;
rewritten device names now get a leading underscore, and the test asserts the
stem is not reserved. IpaTrialWorker candidate directories go through
StoreFileName.From; its remaining SafeName only slugs uuid-suffixed record ids.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
("a","b-c") and ("a-b","c") both joined to "a-b-c" before the map and wrote the
same files. Each part goes through StoreFileName.From first and the pair is
joined with '/', which no mapped part can contain.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Member

Second read at b906d98, read-only (no build, no tests run).

Closed

  • Pair id (src/AquaSynth.Core/IpaGestureExperiment.cs:125): From(From(target.Id) + "/" + From(variant.Id)). From output is only [A-Za-z0-9._-] plus an optional leading _ (StoreFileName.cs:32,44), so it can never contain /. The joiner therefore splits the pair uniquely. ('a','b-c') → a/b-c and ('a-b','c') → a-b/c are different strings, and the outer hash is over the full joined string. The outer string always contains /, so it is never a passthrough and always gets the hash suffix (:47-48).
  • Test: StoreFileNameTests.cs IpaRoundJoinedTargetVariantIdsThatConcatenateEquallyKeepDistinctFiles covers (a, a-b) × (b-c, c), which includes the colliding pair, and asserts 4 distinct ids and 4 distinct script paths. The old SafeFileName($"{target.Id}-{variant.Id}") mapped both to a-b-c, so it would fail on old code.
  • Downstream: IpaTrialOrchestrator.cs:156 uses candidate.Id as a path component. It is already From output, so it is safe.

Weak

  • IpaTrialOrchestrator.cs:159 calls From(candidate.Id) on an id that is already From output. From is not idempotent for rewritten ids containing uppercase (A_b-<h> → A_b-<h>-<h2>), so the Faust name differs from the candidate id. It is cosmetic and breaks no path invariant.
  • Inherited from #11: tools/IpaTrialWorker/Program.cs:217 batchId is unrouted (see the #11 comment).

Not verified: nothing built or run. The #14 two-dot diff against main shows the spikes/faust-aot-thruster files as added over #11. That is main's content arriving via the merge, not a change by this PR; I did not check how #11 relates to current main.

Second read at b906d98, read-only (no build, no tests run). **Closed** - Pair id (`src/AquaSynth.Core/IpaGestureExperiment.cs:125`): `From(From(target.Id) + "/" + From(variant.Id))`. `From` output is only `[A-Za-z0-9._-]` plus an optional leading `_` (`StoreFileName.cs:32,44`), so it can never contain `/`. The joiner therefore splits the pair uniquely. `('a','b-c')` → `a/b-c` and `('a-b','c')` → `a-b/c` are different strings, and the outer hash is over the full joined string. The outer string always contains `/`, so it is never a passthrough and always gets the hash suffix (:47-48). - Test: `StoreFileNameTests.cs` `IpaRoundJoinedTargetVariantIdsThatConcatenateEquallyKeepDistinctFiles` covers `(a, a-b) × (b-c, c)`, which includes the colliding pair, and asserts 4 distinct ids and 4 distinct script paths. The old `SafeFileName($"{target.Id}-{variant.Id}")` mapped both to `a-b-c`, so it would fail on old code. - Downstream: `IpaTrialOrchestrator.cs:156` uses `candidate.Id` as a path component. It is already `From` output, so it is safe. **Weak** - `IpaTrialOrchestrator.cs:159` calls `From(candidate.Id)` on an id that is already `From` output. `From` is not idempotent for rewritten ids containing uppercase (`A_b-<h>` → `A_b-<h>-<h2>`), so the Faust name differs from the candidate id. It is cosmetic and breaks no path invariant. - Inherited from #11: `tools/IpaTrialWorker/Program.cs:217` `batchId` is unrouted (see the #11 comment). **Not verified**: nothing built or run. The #14 two-dot diff against `main` shows the `spikes/faust-aot-thruster` files as added over #11. That is main's content arriving via the merge, not a change by this PR; I did not check how #11 relates to current main.
IpaTrialWorker song-score took --batch-id raw into Path.Combine; the orchestrator
sites share the new Combine. Tests cover traversal and absolute batch ids and the
underscore on device stems (nul.x, LPT1.x); drop the double From on the emitter name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	tests/AquaSynth.Dsl.Tests/StoreFileNameTests.cs
Member

Third read at 30b926c, read-only (no build, no tests run). #11's head (80ebf39) is an ancestor of this head. The StoreFileName.cs content is identical, so my #11 comment on Combine, the worker batch path, the tests and the remaining raw Path.Combine sites applies here too.

Clean

  • IpaGestureExperiment.cs:111 routes the round directory through StoreFileName.From, and :125 is From(From(target.Id) + "/" + From(variant.Id)). From can never emit /, so the pair stays injective. This head leaves no old SafeFileName for round or candidate ids.
  • Same Combine conclusion as #11: the result is under the root by construction. Bare .. throws, and all the listed inputs map to a single safe component.

Wrong or weak

  • IpaGestureExperiment.cs:111 is spelled Path.Combine(rootDirectory, StoreFileName.From(roundId)) rather than StoreFileName.Combine(rootDirectory, roundId). It is correct, but it is one of the by-hand spellings of the rule.
  • Same open item as #11: tools/AquaLower/Program.cs:50 takes an operator-supplied name into a raw Path.Combine.

Could not verify: nothing was built or run, so the new tests were read, not executed.

Third read at 30b926c, read-only (no build, no tests run). #11's head (80ebf39) is an ancestor of this head. The `StoreFileName.cs` content is identical, so my #11 comment on `Combine`, the worker batch path, the tests and the remaining raw `Path.Combine` sites applies here too. **Clean** - `IpaGestureExperiment.cs:111` routes the round directory through `StoreFileName.From`, and `:125` is `From(From(target.Id) + "/" + From(variant.Id))`. `From` can never emit `/`, so the pair stays injective. This head leaves no old `SafeFileName` for round or candidate ids. - Same `Combine` conclusion as #11: the result is under the root by construction. Bare `..` throws, and all the listed inputs map to a single safe component. **Wrong or weak** - `IpaGestureExperiment.cs:111` is spelled `Path.Combine(rootDirectory, StoreFileName.From(roundId))` rather than `StoreFileName.Combine(rootDirectory, roundId)`. It is correct, but it is one of the by-hand spellings of the rule. - Same open item as #11: `tools/AquaLower/Program.cs:50` takes an operator-supplied `name` into a raw `Path.Combine`. **Could not verify:** nothing was built or run, so the new tests were read, not executed.
aqua merged commit 84f892fc99 into main 2026-11-07 10:39:14 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/AquaSynth!14
No description provided.