Sentinel Policy M0: Commercial and Legal Feasibility Gate (v1.0.688)
Status: engineering complete, legal PASS not declared
Per the M0 spec's own explicit instruction (§16/§17/§49.16-17): M0 cannot be marked PASS solely on the basis of engineering completion. Everything in this report that is engineering/documentation work is done. The external counsel review (§34) has not happened — that's a real legal engagement outside what an engineering session can perform. Until that review completes and a human explicitly approves at least one source for commercial distribution, Sentinel Policy remains 100% fail-closed: zero sources are COMMERCIAL_APPROVED, zero customer-facing endpoints exist, and the projection layer would reject every event even if a customer endpoint existed today.
What this milestone adds
M0 is a hard launch blocker (LIC-001): no commercial/customer-facing data endpoint may ship until it resolves. M9 (the customer REST API) doesn't exist yet, so there was zero commercial exposure before this work and zero after — but the rights-aware architecture M9 will be required to build on top of is now in place, tested, and documented, rather than something to retrofit later.
Source registry extended (spec §4/§5)
Source (policy/src/sentinel_policy/db/models.py) gained authority_rank, terms_url, license_url, robots_url, reviewed_at, reviewed_by, review_notes. terms_review_state (already existed, LIC-004) widened from a 4-value set to the spec's 6-value commercial-approval state machine: DISCOVERED, UNDER_REVIEW, INTERNAL_ONLY, COMMERCIAL_APPROVED, COMMERCIAL_RESTRICTED, DISABLED. Migration 0008_m0_rights.py data-migrates existing rows and classifies the 2 real sources as AUTH_A (CMS is the canonical issuing authority for its own NCDs). See sentinel-policy-m0-source-rights-matrix.md.
Code-system rights registry (spec §6-14)
New sentinel_policy/rights/registry.py: a RightsClass enum (GREEN/YELLOW/RED/UNKNOWN) and CODE_SYSTEM_RIGHTS, the spec's own conservative initial classifications for every code system this product currently extracts or plans to. classify_code_system() normalizes and fails closed to UNKNOWN for anything not explicitly classified — this alone satisfies RIGHTS-001 for any future/ unanticipated code system, with no code change required. See sentinel-policy-m0-code-system-rights-matrix.md for the full table and citations.
Commercial projection layer (spec §19-25)
New sentinel_policy/rights/projection.py: project_change_event(event, source) is the single function any future customer-facing surface must call — it returns None (whole-event suppression) unless the source is COMMERCIAL_APPROVED, strips any non-GREEN code's identifier/descriptor (spec §21's code: null, restricted: true shape) rather than ever including it raw, and rejects code-centric events (procedure_code_added, modifier_added, etc.) whole when every one of their codes ends up restricted, since a code-centric event with no codes left is misleading (spec §23). Every non-suppressed projection carries schema_version, taxonomy_version, and a rights block with rights_policy_version/projection_version/ generated_at (spec §20/§25).
No live consumer calls this yet — M9 doesn't exist. See docs/adr/sentinel-policy-adr-001-rights-aware-commercial-projection.md for the full design rationale.
Real bug found and fixed: dental codes mislabeled
diffengine.py's deterministic code extractor's original HCPCS pattern ([A-V]\d{4}) matched D-prefixed dental codes (D0000- D9999) since D falls in [A-V]. Spec §11 explicitly requires HCPCS Level II to be split from CDT/dental content, since they carry different rights owners (CMS vs. ADA). Fixed: _extract_codes() now reclassifies any D-prefixed match to HCPCS_LEVEL_II_CDT (RED) rather than HCPCS_LEVEL_II (YELLOW). Also renamed DRG → MS_DRG to match the spec's naming and the registry's keys.
Schema/taxonomy freeze (spec §20/§30/§31, CI-008)
/v1/build now reports change_event_schema_version: "1.0" and change_taxonomy_version: "1.0", distinct from that endpoint's own pre-existing schema_version field (the build-info response's own schema, unrelated). test_rights.py::test_frozen_v1_taxonomy_values_are_all_still_present hardcodes a copy of today's CHANGE_TAXONOMY and asserts it remains a subset of the live constant — a real regression guard that runs in every future CI/test invocation, not just a documentation claim.
Phase G audit of existing data (spec §42)
Queried production directly (2026-08-20, before writing any code): all 46 ExtractedFact/ChangeEvent rows have affected_codes = []. No structured code of any kind — restricted or otherwise — has ever been extracted, stored, or distributed. No remediation was needed. This is the audit's actual finding against live data, not an assumed clean bill of health.
Endpoint isolation confirmed (spec §49.13, RIGHTS-005)
Every router in api/policies.py, api/sources.py, api/console.py already carried dependencies=[Depends(require_admin_token)] before this work — confirmed by direct inspection and now permanently guarded by test_rights.py::test_no_commercial_endpoint_bypasses_admin_auth, which iterates every live route in the FastAPI app and fails if any non-/healthz//readyz//v1/build route lacks the dependency.
Files changed
policy/src/sentinel_policy/db/models.py—Sourcefields, widenedpolicy/alembic/versions/0008_m0_rights.py— new migration.policy/src/sentinel_policy/api/schemas.py—SourceCreateRequest/policy/src/sentinel_policy/rights/__init__.py,policy/src/sentinel_policy/diffengine.py— dental-code split,policy/src/sentinel_policy/main.py—/v1/buildschema/taxonomypolicy/tests/test_rights.py— new, 19 tests (RIGHTS-001/002/004/policy/tests/test_migrations.py— migration 0008 data-migrationpolicy/tests/test_diffengine.py— updated for the renamed/splitpolicy/tests/test_api.py— milestone/schema-version assertions.docs/sentinel-policy-m0-source-rights-matrix.md,
TERMS_REVIEW_STATES, new SOURCE_AUTHORITY_RANKS.
SourceResponse extended with the new fields.
rights/registry.py, rights/projection.py — new package.
DRG → MS_DRG rename.
version fields, milestone string.
005/006/009/010, taxonomy freeze).
test.
code systems, new dental-split regression test.
docs/sentinel-policy-m0-code-system-rights-matrix.md, docs/adr/sentinel-policy-adr-001-rights-aware-commercial-projection.md — new, living compliance documents.
Test results
PYTHONPATH=policy/src:policy/tests python -m pytest policy/tests -q — 150 passing (up from 129 pre-M0).
Initial commercial allowlist
Every source stays fail-closed. Confirmed directly with the product owner (2026-08-20): no source is flipped to COMMERCIAL_APPROVED as part of this work, including CMS's own NCD narrative facts. The mechanism exists, is tested against real production data, and defaults to suppressing everything — approving any source for commercial distribution is an explicit future decision, not a byproduct of this milestone.
Disabled sources/content
All 3 registered sources (cms-medicare-ncd, cms-medicare-ncd-eval, s3-verify-test) remain DISCOVERED/INTERNAL_ONLY. All 9 classified code systems remain non-GREEN (CPT/CDT/HCPCS_LEVEL_II_CDT/ REVENUE_CODE = RED; HCPCS_LEVEL_II/ICD_10_CM/ICD_10_PCS/ MS_DRG = YELLOW; MODIFIER = UNKNOWN).
Unresolved legal questions (spec §34, for counsel)
- Can Sentinel Signal commercially redistribute the specific
- Can Sentinel Signal commercially redistribute the specific
- Under what conditions can Sentinel Signal generate and sell
- What AMA commercial license model applies if Sentinel Policy
- Are the proposed treatment and separation of CDT and UB-04/NUBC
CMS-published non-CDT HCPCS Level II identifiers/descriptors intended for the product?
ICD-10-CM and ICD-10-PCS identifiers/descriptors/files intended for the product?
structured policy-change facts derived from MAC and state Medicaid publications without redistributing substantial source text?
processes and/or exposes CPT-related information to SaaS/API customers?
content sufficient until commercial licenses are obtained?
Remaining counsel/product actions
- Engage counsel per spec §34, providing this report, the two rights
- Decide whether/when to flip
cms-medicare-ncd's - Begin an informational AMA licensing inquiry if/when customer demand
- A real future milestone (not M0): track modifier-originating-system
matrices, the ADR, and sample projection output (available by calling project_change_event directly against any real ChangeEvent — currently always returns None since no source is approved).
terms_review_state to COMMERCIAL_APPROVED (a product/legal decision, not an engineering one).
for CPT-inclusive output materializes (spec §35) — not blocking v1.
provenance so MODIFIER can move off UNKNOWN (see the code-system matrix's documented gap).