categories_normalize
Rename and merge stored categories by re-keying fingerprints to canonical forms. Dry-run reports exact changes before you apply.
Instructions
Rename stored categories and re-key their fingerprints (CB-61).
TWO MODES, and the second is a working mode rather than a side effect.
Without fold_map this folds every stored SPELLING to its canonical
form, for rows filed before write-time canonicalization existed, whose
stored auto:v1 fingerprint still carries the old spelling and therefore
forks identity when the same defect is reported again. With a fold_map
it MERGES CATEGORY NAMES: any stored name may be renamed to any canonical
target, and the two need not be spellings of each other. That second mode
is how a tracker's rare category names are collapsed into its common ones.
DRY RUN BY DEFAULT — without apply=true nothing is written and the
report tells you exactly what would change. A key that matches no stored
category is accepted and renames nothing, and unmatched_fold_keys names
every one of them, so a typo on the left-hand side is stated rather than
left to be spotted as a pair missing from the from -> to table. A typo
in a TARGET is refused instead — see new_category. Matching is exact
against the stored spelling, so a canonical key does not reach a stored
Process Improvement and is reported unmatched. Take an export-csv
backup before applying; restore-csv puts findings back verbatim into an
EMPTY tracker, but milestone items and audit history are not in a CSV
export and are not restored.
Each renamed row's fingerprint is handled by kind: a NULL or a
caller-SUPPLIED fingerprint is left byte-identical, an auto:v1 one is
re-derived with the new category after its stored inputs are verified to
reproduce the stored hash. A row that fails that round trip is skipped
WHOLE and reported under unverifiable. The occurrence ring
(meta.occurrences) is never rewritten.
If the fold would put two LIVE findings on one fingerprint, the run
writes NOTHING and reports the colliding pair by id — merging two cards
is a decision, not a migration step. Any OTHER identity merge — two closed
cards, or a closed card and a live one — is legal, so it is reported under
merged_identities rather than refused: the run proceeds, and you are told
which cards this fold fused. Both unmatched_fold_keys and
merged_identities are always present; [] means "checked, none".
Args:
fold_map: Optional {stored category name: canonical target name} map, as an object or a JSON string. The key is matched exactly against the stored value and may be any name the table holds. Every target must already be canonical (casefold, hyphen/whitespace -> "_"). Omit it to fold every stored spelling to its own normalized form;
{}is an explicit no-op.apply: Write the changes. Default false (report only).
new_category: Permission to fold INTO a category this tracker does not hold yet, for the whole map at once. Without it such a target is refused, naming the nearest existing categories — an operation meant to REDUCE the number of category names must not invent one by typo. The refusal stops at the first bad target.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apply | No | ||
| fold_map | No | ||
| new_category | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||