FA-9231 / Packaging / Open access
Artifact identity tuple: Underscores split equivalent project identities · case 01
Underscores split equivalent project identities.
ROOT CAUSE
The implementation substitutes project.casefold() for project.casefold().replace('_', '-'), so underscores split equivalent project identities.
VERIFIED REPAIR
Case-fold project names and normalize underscores to hyphens.
Unsuccessful approach: The attempted repair substitutes project.replace('_', '-'). Fixture 1 still yields ('My-Pkg', (1, 2, 3), 'cp311', 'debug', None) instead of ('my-pkg', (1, 2, 3), 'cp311', 'debug', None).
Case contract
An artifact identity consists of project, version tuple, target ABI, build flavor, and optional classifier; preserve all fields including empty classifier.
Why this case matters
An offline model of artifact identity tuple, suitable for testing build and release tooling without external services.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(project, version, abi, flavor, classifier):
return (project.casefold(), tuple(version), abi, flavor, classifier)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('My_Pkg', (1, 2, 3), 'cp311', 'debug', None), ('my-pkg', (1, 2, 3), 'cp311', 'debug', None))
check('fixture 2', solve('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'), ('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'))
check('fixture 3', solve('A_B', (2, 0, 1), 'abi3', 'release', ''), ('a-b', (2, 0, 1), 'abi3', 'release', ''))
check('fixture 4', solve('A_B', (2, 0, 2), 'abi3', 'debug', 'docs'), ('a-b', (2, 0, 2), 'abi3', 'debug', 'docs'))
check('fixture 5', solve('a_b', (2, 1, 0), 'none', 'release', None), ('a-b', (2, 1, 0), 'none', 'release', None))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fixture 1 | ['my_pkg', [1, 2, 3], 'cp311', 'debug', None] | ['my-pkg', [1, 2, 3], 'cp311', 'debug', None] | Failed |
| fixture 2 | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | Passed |
| fixture 3 | ['a_b', [2, 0, 1], 'abi3', 'release', ''] | ['a-b', [2, 0, 1], 'abi3', 'release', ''] | Failed |
| fixture 4 | ['a_b', [2, 0, 2], 'abi3', 'debug', 'docs'] | ['a-b', [2, 0, 2], 'abi3', 'debug', 'docs'] | Failed |
| fixture 5 | ['a_b', [2, 1, 0], 'none', 'release', None] | ['a-b', [2, 1, 0], 'none', 'release', None] | Failed |
SHA-256 / d7637ef47d6f97cfacfc25554c7762d00c9ca2f03fc4f2fdbad311f5ff5999ed
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(project, version, abi, flavor, classifier):
return (project.replace('_', '-'), tuple(version), abi, flavor, classifier)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('My_Pkg', (1, 2, 3), 'cp311', 'debug', None), ('my-pkg', (1, 2, 3), 'cp311', 'debug', None))
check('fixture 2', solve('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'), ('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'))
check('fixture 3', solve('A_B', (2, 0, 1), 'abi3', 'release', ''), ('a-b', (2, 0, 1), 'abi3', 'release', ''))
check('fixture 4', solve('A_B', (2, 0, 2), 'abi3', 'debug', 'docs'), ('a-b', (2, 0, 2), 'abi3', 'debug', 'docs'))
check('fixture 5', solve('a_b', (2, 1, 0), 'none', 'release', None), ('a-b', (2, 1, 0), 'none', 'release', None))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fixture 1 | ['My-Pkg', [1, 2, 3], 'cp311', 'debug', None] | ['my-pkg', [1, 2, 3], 'cp311', 'debug', None] | Failed |
| fixture 2 | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | Passed |
| fixture 3 | ['A-B', [2, 0, 1], 'abi3', 'release', ''] | ['a-b', [2, 0, 1], 'abi3', 'release', ''] | Failed |
| fixture 4 | ['A-B', [2, 0, 2], 'abi3', 'debug', 'docs'] | ['a-b', [2, 0, 2], 'abi3', 'debug', 'docs'] | Failed |
| fixture 5 | ['a-b', [2, 1, 0], 'none', 'release', None] | ['a-b', [2, 1, 0], 'none', 'release', None] | Passed |
SHA-256 / bb05e50fbf5de21c44d117d49c8ef759d6d91ce6b89c164e3a82acf892ce5fa9
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(project, version, abi, flavor, classifier):
return (project.casefold().replace('_', '-'), tuple(version), abi, flavor, classifier)
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('fixture 1', solve('My_Pkg', (1, 2, 3), 'cp311', 'debug', None), ('my-pkg', (1, 2, 3), 'cp311', 'debug', None))
check('fixture 2', solve('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'), ('my-pkg', (1, 2, 3), 'cp312', 'release', 'tests'))
check('fixture 3', solve('A_B', (2, 0, 1), 'abi3', 'release', ''), ('a-b', (2, 0, 1), 'abi3', 'release', ''))
check('fixture 4', solve('A_B', (2, 0, 2), 'abi3', 'debug', 'docs'), ('a-b', (2, 0, 2), 'abi3', 'debug', 'docs'))
check('fixture 5', solve('a_b', (2, 1, 0), 'none', 'release', None), ('a-b', (2, 1, 0), 'none', 'release', None))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fixture 1 | ['my-pkg', [1, 2, 3], 'cp311', 'debug', None] | ['my-pkg', [1, 2, 3], 'cp311', 'debug', None] | Passed |
| fixture 2 | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | ['my-pkg', [1, 2, 3], 'cp312', 'release', 'tests'] | Passed |
| fixture 3 | ['a-b', [2, 0, 1], 'abi3', 'release', ''] | ['a-b', [2, 0, 1], 'abi3', 'release', ''] | Passed |
| fixture 4 | ['a-b', [2, 0, 2], 'abi3', 'debug', 'docs'] | ['a-b', [2, 0, 2], 'abi3', 'debug', 'docs'] | Passed |
| fixture 5 | ['a-b', [2, 1, 0], 'none', 'release', None] | ['a-b', [2, 1, 0], 'none', 'release', None] | Passed |
SHA-256 / a9d760254e3b00fc0366719694bb81c8e0a76be3b875189dfb452decd83d14bd
Verification & scope
This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:38:28.125920+00:00.
Case digest / 68b0e3e14929c2cd5dc1bcbc59c7caf9509e86219a755bdfc99e52681b36f074