seam: Auditable.audit_entity_id / before_flush hook broken for server-generated (identity) PKs #57
Labels
No labels
in-progress
in-review
ready-for-agent
seam-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
patrick/Polaris#57
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Discovered while building PH1.1 (#9,
feature/db-schema-master-data).backend/src/polaris/db/audit.pyis outside PH1.1's owned paths (backend/alembic/versions/**,backend/src/polaris/db/models/**), but the bug blocks every futureAuditablemapped class that uses the project's own PK convention (docs/08-data-model.md: "Primary keysid bigint generated always as identity"), so I fixed it directly in the PH1.1 PR rather than widen the ticket or leave PH1.1 broken — flagging it here per the seam-request process in CLAUDE.md.Bug:
Auditable.audit_entity_id's default implementation readinspect(self).identity, and the hook fired onbefore_flush. Two problems, both real:before_flushruns before the flush'sINSERTexecutes, so a server-generated PK (the standard case for every master-data table) genuinely has no value yet — raisesValueError: audit_entity_id: instance has no identity (not yet flushed).after_flushwasn't enough on its own:inspect(obj).identityis stillNoneat that point (SQLAlchemy doesn't register the identity key until later in the flush's finalize step, confirmed empirically against 2.0.52) even though the plain attribute (obj.id) is already populated by then.It never showed up in
0001becauseSetting's primary key is a client-supplied string (key), and itsaudit_entity_idoverride readsself.keydirectly rather than going throughinspect().identity— it never exercises either bug.taskopts out ofAuditableentirely. So this was latent until a mapped class with a server-generated int PK actually mixed inAuditableand got inserted, which is exactly what every PH1.1 master-data table does.Fix (already merged in the PH1.1 PR,
backend/src/polaris/db/audit.py):before_flushtoafter_flush(still same transaction — newAuditLogrows added here just join the next autoflush/commit rather than this exact flush call, so a rollback still rolls them back too).Auditable.audit_entity_idnow reads the primary-key column's attribute directly (getattr(self, mapper.primary_key[0].key)) instead ofinspect(self).identity, since the attribute is populated byafter_flusheven though the identity key isn't yet.All existing
tests/integration/db/test_audit_hook.pycases (insert/update/delete/actor-context/task-opt-out) still pass unchanged. New coverage:tests/integration/db/test_master_data_models.pyexercises insert of severalAuditableclasses with identity PKs.No action needed unless another session already has local changes to
db/audit.pyon a different branch — rebase ontodevelopafter this merges. Opened for traceability, not as a blocker (already resolved).