Enforce required-field completeness when transitioning a record to Public #16

Closed
opened 2026-06-02 12:06:07 +00:00 by logaritmisk · 1 comment
Owner

Context

db::catalog::set_object_fields intentionally does not enforce required-field completeness — a caller may set any subset. Its doc comment notes that completeness "belongs to the publish gate (when moving to Visibility::Public)". Plan 7 built the stepwise transition machine but deferred this gate to keep scope tight.

What to do

When transitioning a record to Visibility::Public (in set_visibility, or a dedicated publish path), validate that every FieldDefinition with required = true has a present value on the object. Reject the publish with a clear error listing the missing required fields if not.

Open questions

  • Should the gate live in db::catalog::set_visibility (so every caller is protected) or at the service/API layer?
  • Required-completeness for the typed inventory minimum vs. flexible required fields — both, presumably.

References

  • crates/db/src/catalog.rsset_object_fields doc comment; set_visibility
  • domain::FieldDefinition { required, .. }
  • Plan: docs/plans/2026-06-02-publishing-public-api.md (Notes for follow-on plans)
## Context `db::catalog::set_object_fields` intentionally does **not** enforce required-field completeness — a caller may set any subset. Its doc comment notes that completeness "belongs to the publish gate (when moving to `Visibility::Public`)". Plan 7 built the stepwise transition machine but deferred this gate to keep scope tight. ## What to do When transitioning a record to `Visibility::Public` (in `set_visibility`, or a dedicated publish path), validate that every `FieldDefinition` with `required = true` has a present value on the object. Reject the publish with a clear error listing the missing required fields if not. ## Open questions - Should the gate live in `db::catalog::set_visibility` (so every caller is protected) or at the service/API layer? - Required-completeness for the typed inventory minimum vs. flexible required fields — both, presumably. ## References - `crates/db/src/catalog.rs` — `set_object_fields` doc comment; `set_visibility` - `domain::FieldDefinition { required, .. }` - Plan: `docs/plans/2026-06-02-publishing-public-api.md` (Notes for follow-on plans)
Author
Owner

Implemented and merged to main (e96f74f, no-op fix 8cfcf07).

Decisions (the issue's open questions):

  • Gate lives in db::catalog::set_visibility — every caller is protected and the check is atomic with the transition (integrity-critical logic stays in db).
  • Only flexible required fields are checked. The typed inventory-minimum columns (object_number, object_name, number_of_objects) are already NOT NULL in 0003_object.sql; the other typed columns are optional. So the gate covers field definitions with required = true, verifying each key is present and non-null in the object's fields JSONB.

Behaviour:

  • Transitioning to Public with missing required fields → VisibilityError::MissingRequiredFields(keys); the admin publish endpoint maps it to 422, and the transition is rejected (visibility unchanged, no audit).
  • The gate fires only on an actual transition into public, so the documented set-to-current idempotent no-op is preserved even if a required field is introduced after publish (regression test added).

Tests: db reject-then-succeed round-trip + the re-set no-op; api real-HTTP 422 with visibility unchanged. Full workspace green.

Note: the response is a bare 422 (consistent with the other admin validation errors, which also don't return a structured body). The db error variant carries the missing-key list; surfacing it in the response body — across all the admin 422 paths — would be a worthwhile separate enhancement. Closing.

Implemented and merged to `main` (`e96f74f`, no-op fix `8cfcf07`). **Decisions (the issue's open questions):** - **Gate lives in `db::catalog::set_visibility`** — every caller is protected and the check is atomic with the transition (integrity-critical logic stays in `db`). - **Only flexible required fields are checked.** The typed inventory-minimum columns (`object_number`, `object_name`, `number_of_objects`) are already `NOT NULL` in `0003_object.sql`; the other typed columns are optional. So the gate covers field definitions with `required = true`, verifying each key is present and non-null in the object's `fields` JSONB. **Behaviour:** - Transitioning to `Public` with missing required fields → `VisibilityError::MissingRequiredFields(keys)`; the admin publish endpoint maps it to **422**, and the transition is rejected (visibility unchanged, no audit). - The gate fires only on an *actual* transition into public, so the documented set-to-current idempotent no-op is preserved even if a required field is introduced after publish (regression test added). **Tests:** `db` reject-then-succeed round-trip + the re-set no-op; `api` real-HTTP 422 with visibility unchanged. Full workspace green. **Note:** the response is a bare 422 (consistent with the other admin validation errors, which also don't return a structured body). The db error variant carries the missing-key list; surfacing it in the response body — across all the admin 422 paths — would be a worthwhile separate enhancement. Closing.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logaritmisk/biggus-dickus#16