Commit e6e0ab78 by PLN (Algolia)

feat(foundry): kitgate — one gate between cutting a pack and loading it

Four checks, run before anything is linked into Dirt-Samples.

grade is deliberately re-run over the WRITTEN FILES rather than trusted from the
finder's in-memory scoring: same verdict by a different code path, so a disagreement
means one of them is wrong, which is the entire reason to run it twice.

Bank shadowing is the one that would hurt silently. loadSoundFiles defaults to
appendToExisting = false, so a new folder whose basename matches an existing bank frees
that bank's buffers and takes the name — no error anywhere, and the only symptom is that
an old track now plays the wrong sound. Checked BEFORE linking, through tools/pvbanks.py
so this and bank-check.py cannot disagree about what a bank is.

Rights is advisory and never blocks the load, because loading a kit locally is not
releasing a track — but it does say out loud when a kit cut from someone else's stems
is not yet ledgered.

Self-test on the synthetic pack immediately blocked on clipping in the fixture, which
is the fixture's fault and the gate's point.
parent 0c140b17
#!/usr/bin/env python3
"""kitgate — the release gate for a freshly cut sample pack. Run before linking.
Cutting a pack produces audio; this decides whether it is fit to load into a running
rig. Four checks, three of them things a single earlier stage cannot answer alone:
1. **grade, on the shipped bytes.** `engine.grade` re-run over the written files, not
the candidate windows the finder scored. It is a separate code path reaching the same
verdict, so a disagreement means one of them is wrong — which is the point of running
it twice (`feedback_assembling_checks_revalidates`).
2. **kitcheck.** Dead bars, bar-multiple exactness, per-family periodicity, duplicates.
3. **Bank shadowing.** `loadSoundFiles` defaults to appendToExisting = false, so a new
folder whose basename matches an existing bank frees that bank's buffers and takes
the name — silently, with the only symptom being that an old track now plays the
wrong sound. Checked BEFORE anything is linked, via `tools/pvbanks.py` so this agrees
with `bank-check.py` by construction (`feedback_one_parser_per_concept`).
4. **Rights.** A kit cut from someone else's stems must not be releasable per-track by
default. Reported here, ledgered by `armada/tide-table/register_fred_rights.py`.
python3 kitgate.py <cuts.json> [--link] [--source "..."]
`--link` performs the Dirt-Samples symlink for every kit, but only if 1-3 pass.
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from collections import Counter
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
TIDAL_TOOLS = HERE.parent # tools/
def check_grades(cuts: list[dict]) -> tuple[bool, list[str]]:
"""Re-grade the written files and compare with what the finder recorded."""
from engine import grade as G
lines, bad = [], []
tiers = Counter()
for c in cuts:
g = G.grade(c["path"], role=c.get("family"))
tiers[g.tier] += 1
# the finder graded the same audio in memory; a real gap means one path is wrong
if abs(g.grade - c["grade"]) > 0.02:
bad.append(f" {c['kit']}/{c['name']}: finder said {c['grade']:.3f}, "
f"file grades {g.grade:.3f}")
for f in g.flags:
if f in ("clipping", "near-silent", "mono-incompatible"):
bad.append(f" {c['kit']}/{c['name']}: {f}")
lines.append(" tiers " + " ".join(f"{t}x{tiers[t]}" for t in "SABCD" if tiers[t]))
lines += bad[:20]
if len(bad) > 20:
lines.append(f" … and {len(bad) - 20} more")
return not bad, lines
def check_kits(cuts: list[dict]) -> tuple[bool, list[str]]:
from engine import kitcheck as K
checks = K.check_cuts(cuts)
hard = [c for c in checks if not c.ok]
soft = [c for c in checks if c.ok and c.problems]
dupes = [c for c in checks if c.dupe_of]
lines = [f" {len(checks) - len(hard) - len(soft)} clean, {len(soft)} advisory, "
f"{len(hard)} failing · {len(checks) - len(dupes)} distinct sounds"]
lines += [f" ✗ {c.kit}/{c.name}: {'; '.join(c.problems)}" for c in hard[:20]]
return not hard, lines
def check_shadowing(cuts: list[dict]) -> tuple[bool, list[str]]:
"""Would linking these kits steal a name an existing bank already answers to?"""
sys.path.insert(0, str(TIDAL_TOOLS))
try:
from pvbanks import scan
except Exception as e:
return True, [f" (skipped: {e})"]
existing = scan()
kits = sorted({c["kit"] for c in cuts})
clash = [k for k in kits if k in existing]
lines = [f" {len(kits)} kits vs {len(existing)} known banks"]
lines += [f" ✗ {k} already exists: "
f"{', '.join(str(e[0]) for e in existing[k])}" for k in clash]
return not clash, lines
def check_rights(cuts: list[dict], source: str) -> tuple[bool, list[str]]:
"""Are these banks recorded as NOT releasable per-track? Advisory, never blocking."""
ledger = (TIDAL_TOOLS.parent / "armada/tide-table/rights_ledger.json")
kits = sorted({c["kit"] for c in cuts})
if not ledger.exists():
return True, [" (no ledger)"]
banks = json.loads(ledger.read_text()).get("banks", {})
unregistered = [k for k in kits if k not in banks]
releasable = [k for k in kits
if banks.get(k, {}).get("status") in ("original", "cleared", "dirt_samples")]
lines = [f" {len(kits) - len(unregistered)} of {len(kits)} in the ledger"]
if unregistered:
lines.append(f" ! {len(unregistered)} unregistered — run register_fred_rights.py "
f"so they block per-track release with their provenance attached")
if releasable:
lines.append(f" ! {len(releasable)} marked releasable per-track: "
f"{', '.join(releasable[:5])}")
return True, lines
def main(argv=None) -> int:
ap = argparse.ArgumentParser(prog="kitgate", description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("cuts_json", type=Path)
ap.add_argument("--link", action="store_true", help="symlink into Dirt-Samples if clean")
ap.add_argument("--source", default="third-party producer stem pack")
a = ap.parse_args(argv)
cuts = json.loads(a.cuts_json.read_text())
print(f"kitgate — {len(cuts)} samples in {len({c['kit'] for c in cuts})} kits\n")
results = {}
for label, fn in [("grade (re-run on the written files)", lambda: check_grades(cuts)),
("kitcheck", lambda: check_kits(cuts)),
("bank shadowing", lambda: check_shadowing(cuts)),
("rights", lambda: check_rights(cuts, a.source))]:
ok, lines = fn()
results[label] = ok
print(f"{'✓' if ok else '✗'} {label}")
for l in lines:
print(l)
print()
blocking = [k for k, v in results.items() if not v and k != "rights"]
if blocking:
print(f"BLOCKED by: {', '.join(blocking)}")
return 1
print("all blocking checks pass")
if a.link:
from engine import publish as P
acted = Counter()
for kit in sorted({c["kit"] for c in cuts}):
try:
acted[P.link_kit(kit).action] += 1
except Exception as e:
print(f" ! {kit}: {e}")
acted["failed"] += 1
print("linked into Dirt-Samples: " +
", ".join(f"{v} {k}" for k, v in sorted(acted.items())))
if acted.get("skipped-real-dir"):
print(" (a real directory was left untouched — we never delete what we did "
"not create)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment