Two lifecycle gaps found in the same audit. Both are low urgency — the marketplace has never been used in production and the contractor-type case degrades quietly — but both are one-way doors today.
B — marketplace libraries: global table, per-tenant writer, no way back
marketplace_libraries has no tenant_id; it is a shared catalogue. Its only writer is server/services/starter-content.service.ts:377, which runs during per-tenant provisioning. There is no create/update/delete API for it, for tenants or for sysadmin.
The result is visible in production: two rows, both seeded, both with download_count 0, one keyed by a stable slug and one by a UUID — two generations of the same seed, neither removable, and every tenant sees both.
tenant_library_imports has INSERT (marketplace.service.ts:418) and UPDATE (:521) and no DELETE. A tenant who imports a library can never un-import it. The one delete in the file (:500) is replace-mode pruning of prior-import comment rows, which is a different operation.
Production tenant_library_imports is 0 — the whole chain has never been exercised.
What is needed before anyone uses it:
- an owner for the catalogue (sysadmin CRUD, or move the seed out of per-tenant provisioning so it stops writing a global table)
- an un-import path that removes the import record and its
library_id-tagged comment rows
- a reconciliation for the two existing rows
D — deleting a contractor type leaves dangling references
ContractorTypeService.delete deletes unconditionally, with no reference check.
comments.recommendedContractorTypeId is a soft reference by design — the schema comment says "Soft ref → contractor_types.id (no DB FK per schema rules). Stale ref acceptable." So the delete is not a corruption, but the library UI's dropdown silently shows empty for every affected row and the tenant is not told how many they just orphaned.
Published reports are unaffected: server/lib/db/schema/repair-request.ts:45 notes that repair items store the resolved label, not the id.
Minimum fix: count referencing comments before deleting and say so in the confirm dialog. Full fix: offer to reassign them to another type.
Note this becomes more load-bearing once contractor types carry trade_slug (see #277) — a delete would then also drop the mapping to the immutable DEFECT_TRADES vocabulary.
Two lifecycle gaps found in the same audit. Both are low urgency — the marketplace has never been used in production and the contractor-type case degrades quietly — but both are one-way doors today.
B — marketplace libraries: global table, per-tenant writer, no way back
marketplace_librarieshas notenant_id; it is a shared catalogue. Its only writer isserver/services/starter-content.service.ts:377, which runs during per-tenant provisioning. There is no create/update/delete API for it, for tenants or for sysadmin.The result is visible in production: two rows, both seeded, both with
download_count0, one keyed by a stable slug and one by a UUID — two generations of the same seed, neither removable, and every tenant sees both.tenant_library_importshas INSERT (marketplace.service.ts:418) and UPDATE (:521) and no DELETE. A tenant who imports a library can never un-import it. The one delete in the file (:500) is replace-mode pruning of prior-import comment rows, which is a different operation.Production
tenant_library_importsis 0 — the whole chain has never been exercised.What is needed before anyone uses it:
library_id-tagged comment rowsD — deleting a contractor type leaves dangling references
ContractorTypeService.deletedeletes unconditionally, with no reference check.comments.recommendedContractorTypeIdis a soft reference by design — the schema comment says "Soft ref → contractor_types.id (no DB FK per schema rules). Stale ref acceptable." So the delete is not a corruption, but the library UI's dropdown silently shows empty for every affected row and the tenant is not told how many they just orphaned.Published reports are unaffected:
server/lib/db/schema/repair-request.ts:45notes that repair items store the resolved label, not the id.Minimum fix: count referencing comments before deleting and say so in the confirm dialog. Full fix: offer to reassign them to another type.
Note this becomes more load-bearing once contractor types carry
trade_slug(see #277) — a delete would then also drop the mapping to the immutableDEFECT_TRADESvocabulary.