Feature Specification: Taxonomy System (Categories, Tags, Collections)
Feature ID: taxonomy-system
Status: Retrospective
Created: 2026-05-01
Last updated: 2026-05-01
Owner: Ever Works Team
1. Overview
Every work has a three-dimensional taxonomy:
- Categories — primary classification, exactly one per item
- Tags — descriptive labels, 0 or more per item
- Collections — editorial groupings, 0 or 1 per item (optional)
Taxonomy is stored in the data repository as YAML files
(categories.yml, tags.yml, collections.yml) and managed through
WorkTaxonomyService. CRUD operations enforce role-based access
control, auto-generate URL-friendly slug ids from names, validate name
uniqueness case-insensitively, and persist by reading-modifying-writing
the relevant YAML file with a single git commit.
2. User Scenarios
2.1 Primary scenarios
- Given I'm an editor on a work, when I
POSTto/api/works/:id/categorieswith{name: "Frontend Frameworks"}, then the platform creates a category with idfrontend-frameworks(auto-slugified) and commits the updatedcategories.ymlto the data repo. - Given an item references
category: frontend-frameworksandtags: [open-source, typescript], when I generate the website, then the rendered site shows the category and tags fromcategories.ymlandtags.yml. - Given I rename a category, when the update completes, then the new name shows immediately and the slug id is preserved (so existing item references stay valid).
- Given I'm a viewer, when I list categories/tags/collections,
then I see them all; when I try to create or update one,
then I get
403.
2.2 Edge cases & failures
- Given I try to create a tag with the same name as an existing tag (case-insensitive match), when the validator runs, then the create is rejected with a clear "duplicate name" error before the YAML is touched.
- Given I delete a category that items reference, when the
delete completes, then the items keep their
categoryfield pointing at the now-deleted slug — they are NOT auto-reassigned; a follow-up cleanup is the user's responsibility. - Given I rename "My Category" to "Renamed Category", when the
update commits, then the slug id stays
my-category(slugs are immutable on update; renaming the slug requires delete + create). - Given the AI generates a new category during a pipeline run, when the categorization step finishes, then the new category is created via the same service path as a manual create (uniqueness validation, slug generation, git commit).
3. Functional Requirements
- FR-1 The system MUST manage three taxonomy entity types: categories, tags, collections.
- FR-2 Item cardinality MUST be: exactly 1 category (required), 0+ tags, 0–1 collection (optional).
- FR-3 All entity ids MUST be auto-generated by slugifying the
name with
slugifyText()(URL-friendly, lowercase, hyphenated). - FR-4 Users MUST NOT be able to set ids directly.
- FR-5 Slug ids MUST be immutable on update; renaming the entity keeps the existing id.
- FR-6 Name uniqueness MUST be enforced case-insensitively per entity type within a work.
- FR-7 Categories MUST support:
id,name, optionaldescription, optionalicon_url, optionalpriority. - FR-8 Tags MUST support:
id,name(no description / icon / priority — kept minimal). - FR-9 Collections MUST support:
id,name, optionaldescription, optionalicon_url, optionalpriority. - FR-10 Storage MUST be YAML files in the data repo:
categories.yml,tags.yml,collections.yml. - FR-11 Items MUST reference taxonomy entities by slug id.
- FR-12 Read operations MUST require viewer role; mutations MUST
require editor role (enforced by
WorkOwnershipService). - FR-13 Deletion MUST NOT auto-reassign referencing items — they're left dangling until the user re-categorises.
- FR-14 AI-generated taxonomy entries MUST flow through the same service as manual entries (uniqueness + slug + git commit).
4. Non-Functional Requirements
- Performance: each CRUD operation is one read-modify-write cycle plus a git commit + push (~500 ms–2 s typical).
- Reliability: a failed git push leaves both DB and repo unchanged.
- Security & privacy: role-checked on every operation.
- Observability: taxonomy mutations emit
category_change,tag_change,collection_changeentries in the work changelog. - Compatibility: YAML schema is backwards-compatible — older items missing optional fields still validate.
5. Key Entities & Domain Concepts
| Entity / concept | Description |
|---|---|
| Category | Primary classification; required on every item |
| Tag | Descriptive label; 0+ per item |
| Collection | Editorial grouping; 0–1 per item, optional |
| Slug id | URL-friendly, lowercase, hyphen-separated; auto-generated from name |
| YAML files | categories.yml, tags.yml, collections.yml in the data repo |
| Reference style | Items reference taxonomy by slug id, not by name or numeric id |
6. Out of Scope
- Hierarchical / nested categories.
- Tags with metadata (description, icon, priority).
- Multiple collections per item.
- Cross-work shared taxonomy.
- Auto-reassignment on delete (deliberate choice; user owns cleanup).
7. Acceptance Criteria
- Three CRUD surfaces with consistent slug + uniqueness behaviour.
- Role-based access (viewer for read, editor for write).
- Items reference by slug id; YAML round-trips cleanly.
- AI generation goes through the same service.
- Tests cover slug generation, duplicate detection, delete + referencing items, role enforcement.
8. Open Questions
None on develop.
9. Constitution Gates
- I: AI generation uses the AI provider plugin (categorization step in the Standard Pipeline).
- II: AI access via facade; role checks via the ownership service.
- III: taxonomy lives in the data repo, not the database.
- IV: AI generation runs inside existing pipeline; no new background work.
- V: no DB schema (storage is in the repo).
- VI: covered in
packages/agent/src/services/__tests__/work-taxonomy.service.spec.ts. - VII: no secrets involved.
- VIII: N/A.
- IX: behaviour-first.
- X: YAML schema is forward-compatible.
10. References
- User-facing doc:
../../../features/taxonomy-system.md - Related:
collections/spec.md - Implementation:
packages/agent/src/services/work-taxonomy.service.tspackages/agent/src/services/work-ownership.service.ts
- Pipeline:
packages/plugins/standard-pipeline/src/steps/categories-tags-processing.step.ts