Harden anchor_id column to NOT NULL #124
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/pal-e-api#124
Loading…
Add table
Add a link
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?
Lineage
todo-anchor-id-not-null— QA nit from PR #120 (Epilogue item 9b)Repo
forgejo_admin/pal-e-docsUser Story
As a platform operator
I want the database schema to enforce the anchor_id invariant
So that no code path can accidentally create a block without an anchor_id
Context
PR #120 guarantees all blocks get anchor_ids via the parser and create_block API. But the DB column
blocks.anchor_idremainsnullable=True. The invariant is only enforced at the application layer. AddingNOT NULLto the column pushes enforcement to the schema level — belt and suspenders.Must run AFTER PR #120's migration has backfilled all existing NULLs.
File Targets
Files the agent should modify:
src/pal_e_docs/models.py— changeanchor_idcolumn tonullable=Falsealembic/versions/— new migration:ALTER COLUMN anchor_id SET NOT NULLFiles the agent should NOT touch:
src/pal_e_docs/blocks/parser.py— already correct from PR #120src/pal_e_docs/routes/blocks.py— already correct from PR #120Acceptance Criteria
models.pyhasnullable=Falseonanchor_idnullable=TrueTest Expectations
pytest tests/ -valembic upgrade headpytest tests/ -vConstraints
down_revisioncorrectly)Checklist
Related
todo-anchor-id-not-null— tracking TODOPR #127 Review
BLOCKERS
None.
NITS
sync.py:104uses.get("anchor_id")which can returnNone-- Insrc/pal_e_docs/blocks/sync.pyline 104, the Block constructor receivesanchor_id=bd.get("anchor_id"). While this is currently safe (the parser always populates this key with a non-None value before returning), the.get()pattern silently returnsNoneif the key is missing, which would now cause a NOT NULL violation at the DB level. Consider changing tobd["anchor_id"]so a missing key raises aKeyErrorat the application level rather than a crypticIntegrityErrorfrom Postgres. This is non-blocking because the parser contract guarantees the key is present.recompile()line 42 still hasif b.anchor_id:guard -- Insync.pyline 42, the TOC builder filters onif b.anchor_id:. With the NOT NULL constraint, this condition is always truthy (empty strings are falsy but the parser never produces them). This guard is now redundant for heading blocks. Non-blocking -- defensive code is fine, just noting the dead branch.SOP COMPLIANCE
124-anchor-id-not-nullreferences issue #124)todo-anchor-id-not-null) and Forgejo issue #124CODE REVIEW
Migration chain is correct:
k1f2g3h4i5j6(PR #120) -- backfill + unique constraint, depends onj0e1f2g3h4i5l2g3h4i5j6k7(PR #122) -- vector embeddings, depends onk1f2g3h4i5j6m3h4i5j6k7l8(this PR) -- NOT NULL hardening, depends onl2g3h4i5j6k7Chain is linear and correct. The new migration sits at the head.
Safety check is correct: The upgrade function queries
SELECT COUNT(*) FROM blocks WHERE anchor_id IS NULLand raisesRuntimeErrorif any NULLs remain. This is a proper guard -- it will abort the migration before applying the constraint if prerequisites were somehow skipped.Downgrade is safe: Reverts
nullable=Falseback tonullable=True. This is a non-destructive operation -- existing data is preserved.Model annotation matches:
Mapped[str | None]withnullable=Truechanged toMapped[str]withnullable=False. The SQLAlchemy type annotation and column definition are consistent.existing_type=sa.String(200)matches the model definition: The column type in the migration matches the model'sString(200).Test changes are correct: Two test helpers in
test_blocks_compiled_pages.pythat created Block instances withoutanchor_idnow provide explicit values following the{block_type}-{position}pattern. All anchor_id values are unique within their respective notes.No unrelated changes: All 3 files (migration, model, test) are directly scoped to the NOT NULL hardening.
VERDICT: APPROVED