CA/PKI Backend Project Context Stack Python 3 + psycopg (dict_row cursors) PostgreSQL database: ca Unit tests: unittest (python3 -m unittest discover) Database Schema (current assumptions) entity id INT identity PK creation_ts TIMESTAMPTZ default now() creator INT FK → entity(id) (the entity that created this one; nullable) name VARCHAR(100) NOT NULL type VARCHAR(...) NOT NULL (e.g. person, group, device, alias) public_key VARCHAR(300) NOT NULL symmetrical_key VARCHAR(100) NULL status VARCHAR(...) NOT NULL default 'active' (values: 'active', 'revoked') expiration DATE NULL Index on entity(name) (and other indexes as needed) group_member group_id INT FK → entity(id) ON DELETE CASCADE member_id INT FK → entity(id) ON DELETE CASCADE role VARCHAR(10) PK (group_id, member_id) Index (member_id, group_id) Groups can contain any entity type, including other groups and devices. property Columns: (id INT FK → entity(id), property_name VARCHAR(100)) PK (id, property_name) Used for flags/roles such as "creator" metadata Intended “singleton row” table, enforced at application level Columns: name, comment, private_key, public_key log id SERIAL PK ts TIMESTAMPTZ default now() entry TEXT NOT NULL Every API mutation must log one row here. Core Business Rules Creators are not an entity type creator is a property (property_name='creator') on a person entity. insert_creator() creates a person entity and inserts the creator property. Revoked entities are immutable Any mutation on an entity requires ensure_entity_active(cursor, entity_id). Revoked entities cannot: Join groups or accept members Add/delete properties Change keys (public_key, symmetrical_key) Change status again Logging All changes to entity, group_member, property, metadata must call log_change(cursor, "...") Logging happens inside the same transaction (no extra commits). Python Modules (current structure) ca_core/entity.py Must provide: ensure_entity_active(cursor, entity_id) insert_creator(cursor, name, public_key) enroll_person(cursor, name, public_key, creator_id) create_group(cursor, name, public_key, creator_id) create_alias(cursor, target_entity_id) get_entity(cursor, entity_id) set_entity_status(cursor, entity_id, status, changed_by) (requires active entity) set_entity_keys(cursor, entity_id, public_key, changed_by) (active-only) set_symmetrical_key(cursor, entity_id, key, changed_by) (active-only) get_symmetrical_key(cursor, entity_id) ca_core/group_member.py Uses member_id (not person_id) Must prevent adding revoked groups/members (via ensure_entity_active) Logs add/remove membership ca_core/property.py Table is property(id, property_name) (NOT entity_id/name) Must reject mutations if entity revoked (immutability) Logs set/delete property ca_core/metadata.py Updates metadata fields and logs changes ca_core/db_logging.py log_change(cursor, message: str) inserts into log(entry) Tests tests/test_entity.py, tests/test_group.py, tests/test_property.py, tests/test_metadata.py Tests verify: Core behaviors (create, enroll, group membership, revoke immutability) Log entry is created for mutations (case-insensitive substring checks) Run via: python3 -m unittest discover Known gotchas Avoid naming a module logging.py (conflicts with stdlib). Use db_logging.py. Schema and code must stay aligned (e.g., property.id/property_name, group_member.member_id).