Skip to content

feat(crm): preserve CRM source provenance in field scans (PR-A of crm-schema-scan) - #214

Merged
cryptoxdog merged 5 commits into
mainfrom
agent/claude/crm-schema-scan-provenance
Sep 21, 2026
Merged

cryptoxdog merged 5 commits into
mainfrom
agent/claude/crm-schema-scan-provenance

Conversation

@cryptoxdog

@cryptoxdog cryptoxdog commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Transport-neutral scanner change carried over from #213 (closed): CRMField / FieldMapping gain optional source_system / source_resource provenance so identically named fields from different CRM resources (res.partner.phone vs crm.lead.phone) stay distinguishable through scan_crm_fields(), coverage counts unique domain properties, and the manual POST /api/v1/scan path can carry that provenance. This is PR-A of the Gate-routed crm-schema-scan development pack; no transport, adapter, or config changes are included.

Type of change

  • Feature
  • Bug fix (coverage_ratio could exceed 1.0 with duplicate-name matches)
  • Refactor
  • Tooling / CI
  • Documentation
  • AI-generated (review required)

What changed

app/services/crm_field_scanner.py

  • CRMField.source_system / source_resource and FieldMapping.source_system / source_resource (optional, default None); propagated to matched and unmapped mappings, never to domain-owned missing entries.
  • scan_result_to_dict exposes the two keys additively on matched and unmapped.
  • scan_hash hashes (source_system, source_resource, name, field_type); legacy CRMField(name=...) stays deterministic.
  • coverage_ratio = unique matched domain keys / total domain properties.

app/api/v1/discover.py + docs/contracts/api/openapi.yaml (Codex P1)

  • CRMFieldInput declares optional source_system / source_resource and forwards them into CRMField; the OpenAPI ScanRequest.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 for app/services/ and app/api/v1/).
  • tests/test_discover_scan_endpoints.py — endpoint forwards provenance for duplicate-name fields from two resources; legacy fields keep None.

Checklist

  • ruff check passes
  • ruff format applied
  • mypy passes on touched modules
  • Tests added / updated for changed behaviour
  • pytest passes locally — 33 passed across the touched suites; tests/contracts 764 passed with one pre-existing phase-5 readiness failure that reproduces identically on main
  • .env.example updated if new env vars added — none added
  • CHANGELOG.md updated — not touched

AI-generated code notice

  • Reviewed for prompt injection vectors in enrichment inputs — no new input path beyond two optional string fields on an authenticated endpoint
  • Signal schema validation verified — pydantic optional fields, additive dataclass fields
  • No hardcoded secrets or credentials
  • Path traversal safety checked if file I/O added — no file I/O

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:17 ruff F401 · tests/services/test_gate_registration.py:215 collection ImportError · tools/verify_contracts.py SHA mismatch for app/services/gate_client.py · semgrep float-requires-try-except at gate_client.py:67 · pip-audit nltk PYSEC-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

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
Copilot AI lite review requested due to automatic review settings September 19, 2026 22:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-19T22:18:52.883258Z 52bc41f PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

PR Size Report

Metric Value Limit
Lines changed 249 1000
Files changed 6 50
Additions +246 -
Deletions -3 -

✅ PR size is within recommended limits

@github-actions

Copy link
Copy Markdown

L9 Audit Review

Status

  • Result: pass_with_advisories
  • Mode: explicit_advisory_blocking_tier_policy
  • Total findings visible: 1200
  • Blocking findings: 0
  • Advisory findings: 1200

Policy

  • Flatcase/style findings are advisory.
  • SQL/auth/security/chassis/transport/ingress/contract/runtime findings are blocking when new or touched.
  • Unknown HIGH/CRITICAL findings fail closed as blocking.

Blocking Findings

  • none

Advisory Findings

  • app/agents/deal_risk.py:36 Likely flatcase field 'recommendations' — advisory
  • app/score/score_explainer.py:54 Likely flatcase field 'recommendation' — advisory
  • app/score/score_explainer.py:103 Likely flatcase field 'recommendations' — advisory
  • app/score/score_models.py:96 Likely flatcase field 'recommendation' — advisory
  • app/services/crm/salesforce_client.py:189 f-string SQL query -- injection risk — advisory
  • app/bootstrap/l9_contract_runtime.py:32 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/core/auth.py:34 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/core/auth.py:35 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/score/score_api.py:40 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/middleware/rate_limiter.py:28 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/middleware/rate_limiter.py:29 FastAPI import in engine module -- chassis isolation violation — advisory
  • app/services/enrichment/sources/llm_base.py:25 Deep relative import (level=3) — advisory
  • app/services/enrichment/sources/perplexity_adapter.py:21 Deep relative import (level=3) — advisory
  • app/services/enrichment/sources/perplexity_adapter.py:22 Deep relative import (level=3) — advisory
  • app/services/enrichment/sources/openai_adapter.py:20 Deep relative import (level=3) — advisory
  • app/services/enrichment/sources/anthropic_adapter.py:20 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:21 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:22 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:23 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:24 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:25 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:26 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:27 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:28 Deep relative import (level=3) — advisory
  • app/api/v1/converge.py:34 Deep relative import (level=3) — advisory

