# Category Management specification
Pair: [user guide](../../user/accounting/categories.md). Entries: public/categories.php, categories-api.php. UI: categories.js/categories.css. Owner: private/modules/accounting/categories.php. Consumers: classification_catalog.php, classification_records.php, classification_rule_actions.php, accounting/vectors.php. This contract owns tree semantics.
## Storage identities
Default definitions: accounting_default_categories(default_key,value_id,parent_key,display_name,sort_order). Preferences: accounting_default_category_preferences(default_key,is_active), absent row means active. Custom definitions: accounting_custom_categories(value_id,parent_value_id,display_name,is_active,sort_order).
Both sets share stable reference IDs in classification_values for existing consumers/history. This is an identity/compatibility projection, not combined definition ownership. Default IDs and custom IDs therefore do not collide. Default key identifies supplied definitions across installs; numeric IDs should not be hardcoded across installations. Current supplied definitions are in [catalog JSON](../reference/default-categories.json).
Deleted custom definitions are removed, while accounting_deleted_category_identities records their value_id and deletion time. classification_values retains an inactive path identity for historical readability. accounting_category_deletion_audit stores the confirmed impact JSON.
Use-policy columns and classification_category_uses were removed. APIs may return applicability:[] for compatibility; it must never be treated as permission policy.
## Tree and activation
cat_index combines definitions, recursively computes path, depth, ancestors and effective is_active. Own activation and effective activation are distinct: effective active = own active AND every ancestor active. Paths join names with ' → '. Invalid/missing ancestry is rejected defensively; no reparenting action exists.
cat_catalog uses parent-first traversal, siblings ordered by sort_order, name, then ID. cat_sync projects full paths, effective activation and ordering into classification_values. cat_segments yields stable value_key segments and names for accounting node creation. Category display-name changes do not move existing vector endpoints or rename previously created nodes.
cat_save accepts {id,name,parent_id,order,active}. Existing default saves change preferences only. Unspecified cannot be changed/deactivated or have children. New custom name: nonempty, up to 160 bytes; order integer 0–100000. Parent must exist. Existing custom parent_id must match its current parent; renaming/activation/order are permitted by the backend, though UI exposes name and activation, not an ordering editor. Custom value_key is random, not derived from the name.
## HTTP actions
GET init synchronizes compatibility entries and returns ordered categories plus CSRF. POST save returns catalog and ID; delete_preview takes id; delete takes {preview_token,confirmed:true}. Save and delete invalidate classification_view and deletion preview as relevant. All writes are transactional.
## Deletion protocol
1. cat_impact rejects defaults and computes the entire custom descendant branch.
2. Collect current latest-revision statement assignments, rule targets, compatibility references, revision history, rule events, classification audits, rule-control audits and linked vectors. Include identifiers, paths and human-readable descriptions where available.
3. cat_delete_preview returns these effects and a random token; session stores root, IDs and SHA-256 of the impact JSON.
4. Confirm requires the same token and literal boolean true. Start transaction, lock identities and recompute impact. A changed hash rejects deletion; require fresh preview.
5. For each affected current row, lock statement/revision and check preview revision. Preserve Use/business/percentage, set category to Unspecified through cf_write, appending a new accounting revision and normal audit.
6. Lock affected rules, check preview settings, replace category targets with Unspecified and append control audit. Reset compatibility references too.
7. Remove custom definitions deepest-first; retain inactive classification identity and deletion tombstone. Append the full impact audit and commit.
On any error rollback, preserving the branch and assignments. The client invalidates failed confirmation state. Historical vectors/metadata/audits are not rewritten. A preview lists historical items as retained effects, not items to delete. A new category with the same name receives a new identity; it must not inherit the deleted one's references.
## Interface
Compact indented tree, default/custom tabs, search and Show inactive. Arrows collapse/expand; Active controls own flag. ⋯ offers Add subcategory, and custom-only Edit/Preview deletion. Custom additions may display under default ancestry. Deletion dialog lists the entire impact; historical detail may be expandable but cannot be omitted from accessible preview content.
Classifier chooser consumes the same active tree, roots collapsed on each open. Any level can be chosen; selecting a category never changes Use. Existing inactive selections remain visible and saveable; a newly chosen inactive or deleted identity is rejected.
## Verification
Default protected; Unspecified always active. Deactivate/reactivate parent with independently inactive child. Custom child of default, duplicate labels with distinct IDs, fixed parentage rejection. Delete populated parent: all descendants and all reference types previewed, rows/rules reset, history unchanged, current rows unfinished. Change an affected row/rule between preview and confirmation: reject. Cancel: no changes. Recreate: fresh ID. Confirm category choices identical across Uses.