Repository navigation
feat(import): domain reconciliation + ontos* round-trip on ODCS/ODPS import (#851) - #862
Conversation
…ODCS & ODPS (#855) Unify contract (ODCS) and product (ODPS) upload so both accept a single entity, an array of entities in one file, and multiple files in one operation, reported via a truthful BatchImportResult summary (N created / M skipped / K failed). Backend - New shared models.import_results.BatchImportResult / ImportItemResult. The `skipped` status and source_id/entity_id provenance fields are part of the contract from the start so the sibling import features (#851 domain reconciliation, #853 UUID-as-PK duplicate handling) can populate them without another response-shape change. - Contracts: add parse_uploaded_entities (array-in-file parity) and create_contracts_from_files; /data-contracts/upload now takes List[UploadFile], /odcs/import accepts an object or an array. Both return BatchImportResult. - Products: extract _parse_products_content, add create_products_from_files; /data-products/upload takes List[UploadFile] and returns BatchImportResult. Fix a latent bug where json.JSONDecodeError was referenced in an except clause before json was imported (only reachable now that non-json files flow through). - Per-entity failures are recorded as failed items and never abort the batch (each entity commits in its own transaction). Frontend - Shared types/import-results.ts. data-products and data-contracts views send every selected file under `files` (multi-file), and render the truthful summary + per-entity failures. Tests - New manager-level unit tests for both entities (array-in-file, multi-file, bad-entity-continues, unsupported/unparseable file, sourceId preservation). - Updated the contract upload/import integration tests to the new files/BatchImportResult contract. Session: claude -r 37a1420d-86af-4380-bef3-0b74c6f8b4b9
…import (#851) Best-effort domain reconciliation on import, lossless round-trip on export, and a per-upload "Create missing domains" toggle. Stacked on #855 (feat/import-batch). Reconciliation (import, both entities via the shared DomainExportAdapter) - Match ID first, then unique name. Priority: ontosDomainId (authoritative) → legacy domainIds/primaryDomainId/domainId → standard `domain` name (+ legacy additionalDomains, still read for backward compat). - No match → left UNASSIGNED (the previous silent auto-create in contracts' _resolve_domain is removed). The raw incoming domain string(s) are always preserved as the `ontosOriginalDomain` provenance custom property. - Opt-in "Create missing domains" toggle (default OFF) auto-creates unresolved names by name and assigns them. Threaded route → manager → adapter; applied per-upload to every entity in the batch. Contract file upload + product upload take a `create_missing_domains` form field; the paste /odcs/import takes a query param. Round-trip (export, both entities) - apply_odcs now emits `ontosDomainId` (all assigned ids, primary first — the authoritative rebind key) as a custom property, and keeps the standard `domain` = primary name. `ontosOriginalDomain` rides along as a stored property. - Drops the legacy top-level `domainIds`/`primaryDomainId` and the `customProperties.additionalDomains` entry (superseded by `ontosDomainId`); both are still READ on import so pre-#851 exports keep working. - merge_custom_properties now preserves `ontosDomainId` across the export builders' customProperties rebuild. Also fixes a latent `json` UnboundLocalError in build_odcs_from_db: four redundant inner `import json` statements shadowed the module-level import, crashing the custom- properties deserialization — newly exercised because ontos* values are list-valued (stored as JSON strings, deserialized on export). Frontend - "Create missing domains" toggle in the contract upload dialog (covers file drop and pasted JSON) and a new compact product import-options dialog hosting the same toggle; both post the flag with the upload. Tests - Reworked test_domain_export_adapter to the ontosDomainId/ontosOriginalDomain contract; added create_missing + stale-id-fallthrough + provenance coverage. - New manager-level test_import_domain_reconcile: match, no-match-unassigned + provenance, create_missing toggle, and export→re-import UUID rebind, for both contracts and products. Verified live (isolated 8140/3140): API round-trip rebinds by UUID; UI toggle auto-creates a missing domain on product upload. Session: claude -r 37a1420d-86af-4380-bef3-0b74c6f8b4b9
…fpiz PRs Rebuilt test/combined-mcp-odcs-odps on the current development tip and merged every open PR labelled stakeholder/fpiz: - import stack #860/#862/#863 (feat/import-id-adopt) - #864 resolve-refs, #856 domain/tag dropdown scroll fix - search #865 (improve-search) + #867 (improve-search-phase2) Dropped a stray unused search_scoring import that the search-branch merge left in data_products.py (query_index is used). Registry imports; 43 search+import unit tests pass. Co-authored-by: Isaac <no-reply@databricks.com>
) The batch importers created a contract from any uploaded file (and a product from any file on the products page), silently turning ODPS Data Products into Data Contracts because kind was never checked. Both upload views also only rendered failed items, so skips were invisible. - Add src/common/entity_kind.py: classify an entity as contract/product via kind (primary) + exclusive structural markers; ambiguous stays unknown. - create_contracts_from_files / create_products_from_files skip cross-type entities as 'skipped' items with a message pointing to the correct page. - Both upload views surface skipped (not just failed) in the import summary. - Tests for the classifier and cross-type/mixed-batch import. Folded into #860 (batch import). Tracks #929. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
Drop the trailing slash so the pattern matches a node_modules symlink (a file to git) as well as a real directory. The per-worktree node_modules symlinks used in local dev otherwise slip past 'node_modules/' and can be staged by git add -A. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
…es (#929) A README (or any free-form text) dropped on the contract uploader was turned into a junk contract: parse_uploaded_entities wrapped unstructured content into a minimal contract, and the kind guard only rejected positively-identified products, letting 'unknown' entities through. - parse_uploaded_entities now accepts only .yaml/.yml/.json structured payloads; an unsupported type or non-mapping/array raises (reported as a failed item). - Both batch importers now require a positively recognized entity: a contract must classify as 'contract', a product as 'product'. Cross-type files are skipped with a page hint; unrecognized files are skipped with a clear reason. - Tests: README/text rejection, structured-but-unrecognized skip, cross-type; updated the integration test that asserted the old lenient text fallback. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
mvkonchits-db
left a comment
There was a problem hiding this comment.
Requesting changes on one authorization issue. Items marked (verified) I confirmed in the code.
Must fix:
- (verified)
domain_export_adapter._create_domaincallsdomain_repo.create(db=db, obj_in=DataDomainCreate(name=name))directly. Thecreate_missing_domainstoggle therefore lets any user with contract or product write permission create top-level domains, with nodata-domainspermission check, andcreated_byis accepted but not passed to the repository, so attribution is lost. Please require data-domains write permission for the toggle (or check it in the route) and record the creator.
Should fix (reported, not traced line by line by me):
2. A partly stale ontosDomainId list returns only the surviving ids with no name fallback. Importing [deleted-primary, valid-secondary] with domain: Primary drops Primary and promotes the secondary.
3. Exporting an entity with no domain assignments does not remove a stored stale ontosDomainId, so re-import reassigns the removed domain.
4. Name reconciliation is exact, untrimmed and case-sensitive (" finance " vs Finance is left unassigned, or creates a duplicate when auto-create is on).
Stack notes: relies on #860's per-entity batch rollback, and #863 builds on this export/custom-property pipeline, so these defects carry into ID-adoption imports.
…jection build_odcs_from_db accepts a db_session but the semantic-assignment injection block ignored it and opened a second session via get_db_session(), which commits on exit. Under the test harness's shared single in-memory SQLite connection that commit lands on the outer per-test transaction, defeating its rollback and leaking committed contracts/products across tests (inflating the global-count assertions in test_version_family once the import round-trip tests started exercising the export path). In production it was a redundant session + commit on every export. Reuse the passed session when present (read-only lookups need no own session); fall back to get_db_session() only when no session was provided. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
…ors (#855 review) Addresses mvkonchits-db's review on #860: 1. Cap uploads before parsing: new src/common/upload_limits.read_uploads_capped enforces max file count, per-file bytes and total bytes (413 on breach), reading one byte past the per-file cap so an oversize/alias-heavy payload cannot be loaded fully into memory. Both upload routes use it. 2. Decode UTF-8 per file in the contract route (not up front): a single non-UTF-8 file is now a failed item, no longer a 500 that aborts the batch. 3. Stop echoing raw exception text to clients — the two contract 500 details, the product 500 detail, and the per-entity create-failure item messages now return a generic message and log the full detail. (Parse/format messages, which describe the user's own file and carry no server internals, are kept.) 4. An empty ODPS array is a failed item (parity with the contract path), not a silent "0 imported successfully". Tests: unit coverage for the caps helper (count/per-file/total 413) and the product empty-array parity; route tests for non-UTF-8 -> failed item (not 500) and the file-count 413. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
…rd creator (#851 review) Addresses mvkonchits-db's must-fix on #862 (the authorization issue) plus the case/whitespace name-match should-fix: - The "create missing domains" toggle now requires data-domains WRITE on top of the entity's own write permission, enforced at all three import endpoints (contract upload, contract paste-import, product upload) via a new reusable user_has_feature_level() helper. Without it a contract/product writer could create top-level domains they cannot otherwise manage; now a clear 403. - Auto-created domains record the real uploader instead of the repo's "system" fallback: DataDomainCreate gains an optional created_by, threaded through _create_domain. - Name reconciliation is now trimmed and case-insensitive (data_domain_repo.get_by_name_normalized), so " finance " matches "Finance" rather than being left unassigned or creating a near-duplicate under auto-create. Also merges #860 (upload hardening + the cross-type/non-ODCS guards) so this branch is a proper superset of its base. Deferred to a follow-up (reviewer "should fix", not blocking): stale ontosDomainId list name-fallback (#851.2) and clearing a stale ontosDomainId on domain-less export (#851.3). Tests: real-creator attribution and trimmed/case-insensitive match (manager-level). Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35
mvkonchits-db
left a comment
There was a problem hiding this comment.
Re-reviewed 4610d15. The permission gate is what I asked for, but the same commit breaks product upload.
Blocker (verified):
upload_data_productsindata_product_routes.pystill callsmanager.create_products_from_files(file_inputs, ...), but this commit deleted the line that buildsfile_inputs(file_inputs = [(name, raw) for name, raw, _ in capped]). Every product upload now raises NameError and returns the generic 500. I confirmed with an AST check thatfile_inputsis read at line 1640 and never assigned in the function, its parameters or the module. It is defined at #860's head, so it looks like a merge resolution slip.- CI cannot catch this here. A PR whose base is not
developmentonly runs the title check, so none of the backend tests ran on this head. - Please restore the line, and add a route level test for product upload and a test that asserts a 403 when
create_missing_domainsis on without data-domains write.
Verified as fixed:
- The toggle now requires data-domains write at all three import endpoints, with a clear 403 (the existing handler re-raises HTTPException).
- Auto-created domains record the real creator.
- Name matching is trimmed and case-insensitive.
Notes:
user_has_feature_levelresolves group based permissions only (no team role override). It fails closed, so it is not blocking, but a short comment would help.- The deferred items (stale
ontosDomainIdlist, clearing a stale id on export) are fine as a follow-up. Please file it. - #863 and #930 merged an older head of this branch, so they will need a rebase after the fix.
…ute (#862 re-review) The previous commit (4610d15, data-domains gate) silently dropped the `file_inputs = [(name, raw) for name, raw, _ in capped]` line that #860 had introduced to feed `create_products_from_files`. Every product upload then raised `NameError` inside the generic `except Exception`, surfacing as a generic 500. A PR whose base isn't `development` only runs the title check, so backend tests never flagged it. Changes: - Restore the `file_inputs` build-up right after `read_uploads_capped()`. - Add two route-level tests: a golden-path product upload (guards the NameError regression) and a 403 for `create_missing_domains=True` when the user lacks `data-domains` write. - Expand the `user_has_feature_level` docstring to call out that it resolves group-based permissions only (no team/applied-role override) and fails closed on resolution error. Follow-up tracker filed as #944 (stale ontosDomainId on re-import + clearing a stale id on export). Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 Co-authored-by: Isaac <no-reply@databricks.com>
|
Thanks for the catch — all four items addressed in 0266576.
Rebasing #863 and #930 onto this head next so they carry the fix. |
…review) # Conflicts: # src/backend/src/routes/data_product_routes.py
mvkonchits-db
left a comment
There was a problem hiding this comment.
Re-reviewed 0266576e2. The file_inputs regression is fixed (I re-ran the AST check: it is read at line 1643 and assigned in the function). The commit also adds a real route test for product upload and a 403 test for the data-domains gate, and the user_has_feature_level docstring now states the group-only and fail-closed behavior. Together with the gate, creator attribution and case-insensitive matching from 4610d15c6, everything I asked for on this PR is done. Approving the content.
It cannot merge yet. I retargeted this PR from feat/import-batch to development (that branch is stale now that #860 is merged), and GitHub reports it as conflicting with 25 files. #860 was squash-merged, so the copies of its commits in this branch no longer line up with development. Please rebase onto development and drop the #860 commits, for example git rebase --onto origin/development dccf7c50d feat/import-domain-recon, then push with --force-with-lease. Resolve any overlap with #860's later fixes in favor of what is on development.
Two reasons this matters beyond the conflict:
- The backend tests have never run on this branch, because stacked PRs only run the title check. The
file_inputsbug got through for that reason. Once it targetsdevelopmentthe full suite runs. - A push dismisses my approval, so I will re-review the rebased diff and approve again. It should be just the #862 change.
The follow-up for the stale ontosDomainId items is tracked in #944, as noted in the commit. Thanks for the quick turnaround.
…icts post #860/#865/#867) # Conflicts: # src/backend/src/controller/data_contracts_manager.py # src/backend/src/controller/data_products_manager.py # src/backend/src/routes/data_contracts_routes.py # src/backend/src/routes/data_product_routes.py # src/frontend/src/views/data-contracts.tsx # src/frontend/src/views/data-products.tsx
mvkonchits-db
left a comment
There was a problem hiding this comment.
Re-reviewed the merged result at 44fb41fe6. Approving.
What I checked:
- The diff against
developmentis now 13 files and is only this change. CI ran the full suite on thedevelopmentbase and is green. - I compared every line the PR adds against the four commits I reviewed earlier (
ba05e7bfb,1f3fb48a7,4610d15c6,0266576e2). Nothing in the final diff is outside them, so the conflict resolution added no unreviewed content. The only lines from those commits that are not in the final diff were superseded (the olddomain_repo.createcall, replaced by thecreated_byversion) or already ondevelopmentthrough #860 (thefile_inputsline). - All of my fixes are still present: the data-domains write gate at the three import endpoints, creator attribution, trimmed and case-insensitive matching, and the restored
file_inputsassignment (read at line 1643, assigned in the function). - pyflakes finds no new undefined names. The two it reports (
DataContractRead,DataProductDb) are string forward references that already exist ondevelopment. - The
import jsonlines removed frombuild_odcs_from_dbare fine, sincejsonis imported at module level.
One behavior change to be aware of, intended and safer: the old silent auto-create of a missing domain by name (as system) in _resolve_domain is gone. An unknown domain name now leaves the entity unassigned unless the explicit create-missing toggle is on.
#863 builds on this branch, so it will need to move onto development once this lands.
databrickslabs#930) * feat(import): single + batch (array-in-file + multi-file) upload for ODCS & ODPS (databrickslabs#855) Unify contract (ODCS) and product (ODPS) upload so both accept a single entity, an array of entities in one file, and multiple files in one operation, reported via a truthful BatchImportResult summary (N created / M skipped / K failed). Backend - New shared models.import_results.BatchImportResult / ImportItemResult. The `skipped` status and source_id/entity_id provenance fields are part of the contract from the start so the sibling import features (databrickslabs#851 domain reconciliation, databrickslabs#853 UUID-as-PK duplicate handling) can populate them without another response-shape change. - Contracts: add parse_uploaded_entities (array-in-file parity) and create_contracts_from_files; /data-contracts/upload now takes List[UploadFile], /odcs/import accepts an object or an array. Both return BatchImportResult. - Products: extract _parse_products_content, add create_products_from_files; /data-products/upload takes List[UploadFile] and returns BatchImportResult. Fix a latent bug where json.JSONDecodeError was referenced in an except clause before json was imported (only reachable now that non-json files flow through). - Per-entity failures are recorded as failed items and never abort the batch (each entity commits in its own transaction). Frontend - Shared types/import-results.ts. data-products and data-contracts views send every selected file under `files` (multi-file), and render the truthful summary + per-entity failures. Tests - New manager-level unit tests for both entities (array-in-file, multi-file, bad-entity-continues, unsupported/unparseable file, sourceId preservation). - Updated the contract upload/import integration tests to the new files/BatchImportResult contract. Session: claude -r 37a1420d-86af-4380-bef3-0b74c6f8b4b9 * feat(import): domain reconciliation + ontos* round-trip on ODCS/ODPS import (databrickslabs#851) Best-effort domain reconciliation on import, lossless round-trip on export, and a per-upload "Create missing domains" toggle. Stacked on databrickslabs#855 (feat/import-batch). Reconciliation (import, both entities via the shared DomainExportAdapter) - Match ID first, then unique name. Priority: ontosDomainId (authoritative) → legacy domainIds/primaryDomainId/domainId → standard `domain` name (+ legacy additionalDomains, still read for backward compat). - No match → left UNASSIGNED (the previous silent auto-create in contracts' _resolve_domain is removed). The raw incoming domain string(s) are always preserved as the `ontosOriginalDomain` provenance custom property. - Opt-in "Create missing domains" toggle (default OFF) auto-creates unresolved names by name and assigns them. Threaded route → manager → adapter; applied per-upload to every entity in the batch. Contract file upload + product upload take a `create_missing_domains` form field; the paste /odcs/import takes a query param. Round-trip (export, both entities) - apply_odcs now emits `ontosDomainId` (all assigned ids, primary first — the authoritative rebind key) as a custom property, and keeps the standard `domain` = primary name. `ontosOriginalDomain` rides along as a stored property. - Drops the legacy top-level `domainIds`/`primaryDomainId` and the `customProperties.additionalDomains` entry (superseded by `ontosDomainId`); both are still READ on import so pre-databrickslabs#851 exports keep working. - merge_custom_properties now preserves `ontosDomainId` across the export builders' customProperties rebuild. Also fixes a latent `json` UnboundLocalError in build_odcs_from_db: four redundant inner `import json` statements shadowed the module-level import, crashing the custom- properties deserialization — newly exercised because ontos* values are list-valued (stored as JSON strings, deserialized on export). Frontend - "Create missing domains" toggle in the contract upload dialog (covers file drop and pasted JSON) and a new compact product import-options dialog hosting the same toggle; both post the flag with the upload. Tests - Reworked test_domain_export_adapter to the ontosDomainId/ontosOriginalDomain contract; added create_missing + stale-id-fallthrough + provenance coverage. - New manager-level test_import_domain_reconcile: match, no-match-unassigned + provenance, create_missing toggle, and export→re-import UUID rebind, for both contracts and products. Verified live (isolated 8140/3140): API round-trip rebinds by UUID; UI toggle auto-creates a missing domain on product upload. Session: claude -r 37a1420d-86af-4380-bef3-0b74c6f8b4b9 * feat(import): adopt YAML entity UUID as primary key + duplicate handling (databrickslabs#853) Adopt a valid, non-colliding UUID from the imported ODCS/ODPS file as the entity's primary key so YAML-expressed cross-entity links (product↔contract) survive import, with a lossless id round-trip on export. Stacked on databrickslabs#851 (feat/import-domain-recon). Adoption (both entities) - create_product / create_from_upload take adopt_ids (default on): a valid UUID that does not already exist in that entity's table is adopted as the PK; a non-UUID or a colliding/adopt-off id falls back to a generated UUID (today's behavior). Contracts adopt via id= on DataContractDb; products via product_data['id']. - The incoming id is always recorded as the ontosOriginalId provenance custom property; the legacy sourceId is still written for non-UUID ids during the transition (read either). Export re-derives ontosEntityId (current PK) as a custom property. Duplicate-id handling (batch, both entities) - create_*_from_files take on_duplicate = "skip" (default) | "new". A payload id that is a valid UUID already present is either recorded as skipped ("already present (same id)") — making re-import idempotent — or imported as a new copy with a fresh UUID. Import stays create-only. Uses the skipped status already in BatchImportResult (databrickslabs#855). Routes/frontend - upload endpoints take adopt_ids + on_duplicate form fields; the paste /odcs/import takes them as query params. Both import dialogs gain "Adopt IDs from file" (default on) and "Import duplicate IDs as new copies" (default off) toggles; success toasts now report skipped counts. Also fixes build_odps_export: it accessed the ODPS API model by validation-alias names (product.api_version, input_ports, output_ports, support_channels, custom_properties, authoritative_definitions, port.contract_id) that are not attributes on the camelCase model, so the product ODPS export endpoint crashed with AttributeError for every product. Corrected to the real field names; the endpoint now works (and carries the ontos* keys). Tests - New test_import_id_adopt: adopt valid UUID, non-UUID fallback + sourceId/ontosOriginalId, ontosEntityId on export, duplicate skip (default) and import-as-new — for both entities. - Made the domain-export-adapter test's domain fixture get-or-create so the suite is order-independent (managers commit, so rows persist across tests in the shared session). Verified live (isolated 8140/3140): adoption keeps the file UUID; duplicate skip/new both work; product + contract exports emit ontosOriginalId/ontosEntityId; UI toggles render. Session: claude -r 37a1420d-86af-4380-bef3-0b74c6f8b4b9 * fix(import): guard batch upload against cross-type ODCS/ODPS files (databrickslabs#929) The batch importers created a contract from any uploaded file (and a product from any file on the products page), silently turning ODPS Data Products into Data Contracts because kind was never checked. Both upload views also only rendered failed items, so skips were invisible. - Add src/common/entity_kind.py: classify an entity as contract/product via kind (primary) + exclusive structural markers; ambiguous stays unknown. - create_contracts_from_files / create_products_from_files skip cross-type entities as 'skipped' items with a message pointing to the correct page. - Both upload views surface skipped (not just failed) in the import summary. - Tests for the classifier and cross-type/mixed-batch import. Folded into databrickslabs#860 (batch import). Tracks databrickslabs#929. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * chore: ignore node_modules symlinks, not just directories Drop the trailing slash so the pattern matches a node_modules symlink (a file to git) as well as a real directory. The per-worktree node_modules symlinks used in local dev otherwise slip past 'node_modules/' and can be staged by git add -A. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * fix(import): reject non-ODCS/ODPS files instead of fabricating entities (databrickslabs#929) A README (or any free-form text) dropped on the contract uploader was turned into a junk contract: parse_uploaded_entities wrapped unstructured content into a minimal contract, and the kind guard only rejected positively-identified products, letting 'unknown' entities through. - parse_uploaded_entities now accepts only .yaml/.yml/.json structured payloads; an unsupported type or non-mapping/array raises (reported as a failed item). - Both batch importers now require a positively recognized entity: a contract must classify as 'contract', a product as 'product'. Cross-type files are skipped with a page hint; unrecognized files are skipped with a clear reason. - Tests: README/text rejection, structured-but-unrecognized skip, cross-type; updated the integration test that asserted the old lenient text fallback. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * feat(import): unify ODCS/ODPS upload dialogs into one shared component The Data Contracts and Data Products upload dialogs had drifted apart: one had a drag-&-drop zone + paste box, the other only a 'Choose file(s)' button and a subtitle. Extract a single <ImportEntityDialog> used by both so they are identical: subtitle, the three import toggles, a multi-file drag-&-drop zone, and a paste-JSON box. - New components/common/import-entity-dialog.tsx (dropzone multiple:true). - Both views pass their own i18n labels, toggle state, and upload/paste handlers; data-products gains multi-file selection + drag-drop + paste. - Tighten accept to .json/.yaml/.yml (drop the stale 'plain text' affordance, matching the server-side non-ODCS/ODPS rejection). Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * fix(import): correct contract uploader copy to match ODCS-only intent The dropzone advertised 'plain text' and 'a contract file' (singular); update the en strings to 'JSON or YAML (ODCS)' and 'file(s)', add the dialog subtitle key, and drop 'text files' from the upload tooltip — consistent with the shared ImportEntityDialog and the server-side non-ODCS rejection. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * fix(export): reuse caller's session in build_odcs_from_db semantic injection build_odcs_from_db accepts a db_session but the semantic-assignment injection block ignored it and opened a second session via get_db_session(), which commits on exit. Under the test harness's shared single in-memory SQLite connection that commit lands on the outer per-test transaction, defeating its rollback and leaking committed contracts/products across tests (inflating the global-count assertions in test_version_family once the import round-trip tests started exercising the export path). In production it was a redundant session + commit on every export. Reuse the passed session when present (read-only lookups need no own session); fall back to get_db_session() only when no session was provided. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * fix(import): harden batch upload — caps, per-file decode, generic errors (databrickslabs#855 review) Addresses mvkonchits-db's review on databrickslabs#860: 1. Cap uploads before parsing: new src/common/upload_limits.read_uploads_capped enforces max file count, per-file bytes and total bytes (413 on breach), reading one byte past the per-file cap so an oversize/alias-heavy payload cannot be loaded fully into memory. Both upload routes use it. 2. Decode UTF-8 per file in the contract route (not up front): a single non-UTF-8 file is now a failed item, no longer a 500 that aborts the batch. 3. Stop echoing raw exception text to clients — the two contract 500 details, the product 500 detail, and the per-entity create-failure item messages now return a generic message and log the full detail. (Parse/format messages, which describe the user's own file and carry no server internals, are kept.) 4. An empty ODPS array is a failed item (parity with the contract path), not a silent "0 imported successfully". Tests: unit coverage for the caps helper (count/per-file/total 413) and the product empty-array parity; route tests for non-UTF-8 -> failed item (not 500) and the file-count 413. Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * fix(import): gate create-missing-domains on data-domains write + record creator (databrickslabs#851 review) Addresses mvkonchits-db's must-fix on databrickslabs#862 (the authorization issue) plus the case/whitespace name-match should-fix: - The "create missing domains" toggle now requires data-domains WRITE on top of the entity's own write permission, enforced at all three import endpoints (contract upload, contract paste-import, product upload) via a new reusable user_has_feature_level() helper. Without it a contract/product writer could create top-level domains they cannot otherwise manage; now a clear 403. - Auto-created domains record the real uploader instead of the repo's "system" fallback: DataDomainCreate gains an optional created_by, threaded through _create_domain. - Name reconciliation is now trimmed and case-insensitive (data_domain_repo.get_by_name_normalized), so " finance " matches "Finance" rather than being left unassigned or creating a near-duplicate under auto-create. Also merges databrickslabs#860 (upload hardening + the cross-type/non-ODCS guards) so this branch is a proper superset of its base. Deferred to a follow-up (reviewer "should fix", not blocking): stale ontosDomainId list name-fallback (databrickslabs#851.2) and clearing a stale ontosDomainId on domain-less export (databrickslabs#851.3). Tests: real-creator attribution and trimmed/case-insensitive match (manager-level). Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 * WIP: db column can_adopt_entity_ids (will squash into feature commit) * feat(settings): can_adopt_entity_ids app-role privilege (databrickslabs#853 review) Adds a new per-role boolean privilege shown in the role editor Privileges tab under a new "Import Privileges" section, as the explicit app-role gate for adopting an entity's UUID from an imported file as the primary key. The gate itself (databrickslabs#853.1) replaces the previous default-on-for-any-writer behavior. - DB: AppRoleDb.can_adopt_entity_ids Boolean (default false, server_default 'false'); migration b1_add_adopt_entity_ids descends from n1_search_documents. - Pydantic: AppRoleBase/AppRoleUpdate/AppRole gain can_adopt_entity_ids; settings_manager._map_db_to_api surfaces it (repo write path serializes booleans unchanged via model_dump). - AuthorizationManager.user_can_adopt_entity_ids(groups): mirrors is_user_ontos_admin, resolves roles by case-insensitive group intersection. - Frontend: new "Import Privileges" section in the Privileges tab of role-form-dialog, mirroring Admin Role; TS AppRole gains the field; i18n keys added to all 8 locales (en + de/es/fr/it/ja/nl/pt). The enforcement at the import routes (and the flip of adopt_ids default to false) lands in the next commit. Co-authored-by: Isaac <no-reply@databricks.com> * fix(import): adopt-ids gate, UUID canonicalization, on_duplicate validation (databrickslabs#853 review) Addresses mvkonchits-db's must-fix items on databrickslabs#863: 1. ID squatting (databrickslabs#853.1): adopt_ids defaults to False on all three import endpoints. Even when explicitly on, adoption requires either the new per-role `can_adopt_entity_ids` privilege (prior commit) or feature ADMIN on the target entity. Non-privileged callers silently fall back to a generated UUID with the file id preserved as ontosOriginalId provenance — no hard 403 so unattended tooling degrades gracefully. 2. Canonicalize UUIDs before dup lookup and storage (databrickslabs#853.2): a new _canonical_uuid() parses via uuid.UUID (accepting upper/braced/urn/ hyphenless spellings) and returns the canonical lowercase hyphenated form. All three dup-check sites + both adoption sites use it, so a spelling variant can no longer bypass the duplicate check or create a second identity for the same logical UUID. 3. Validate on_duplicate at the route (databrickslabs#853.3): must be exactly 'skip' or 'new', otherwise 400. A typo no longer falls through to the "new" path and silently duplicates. 4. Skipped-dup existence oracle (databrickslabs#853.5): the batch result for a skipped duplicate now only surfaces the existing row's id/name when the caller is a feature-admin (new reveal_dup_detail flag computed at the route). Non-admin callers get a generic "Supplied id cannot be adopted" skip without entity_id/name, so a sibling import can't be used to probe for invisible entities. 5. Intra-batch reference remap for on_duplicate=new (databrickslabs#853.4): documented as vacuous given current architecture (ODCS carries no intra-file cross-refs; ODPS's only cross-ref inputPort.contractId points at contracts, which are imported through a separate endpoint). Scaffold hook noted for a future cross-entity batch flow. Tests: canonicalization (UPPERCASE + braced spelling), redaction for non-admin on both products and contracts, retained reveal-dup-detail behavior for admin callers. Co-authored-by: Isaac <no-reply@databricks.com> * fix(import-dialog): en product-dialog keys + reset on reopen + keep paste on failure + component test (databrickslabs#930 review) Addresses mvkonchits-db's review on databrickslabs#930 (shared ImportEntityDialog): - en/data-products.json: add the dialog i18n keys the products view was passing (dialogTitle, dialogDescription, createMissingDomains, createMissingDomainsHint, adoptIds, adoptIdsHint, duplicatesAsNew, duplicatesAsNewHint, dropActive, dropInactive, supportedFormats, orPasteOdps, pasteOdpsPlaceholder, importJsonButton, partialSuccess) — previously they only resolved via the hard-coded second-arg fallback. (The other 6 locales still need parity; defer to the databrickslabs#471 i18n sweep so this PR stays focused.) - Reset dialog state when the dialog opens: both views now zero createMissingDomains, adoptIds, duplicatesAsNew, the paste box, and any lingering error whenever openUploadDialog transitions true, so a later import doesn't silently inherit earlier settings. - Default adoptIds to false on the UI, matching the backend-level privilege gate (databrickslabs#853 review). - Keep pasted JSON on partial-success/failure: uploadProductFiles now returns a boolean (allCreated) and importPastedOdps only clears the textarea on full success; the contract-side importPastedOdcs no longer clears on partial. The user keeps the input they need to fix the failing entries and retry. - Component test for the shared dialog: label rendering, optional paste section, paste-submit wiring, uploading indicator. File-dropzone interactions are left to Playwright per the team-dialog precedent (jsdom + react-dropzone is unreliable). Co-authored-by: Isaac <no-reply@databricks.com> * fix(import): restore lost file_inputs assignment in product upload route (databrickslabs#862 re-review) The previous commit (4610d15, data-domains gate) silently dropped the `file_inputs = [(name, raw) for name, raw, _ in capped]` line that databrickslabs#860 had introduced to feed `create_products_from_files`. Every product upload then raised `NameError` inside the generic `except Exception`, surfacing as a generic 500. A PR whose base isn't `development` only runs the title check, so backend tests never flagged it. Changes: - Restore the `file_inputs` build-up right after `read_uploads_capped()`. - Add two route-level tests: a golden-path product upload (guards the NameError regression) and a 403 for `create_missing_domains=True` when the user lacks `data-domains` write. - Expand the `user_has_feature_level` docstring to call out that it resolves group-based permissions only (no team/applied-role override) and fails closed on resolution error. Follow-up tracker filed as databrickslabs#944 (stale ontosDomainId on re-import + clearing a stale id on export). Session: claude -r 171078d5-61c9-42b9-aeb6-5d378c386f35 Co-authored-by: Isaac <no-reply@databricks.com> --------- Co-authored-by: Isaac <no-reply@databricks.com>
Closes #851.
Best-effort domain reconciliation on import, a lossless round-trip on export, and a per-upload "Create missing domains" toggle (default OFF). Unified across contracts (ODCS) and products (ODPS) via the shared
DomainExportAdapter.Reconciliation (import)
ontosDomainId(authoritative) → legacydomainIds/primaryDomainId/domainId→ standarddomainname (+ legacyadditionalDomains, still read for backward compat)._resolve_domainis removed (this also fixes the contracts/products asymmetry). The raw incoming domain string(s) are always preserved as theontosOriginalDomainprovenance custom property.create_missing_domainsform field; the paste/odcs/importtakes a query param.Round-trip (export)
apply_odcsemitsontosDomainId(all assigned ids, primary first — the authoritative rebind key) as a custom property, and keeps the standarddomain= primary name.ontosOriginalDomainrides along as a stored property.domainIds/primaryDomainIdandcustomProperties.additionalDomains(superseded byontosDomainId); both are still read on import so pre-feat(import): adopt ODCS/ODPS domain element on YAML import and round-trip #851 exports keep working.merge_custom_propertiesnow preservesontosDomainIdacross the export builders' customProperties rebuild.Latent bug fixed
build_odcs_from_dbhad four redundant innerimport jsonstatements shadowing the module-level import, leavingjsonunbound and crashing custom-property deserialization. Newly exercised becauseontos*values are list-valued (stored as JSON strings, deserialized on export). Removed the inner imports.Frontend
Tests & verification
test_domain_export_adapterto theontosDomainId/ontosOriginalDomaincontract; added create_missing, stale-id-fallthrough, and provenance coverage.test_import_domain_reconcile: match, no-match-unassigned + provenance, create_missing toggle, and export→re-import UUID rebind — for both contracts and products. (35 domain/import unit tests green; 5 existing product domain-resolution tests still green.)ontosDomainId/ontosOriginalDomain; re-importing an exported file rebinds to the same domain UUID. Playwright: both toggles render/flip; a product upload with the toggle ON auto-created the missing domain.This pull request and its description were written by Isaac.