Repository navigation
feat(crm): preserve CRM source provenance in field scans (PR-A of crm-schema-scan) - #214
Conversation
Add optional source_system / source_resource to CRMField and FieldMapping so fields with identical technical names from different resources (Odoo res.partner.phone vs crm.lead.phone) stay distinguishable through the scanner. Provenance propagates to matched and unmapped mappings, is exposed additively in scan_result_to_dict, and participates in scan_hash. Legacy CRMField(name=...) callers are unchanged; missing entries carry no provenance. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZDbPoPruZTHGhRt7tkFCm
With source provenance, two resources can both match one domain property (res.partner.phone and crm.lead.phone). Both mappings survive, but coverage must count the domain property once; the previous len(matched) numerator could exceed 1.0 (6 mappings / 4 properties = 1.5). Use the unique matched domain keys already tracked for the missing computation. Regression test added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZDbPoPruZTHGhRt7tkFCm
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Size Report
✅ PR size is within recommended limits |
L9 Audit ReviewStatus
Policy
Blocking Findings
Advisory Findings
... truncated 1175 additional findings ... Generated by L9 Audit Engine ( |
The Contract-Bound Change Gate requires a docs/contracts or tests/contracts update alongside app/services changes. Pin the provenance contract the scanner now exposes: source_system/source_resource are optional on CRMField (legacy construction unchanged), serialized matched/unmapped entries carry both keys, missing entries never do, and duplicate-name matches across resources cover a domain property once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZDbPoPruZTHGhRt7tkFCm
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The changes are scoped, internally consistent, and backed by targeted tests covering the new provenance behavior, hash identity, and the coverage regression.
Review effort: Lite
Findings: None
What changed in this PR
Adds optional CRM source provenance to the CRM field scanner so identically named fields from different CRM resources remain distinguishable, while fixing coverage accounting to be based on unique covered domain properties.
Changes:
- Added
source_system/source_resourcetoCRMFieldandFieldMapping, propagated for matched/unmapped mappings and exposed viascan_result_to_dict. - Updated
coverage_ratioto count unique covered domain properties (preventing ratios > 1.0 when duplicate-name matches exist). - Updated
scan_hashidentity to include(source_system, source_resource, name, field_type)and added tests for provenance, hashing, and coverage regression.
| File | Description |
|---|---|
| app/services/crm_field_scanner.py | Adds provenance fields, includes them in serialization, updates scan identity hashing, and fixes coverage accounting to use unique domain keys. |
| tests/test_crm_field_scanner.py | Adds a new provenance-focused test suite validating legacy compatibility, serialization, hashing distinctness, and corrected coverage behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52bc41f428
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
CI triage on head Fixed here
Green on this head: L9 Audit Review (0 blocking), PR Size (147 lines before this push), SonarCloud (0 new issues). Red checks that are not this PR's — all five reproduce on
Once either of those merges, this PR goes green on a plain base merge. I have no permission to re-run jobs from this surface; the PR stays watched. Generated by Claude Code |
Codex P1 on #214: CRMFieldInput did not declare source_system / source_resource, so API callers could not supply provenance and the provenance-aware scan hash always saw None through the production path. Declare both as optional (default None, legacy payloads unchanged), forward them into CRMField, document them in the OpenAPI ScanRequest schema, and cover the endpoint path with duplicate-name fields from two resources. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZDbPoPruZTHGhRt7tkFCm
PR Remediation — Cycle 1 SummaryCommit: Fixed (1)
Deferred (0)none Acknowledged (0)none Disagreed (0)none Local verify: NotApplicable | Threads resolved: 1/1 |
…ma-scan-provenance
|
Remediation contract step 1 — refresh onto current mainAudit Base refresh. Local gate set on the merged tree (all exit 0 unless noted):
Repairs caused by #214: none required. Remaining before merge-ready: the five checks that were red on Generated by Claude Code |
PR Pipeline Gate Summary
✅ All checks passedReady for code review and merge. Local equivalent: |



Summary
Transport-neutral scanner change carried over from #213 (closed):
CRMField/FieldMappinggain optionalsource_system/source_resourceprovenance so identically named fields from different CRM resources (res.partner.phonevscrm.lead.phone) stay distinguishable throughscan_crm_fields(), coverage counts unique domain properties, and the manualPOST /api/v1/scanpath can carry that provenance. This is PR-A of the Gate-routedcrm-schema-scandevelopment pack; no transport, adapter, or config changes are included.Type of change
What changed
app/services/crm_field_scanner.pyCRMField.source_system/source_resourceandFieldMapping.source_system/source_resource(optional, defaultNone); propagated to matched and unmapped mappings, never to domain-owned missing entries.scan_result_to_dictexposes the two keys additively onmatchedandunmapped.scan_hashhashes(source_system, source_resource, name, field_type); legacyCRMField(name=...)stays deterministic.coverage_ratio= unique matched domain keys / total domain properties.app/api/v1/discover.py+docs/contracts/api/openapi.yaml(Codex P1)CRMFieldInputdeclares optionalsource_system/source_resourceand forwards them intoCRMField; the OpenAPIScanRequest.fields[]item schema documents both. Payloads without them behave exactly as before.Tests
tests/test_crm_field_scanner.py—TestSourceProvenance(legacy compatibility, matched/unmapped provenance, none on missing, hash distinguishes resources, unique-domain coverage regression).tests/contracts/test_discover_scan_contract.py— provenance contract pins (satisfies the Contract-Bound Change Gate forapp/services/andapp/api/v1/).tests/test_discover_scan_endpoints.py— endpoint forwards provenance for duplicate-name fields from two resources; legacy fields keepNone.Checklist
ruff checkpassesruff formatappliedmypypasses on touched modulespytestpasses locally — 33 passed across the touched suites;tests/contracts764 passed with one pre-existing phase-5 readiness failure that reproduces identically onmain.env.exampleupdated if new env vars added — none addedAI-generated code notice
Scope attestation
Gate SDK modified: NO · TransportPacket schema modified: NO · CEG modified: NO · ranking modified: NO · Odoo exposes ingress: NO · EIE holds Odoo credential: NO.
Known red checks not caused by this diff (pre-existing on
main)tests/test_pr21_packet_router.py:17ruff F401 ·tests/services/test_gate_registration.py:215collection ImportError ·tools/verify_contracts.pySHA mismatch forapp/services/gate_client.py· semgrepfloat-requires-try-exceptatgate_client.py:67· pip-auditnltkPYSEC-2026-3740. All five are fixed by open PRs #210 / #212; not ported here to avoid duplicating them. See the triage comment for the mapping.Related issues
Supersedes #213 (direct Odoo JSON-2 adapter, closed per Gate-only egress decision).
🤖 Generated with Claude Code
https://claude.ai/code/session_01BZDbPoPruZTHGhRt7tkFCm