... truncated 1175 additional findings ...


Generated by L9 Audit Engine (tools/audit_engine.py)

@cryptoxdog cryptoxdog changed the title feat(crm): preserve CRM source provenance in field scans feat(crm): preserve CRM source provenance in field scans (PR-A of crm-schema-scan) Sep 19, 2026
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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_resource to CRMField and FieldMapping, propagated for matched/unmapped mappings and exposed via scan_result_to_dict.
  • Updated coverage_ratio to count unique covered domain properties (preventing ratios > 1.0 when duplicate-name matches exist).
  • Updated scan_hash identity 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread app/services/crm_field_scanner.py

Copy link
Copy Markdown
Collaborator Author

CI triage on head 0a6b34f.

Fixed here

  • Contract-Bound Change Gate: app/services/ is a contract-bound prefix and the PR carried no tests/contracts change. 0a6b34f pins the provenance contract in tests/contracts/test_discover_scan_contract.py (optional CRMField provenance, serialized matched/unmapped keys, none on missing, unique-domain coverage). Real contract tests, not a placeholder edit.

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 main and none touch this diff. Fixes already exist in open PRs, so I am not porting them here (that would duplicate another author's PR into a provenance-only change):

Check Root cause Owning open PR
Lint (Ruff + Mypy), Lint and Type Check tests/test_pr21_packet_router.py:17 F401 #210, #212 both remove the import
Test Suite, Baseline Ratchet (Required Tests / Verdict) tests/services/test_gate_registration.py:215 imports a symbol removed in #203 #210 deletes the dead re-registration tests; #212 also touches the file
Architecture Compliance / Compliance Gate tools/verify_contracts.py SHA mismatch for app/services/gate_client.py #210 moves the manifest to contract_version pins; #212 re-stamps the manifest
Semgrep Policy Check gate_client.py:67 float-requires-try-except #210 switches to safe_float
Security Scanning pip-audit nltk PYSEC-2026-3740 (no fixed release) #210 adds --ignore-vuln with rationale

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
@cryptoxdog

Copy link
Copy Markdown
Collaborator Author

PR Remediation — Cycle 1 Summary

Commit: 0aec1b02541534a724e85bfc110f30e81487cfaa | Findings processed: 1 | CI gates: NotApplicable

Fixed (1)

Finding File Change
app/services/crm_field_scanner.py app/services/crm_field_scanner.py fixed

Deferred (0)

none

Acknowledged (0)

none

Disagreed (0)

none


Local verify: NotApplicable | Threads resolved: 1/1

@sonarqubecloud

Copy link
Copy Markdown

Copy link
Copy Markdown
Collaborator Author

Remediation contract step 1 — refresh onto current main

Audit 0f26cc86 (PRs 212–214), PR_REMEDIATION_CONTRACT.md step 1.

Base refresh. origin/main moved to c9db573d (#210 merged). Merged it into this branch as e2228ba (merge commit, no conflicts, no history rewrite). Merge-base with main is now c9db573d; the PR diff is unchanged (6 files, +246/−3).

Local gate set on the merged tree (all exit 0 unless noted):

Gate Result
ruff check / ruff format --check pass
mypy (advisory) pass
pytest + coverage 1685 passed, coverage 75.07% (floor 71%)
terminology guard, chassis isolation, KB YAML schema pass
tools/verify_contracts.py, node constitution + attestation pass
gitleaks, bandit, semgrep (governance gate), pip-audit pass / pip-audit skipped (manifests unchanged)
tools/audit_engine.py --strict exit 1 on 10 pre-existing repo-wide CRITICALs outside this PR's files; advisory in CI and L9 Audit Review already passed on this head. Not repaired here (scope expansion = false).

Repairs caused by #214: none required.

Remaining before merge-ready: the five checks that were red on main before #210 should now clear on this head; T3 review is a human step. This PR stops at merge-ready. Merge authorization for this contract is false.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown

PR Pipeline Gate Summary

Phase Status
validate ✅ success
lint ✅ success
semgrep ✅ success
test ✅ success
security ✅ success
compliance ✅ success
l9 ✅ success
docs ✅ success

✅ All checks passed

Ready for code review and merge.

Local equivalent: make pr · Job: 35629578621

@cryptoxdog
cryptoxdog merged commit 5ef2b5c into main Sep 21, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants