Parent PRD
#676
What to build
The end-to-end vertical skeleton for Compliance Templates: an admin can define a template bound to Data Products containing one titled group with a single String field, activate it, and a product owner sees a button on the product detail page that opens a modal to fill in and save that value; read-only users see the populated modal with inputs disabled. This slice stands up all three tables and every integration layer so later slices only add capability, never plumbing.
Layers (thin, String-only):
- Data model (three tables) per PRD Data model section — template definition (with the partial unique index on
(entity_type, scope_type, scope_id), scope_type default all), template field (groups denormalized as group_title/group_order), template value (polymorphic (field_id, entity_type, entity_id), unique). Reserved constraints and scope_* columns exist but unused in v1. Mirror the entity_tag_associations polymorphic pattern (db_models/tags.py) and the cert-levels model style (db_models/certification_levels.py).
- Alembic migration — one new revision off current head, single-head rule (see
11-database-migrations.mdc; mirror a8_cert_levels).
- Backend — Pydantic models, repository, and manager mirroring the cert-levels layering;
set_*-style replace-all write mirroring set_tags_for_entity.
- API — minimal admin create + activate; composed read endpoint returning
{ template, fields, values } for an entity (used by both editable and read-only modal); replace-all write endpoint.
- RBAC — two new feature-registry keys in
common/features.py (one admin key gating template definition management, one gating per-entity value read vs write), gated via PermissionChecker like settings-certification-levels.
- Frontend — minimal Settings page (route under
/settings/..., sidebar entry, mirror certification-levels-settings.tsx); product-detail button + modal mounted in views/data-product-details.tsx (String input only; hidden when no active template; inputs+save disabled for read-only users).
Keep it generic/entity-agnostic (entity type + id strings only); no customer-identifying naming (code word dtag).
Acceptance criteria
Blocked by
None - can start immediately.
User stories addressed
- User story 1
- User story 15
- User story 24
- User story 25
- User story 28
Parent PRD
#676
What to build
The end-to-end vertical skeleton for Compliance Templates: an admin can define a template bound to Data Products containing one titled group with a single String field, activate it, and a product owner sees a button on the product detail page that opens a modal to fill in and save that value; read-only users see the populated modal with inputs disabled. This slice stands up all three tables and every integration layer so later slices only add capability, never plumbing.
Layers (thin, String-only):
(entity_type, scope_type, scope_id),scope_typedefaultall), template field (groups denormalized asgroup_title/group_order), template value (polymorphic(field_id, entity_type, entity_id), unique). Reservedconstraintsandscope_*columns exist but unused in v1. Mirror theentity_tag_associationspolymorphic pattern (db_models/tags.py) and the cert-levels model style (db_models/certification_levels.py).11-database-migrations.mdc; mirrora8_cert_levels).set_*-style replace-all write mirroringset_tags_for_entity.{ template, fields, values }for an entity (used by both editable and read-only modal); replace-all write endpoint.common/features.py(one admin key gating template definition management, one gating per-entity value read vs write), gated viaPermissionCheckerlikesettings-certification-levels./settings/..., sidebar entry, mirrorcertification-levels-settings.tsx); product-detail button + modal mounted inviews/data-product-details.tsx(String input only; hidden when no active template; inputs+save disabled for read-only users).Keep it generic/entity-agnostic (entity type + id strings only); no customer-identifying naming (code word
dtag).Acceptance criteria
alembic upgrade headand downgrade run clean; single head maintained(entity_type, scope_type, scope_id); v1 usesallscope{ template, fields, values }for a given (entity_type, entity_id)Blocked by
None - can start immediately.
User stories addressed