From 888c5f92840b7475d52037c609c75dc9a363cee5 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 12:30:26 -0500 Subject: [PATCH 1/7] feat(precheck): add HIPAA PHI and PCI custom entity support (COMP-5.5) --- app/policies.py | 270 ++++++++++++++++++++++++++++---- tests/test_custom_pii_models.py | 57 +++++++ 2 files changed, 295 insertions(+), 32 deletions(-) create mode 100644 tests/test_custom_pii_models.py diff --git a/app/policies.py b/app/policies.py index 6709317..e015ab9 100644 --- a/app/policies.py +++ b/app/policies.py @@ -16,6 +16,34 @@ EMAIL = re.compile(r"\b([A-Za-z0-9._%+-])[^@\s]*(@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b") PHONE = re.compile(r"\+?\d[\d\s\-\(\)]{7,}\d") CARD = re.compile(r"\b(?:\d[ -]*?){13,19}\b") +PHI_MRN = re.compile( + r"\b(?:MRN|Medical\s*Record(?:\s*Number)?|Patient\s*ID)\s*[:#]?\s*([A-Z0-9\-]{6,20})\b", + re.IGNORECASE, +) +PHI_MEMBER_ID = re.compile( + r"\b(?:Member|Policy|Insurance)\s*(?:ID|Number)\s*[:#]?\s*([A-Z0-9\-]{6,24})\b", + re.IGNORECASE, +) +PHI_NPI = re.compile( + r"\b(?:NPI|National\s*Provider\s*Identifier)\s*[:#]?\s*(\d{10})\b", + re.IGNORECASE, +) +PHI_DEA = re.compile( + r"\b(?:DEA|DEA\s*Number)\s*[:#]?\s*([A-Z]{2}\d{7})\b", + re.IGNORECASE, +) +PHI_DOB = re.compile( + r"\b(?:DOB|Date\s*of\s*Birth)\s*[:#]?\s*((?:0?[1-9]|1[0-2])[/-](?:0?[1-9]|[12][0-9]|3[01])[/-](?:19|20)?\d{2})\b", + re.IGNORECASE, +) +PCI_CVV = re.compile( + r"\b(?:cvv|cvc|cvn|security\s*code)\s*[:#]?\s*(\d{3,4})\b", + re.IGNORECASE, +) +PCI_EXPIRY = re.compile( + r"\b(?:exp(?:iry|iration)?|valid\s*thru)\s*[:#]?\s*((?:0[1-9]|1[0-2])[/-](?:\d{2}|\d{4}))\b", + re.IGNORECASE, +) def luhn_ok(s: str) -> bool: """Luhn algorithm for credit card validation""" @@ -42,7 +70,25 @@ def repl(m): return "**** **** **** ****" if 13 <= len(raw) <= 19 and luhn_ok(raw) else m.group(0) return CARD.sub(repl, s) -SENSITIVE_KEYS = {"email", "phone", "ssn", "card", "cvv", "secret", "token", "apikey", "api_key"} +def _replace_regex(s: str, pattern: re.Pattern, placeholder: str) -> str: + return pattern.sub(lambda _: placeholder, s) + +SENSITIVE_KEYS = { + "email", + "phone", + "ssn", + "card", + "cvv", + "secret", + "token", + "apikey", + "api_key", + "mrn", + "npi", + "dea", + "member_id", + "dob", +} # Global Presidio instances ANALYZER = None @@ -88,6 +134,101 @@ def build_presidio(): ) registry.add_recognizer(ssn_recognizer) + # HIPAA PHI recognizers + registry.add_recognizer( + PatternRecognizer( + supported_entity="US_MEDICAL_RECORD_NUMBER", + patterns=[ + Pattern( + name="US_MEDICAL_RECORD_NUMBER", + regex=r"\b(?:MRN|Medical\s*Record(?:\s*Number)?|Patient\s*ID)\s*[:#]?\s*[A-Z0-9\-]{6,20}\b", + score=0.82, + ) + ], + context=["mrn", "medical record", "patient id", "chart"], + ) + ) + registry.add_recognizer( + PatternRecognizer( + supported_entity="US_HEALTH_MEMBER_ID", + patterns=[ + Pattern( + name="US_HEALTH_MEMBER_ID", + regex=r"\b(?:Member|Policy|Insurance)\s*(?:ID|Number)\s*[:#]?\s*[A-Z0-9\-]{6,24}\b", + score=0.8, + ) + ], + context=["member id", "policy", "insurance", "payer"], + ) + ) + registry.add_recognizer( + PatternRecognizer( + supported_entity="US_NPI", + patterns=[ + Pattern( + name="US_NPI", + regex=r"\b(?:NPI|National\s*Provider\s*Identifier)\s*[:#]?\s*\d{10}\b", + score=0.85, + ) + ], + context=["npi", "provider", "national provider identifier"], + ) + ) + registry.add_recognizer( + PatternRecognizer( + supported_entity="US_DEA", + patterns=[ + Pattern( + name="US_DEA", + regex=r"\b(?:DEA|DEA\s*Number)\s*[:#]?\s*[A-Z]{2}\d{7}\b", + score=0.85, + ) + ], + context=["dea", "prescriber", "controlled substance"], + ) + ) + registry.add_recognizer( + PatternRecognizer( + supported_entity="US_DATE_OF_BIRTH", + patterns=[ + Pattern( + name="US_DATE_OF_BIRTH", + regex=r"\b(?:DOB|Date\s*of\s*Birth)\s*[:#]?\s*(?:0?[1-9]|1[0-2])[/-](?:0?[1-9]|[12][0-9]|3[01])[/-](?:19|20)?\d{2}\b", + score=0.78, + ) + ], + context=["dob", "date of birth", "patient"], + ) + ) + + # PCI-DSS related recognizers + registry.add_recognizer( + PatternRecognizer( + supported_entity="PCI_CVV", + patterns=[ + Pattern( + name="PCI_CVV", + regex=r"\b(?:cvv|cvc|cvn|security\s*code)\s*[:#]?\s*\d{3,4}\b", + score=0.88, + ) + ], + context=["cvv", "cvc", "security code", "payment"], + ) + ) + registry.add_recognizer( + PatternRecognizer( + supported_entity="PCI_EXPIRY", + patterns=[ + Pattern( + name="PCI_EXPIRY", + regex=r"\b(?:exp(?:iry|iration)?|valid\s*thru)\s*[:#]?\s*(?:0[1-9]|1[0-2])[/-](?:\d{2}|\d{4})\b", + score=0.84, + ) + ], + context=["expiry", "expiration", "valid thru", "payment"], + ) + ) + analyzer = AnalyzerEngine(registry=registry, nlp_engine=nlp_engine, supported_languages=["en"]) anonymizer = AnonymizerEngine() return analyzer, anonymizer @@ -114,6 +255,13 @@ def init_presidio(): "IP_ADDRESS": OperatorConfig("mask", {"masking_char": "*", "chars_to_mask": 4, "from_end": True}), "IBAN_CODE": OperatorConfig("mask", {"masking_char": "*", "chars_to_mask": 4, "from_end": True}), "US_SSN": OperatorConfig("mask", {"masking_char": "*", "chars_to_mask": 4, "from_end": True}), + "US_MEDICAL_RECORD_NUMBER": OperatorConfig("replace", {"new_value": ""}), + "US_HEALTH_MEMBER_ID": OperatorConfig("replace", {"new_value": ""}), + "US_NPI": OperatorConfig("replace", {"new_value": ""}), + "US_DEA": OperatorConfig("replace", {"new_value": ""}), + "US_DATE_OF_BIRTH": OperatorConfig("replace", {"new_value": ""}), + "PCI_CVV": OperatorConfig("replace", {"new_value": ""}), + "PCI_EXPIRY": OperatorConfig("replace", {"new_value": ""}), "API_KEY": OperatorConfig("replace", {"new_value": "[REDACTED_API_KEY]"}), "JWT_TOKEN": OperatorConfig("replace", {"new_value": "[REDACTED_JWT]"}), } @@ -125,6 +273,13 @@ def entity_type_to_placeholder(entity_type: str) -> str: "PHONE_NUMBER": "", "CREDIT_CARD": "", "US_SSN": "", + "US_MEDICAL_RECORD_NUMBER": "", + "US_HEALTH_MEMBER_ID": "", + "US_NPI": "", + "US_DEA": "", + "US_DATE_OF_BIRTH": "", + "PCI_CVV": "", + "PCI_EXPIRY": "", "IP_ADDRESS": "", "IBAN_CODE": "", "API_KEY": "", @@ -182,9 +337,86 @@ def anonymize_text_regex(text: str) -> Tuple[str, List[str]]: if CARD.search(text): redacted = _mask_card(redacted) reasons.append("pii.redacted:card") + + if PHI_MRN.search(text): + redacted = _replace_regex(redacted, PHI_MRN, "") + reasons.append("pii.redacted:us_medical_record_number") + + if PHI_MEMBER_ID.search(text): + redacted = _replace_regex(redacted, PHI_MEMBER_ID, "") + reasons.append("pii.redacted:us_health_member_id") + + if PHI_NPI.search(text): + redacted = _replace_regex(redacted, PHI_NPI, "") + reasons.append("pii.redacted:us_npi") + + if PHI_DEA.search(text): + redacted = _replace_regex(redacted, PHI_DEA, "") + reasons.append("pii.redacted:us_dea") + + if PHI_DOB.search(text): + redacted = _replace_regex(redacted, PHI_DOB, "") + reasons.append("pii.redacted:us_date_of_birth") + + if PCI_CVV.search(text): + redacted = _replace_regex(redacted, PCI_CVV, "") + reasons.append("pii.redacted:pci_cvv") + + if PCI_EXPIRY.search(text): + redacted = _replace_regex(redacted, PCI_EXPIRY, "") + reasons.append("pii.redacted:pci_expiry") return redacted, reasons +def _has_overlap(start: int, end: int, findings: List[Dict[str, Any]]) -> bool: + for finding in findings: + if not (end <= finding["start"] or start >= finding["end"]): + return True + return False + +def _append_regex_findings(findings: List[Dict[str, Any]], pattern: re.Pattern, pii_type: str, text: str, score: float) -> None: + for match in pattern.finditer(text): + if _has_overlap(match.start(), match.end(), findings): + continue + findings.append({ + "type": pii_type, + "start": match.start(), + "end": match.end(), + "score": score, + "text": match.group(), + }) + +def detect_regex_pii_findings(raw_text: str) -> List[Dict[str, Any]]: + """Detect PII findings using regex patterns for fallback mode.""" + findings: List[Dict[str, Any]] = [] + + _append_regex_findings(findings, EMAIL, "PII:email_address", raw_text, 0.8) + _append_regex_findings(findings, PHONE, "PII:phone_number", raw_text, 0.8) + + for match in CARD.finditer(raw_text): + raw_card = re.sub(r"[^\d]", "", match.group()) + if not (13 <= len(raw_card) <= 19 and luhn_ok(raw_card)): + continue + if _has_overlap(match.start(), match.end(), findings): + continue + findings.append({ + "type": "PII:credit_card", + "start": match.start(), + "end": match.end(), + "score": 0.9, + "text": match.group(), + }) + + _append_regex_findings(findings, PHI_MRN, "PII:us_medical_record_number", raw_text, 0.82) + _append_regex_findings(findings, PHI_MEMBER_ID, "PII:us_health_member_id", raw_text, 0.8) + _append_regex_findings(findings, PHI_NPI, "PII:us_npi", raw_text, 0.85) + _append_regex_findings(findings, PHI_DEA, "PII:us_dea", raw_text, 0.85) + _append_regex_findings(findings, PHI_DOB, "PII:us_date_of_birth", raw_text, 0.78) + _append_regex_findings(findings, PCI_CVV, "PII:pci_cvv", raw_text, 0.88) + _append_regex_findings(findings, PCI_EXPIRY, "PII:pci_expiry", raw_text, 0.84) + + return findings + def is_password_field(field_name: str) -> bool: """Check if field name indicates a password field""" password_fields = {"password", "pass", "pwd", "secret", "key", "token", "auth"} @@ -717,6 +949,8 @@ def _apply_tool_specific_policy_dynamic(tool: str, raw_text: str, now: int, tool "score": r.score, "text": raw_text[r.start:r.end] }) + else: + findings.extend(detect_regex_pii_findings(raw_text)) import re @@ -960,36 +1194,8 @@ def _apply_strict_fallback(raw_text: str, now: int, user_id: Optional[str] = Non "text": raw_text[r.start:r.end] }) else: - # Fallback regex detection for email, phone, card - if EMAIL.search(raw_text): - for match in EMAIL.finditer(raw_text): - all_findings.append({ - "type": "PII:email_address", - "start": match.start(), - "end": match.end(), - "score": 0.8, - "text": match.group() - }) - if PHONE.search(raw_text): - for match in PHONE.finditer(raw_text): - all_findings.append({ - "type": "PII:phone_number", - "start": match.start(), - "end": match.end(), - "score": 0.8, - "text": match.group() - }) - if CARD.search(raw_text): - for match in CARD.finditer(raw_text): - raw_card = re.sub(r"[^\d]", "", match.group()) - if 13 <= len(raw_card) <= 19 and luhn_ok(raw_card): - all_findings.append({ - "type": "PII:credit_card", - "start": match.start(), - "end": match.end(), - "score": 0.9, - "text": match.group() - }) + # Fallback regex detection for standard, HIPAA PHI, and PCI-DSS entities + all_findings.extend(detect_regex_pii_findings(raw_text)) # Additionally detect passwords (not in Presidio) and payment amounts password_findings = [] @@ -1252,4 +1458,4 @@ def _add_budget_info_to_result(result: Dict, user_id: str, tool: str, raw_text: except Exception as e: # If budget checking fails, return result without budget info print(f"Failed to add budget info: {e}") - return result \ No newline at end of file + return result diff --git a/tests/test_custom_pii_models.py b/tests/test_custom_pii_models.py new file mode 100644 index 0000000..fd45a14 --- /dev/null +++ b/tests/test_custom_pii_models.py @@ -0,0 +1,57 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2024 GovernsAI. All rights reserved. +"""Coverage for custom HIPAA PHI + PCI-DSS entity support.""" + + +def test_hipaa_mrn_redaction_regex(): + from app.policies import anonymize_text_regex + + redacted, reasons = anonymize_text_regex("Patient intake MRN: A1234567 for encounter") + + assert "A1234567" not in redacted + assert "" in redacted + assert "pii.redacted:us_medical_record_number" in reasons + + +def test_hipaa_provider_identifiers_redaction_regex(): + from app.policies import anonymize_text_regex + + text = "NPI: 1234567890 DEA Number: AB1234567 DOB: 01/09/1982" + redacted, reasons = anonymize_text_regex(text) + + assert "1234567890" not in redacted + assert "AB1234567" not in redacted + assert "01/09/1982" not in redacted + assert "" in redacted + assert "" in redacted + assert "" in redacted + assert "pii.redacted:us_npi" in reasons + assert "pii.redacted:us_dea" in reasons + assert "pii.redacted:us_date_of_birth" in reasons + + +def test_pci_entities_redaction_regex(): + from app.policies import anonymize_text_regex + + text = "Card 4532 0151 1283 0366 cvv: 123 exp: 12/29" + redacted, reasons = anonymize_text_regex(text) + + assert "4532 0151 1283 0366" not in redacted + assert "cvv: 123" not in redacted.lower() + assert "exp: 12/29" not in redacted.lower() + assert "**** **** **** ****" in redacted + assert "" in redacted + assert "" in redacted + assert "pii.redacted:card" in reasons + assert "pii.redacted:pci_cvv" in reasons + assert "pii.redacted:pci_expiry" in reasons + + +def test_custom_entity_placeholders_present(): + from app.policies import entity_type_to_placeholder + + assert entity_type_to_placeholder("US_MEDICAL_RECORD_NUMBER") == "" + assert entity_type_to_placeholder("US_HEALTH_MEMBER_ID") == "" + assert entity_type_to_placeholder("US_NPI") == "" + assert entity_type_to_placeholder("US_DEA") == "" + assert entity_type_to_placeholder("PCI_CVV") == "" From 390fa898997c740535838ab4a8614c31d750fa7c Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:42:01 -0500 Subject: [PATCH 2/7] fix(precheck): use BUILD_DATE env var in metrics service info (QW-1) --- app/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/api.py b/app/api.py index 0f35052..7d85954 100644 --- a/app/api.py +++ b/app/api.py @@ -19,6 +19,7 @@ import hashlib import json import secrets +import os from datetime import datetime from typing import List, Tuple, Optional @@ -207,8 +208,8 @@ async def metrics(): # Set service info if not already set set_service_info( version="0.0.1", - build_date="2024-01-XX", - git_commit="unknown" + build_date=os.getenv("BUILD_DATE", "unknown"), + git_commit=os.getenv("GIT_COMMIT", "unknown") ) metrics_data = get_metrics() From e9d6491147f2e0dba7928b4d68afaf8dc7512b75 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:44:10 -0500 Subject: [PATCH 3/7] docs: add CONTRIBUTING guide (QW-4) --- CONTRIBUTING.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f455b18 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing to GovernsAI Precheck + +## Setup + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -e .[dev] +``` + +## Development + +```bash +uvicorn app.main:app --reload --host 0.0.0.0 --port 8080 +``` + +## Validation + +```bash +pytest +``` + +## Pull Request Checklist + +- Add or update tests for policy behavior changes. +- Keep API responses backward compatible unless versioned. +- Document new environment variables and defaults. From aec602551c16637e899a9696e8554c66e13683e1 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:45:39 -0500 Subject: [PATCH 4/7] docs: add README version/license badges (QW-7) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a66f02c..a26e4f5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # GovernsAI Precheck +[![npm](https://img.shields.io/npm/v/%40governs-ai%2Fsdk?label=npm%20%40governs-ai%2Fsdk)](https://www.npmjs.com/package/@governs-ai/sdk) +[![PyPI](https://img.shields.io/pypi/v/governs-ai-sdk?label=PyPI%20governs-ai-sdk)](https://pypi.org/project/governs-ai-sdk/) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) + **Fully Open Source (MIT)** - PII detection and policy evaluation service for AI applications. This service provides real-time policy evaluation and PII detection/redaction for AI tool usage. You can use it, modify it, and even offer it as a hosted service - no restrictions. @@ -263,4 +267,4 @@ Precheck is the core of the GovernsAI ecosystem: - **Browser Extension** - MIT - **Platform Console** - ELv2 (source-available for self-hosting) -Learn more: [GovernsAI Licensing](https://docs.governsai.com/licensing) \ No newline at end of file +Learn more: [GovernsAI Licensing](https://docs.governsai.com/licensing) From bc6a5c487ea3d06665fb19efe2ee64f644e135a6 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:46:18 -0500 Subject: [PATCH 5/7] ci: add PR auto-labeler workflow (QW-8) --- .github/labeler.yml | 77 +++++++++++++++++++++++++++++++++++ .github/workflows/labeler.yml | 19 +++++++++ 2 files changed, 96 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..1b17e93 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,77 @@ +area/docs: + - changed-files: + - any-glob-to-any-file: + - "**/*.md" + - "**/*.mdx" + - "docs/**" + +area/frontend: + - changed-files: + - any-glob-to-any-file: + - "app/**" + - "components/**" + - "pages/**" + - "styles/**" + - "public/**" + +area/backend: + - changed-files: + - any-glob-to-any-file: + - "app/**" + - "api/**" + - "server/**" + - "src/**" + - "main.py" + +area/infra: + - changed-files: + - any-glob-to-any-file: + - "docker-compose*.yml" + - "**/Dockerfile*" + - "infra/**" + - "k8s/**" + - "helm/**" + - "**/*.tf" + +area/sdk: + - changed-files: + - any-glob-to-any-file: + - "governs_ai/**" + - "src/**" + - "pyproject.toml" + - "package.json" + - "tsconfig.json" + +area/security: + - changed-files: + - any-glob-to-any-file: + - "**/auth/**" + - "**/security/**" + - "manifest.json" + - "**/*policy*" + - "**/*permission*" + +kind/ci: + - changed-files: + - any-glob-to-any-file: + - ".github/workflows/**" + - ".github/labeler.yml" + +kind/tests: + - changed-files: + - any-glob-to-any-file: + - "tests/**" + - "**/*test*.py" + - "**/*.spec.ts" + - "**/*.test.ts" + - "**/*.test.tsx" + +kind/deps: + - changed-files: + - any-glob-to-any-file: + - "package.json" + - "pnpm-lock.yaml" + - "requirements.txt" + - "requirements-dev.txt" + - "pyproject.toml" + - "poetry.lock" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..36a6ceb --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,19 @@ +name: PR Labeler + +on: + pull_request_target: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Label pull requests + uses: actions/labeler@v5 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + sync-labels: true From 4b17f60a5cd98abdf3dc0a2effa631910b6b6016 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:46:29 -0500 Subject: [PATCH 6/7] chore: add root editorconfig (QW-9) --- .editorconfig | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2d8e5b5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab From c45a18b98a3728142e6855243e95ecefffe12d05 Mon Sep 17 00:00:00 2001 From: Shaishav Pidadi Date: Sat, 28 Feb 2026 13:46:50 -0500 Subject: [PATCH 7/7] chore(precheck): bump service version to 0.1.0 (QW-10) --- PROJECT_SPECS.md | 8 ++++---- app/api.py | 6 +++--- app/main.py | 2 +- openapi.json | 2 +- pyproject.toml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PROJECT_SPECS.md b/PROJECT_SPECS.md index 44060a3..c6cea63 100644 --- a/PROJECT_SPECS.md +++ b/PROJECT_SPECS.md @@ -154,7 +154,7 @@ GET /api/v1/health { "ok": true, "service": "governsai-precheck", - "version": "0.0.1" + "version": "0.1.0" } ``` @@ -170,7 +170,7 @@ GET /api/v1/ready { "ready": true, "service": "governsai-precheck", - "version": "0.0.1", + "version": "0.1.0", "checks": { "presidio": {"status": "ok", "message": "..."}, "policy": {"status": "ok", "message": "..."}, @@ -284,7 +284,7 @@ GET /api/v1/health { "ok": true, "service": "governsai-precheck", - "version": "0.0.1" + "version": "0.1.0" } ``` @@ -300,7 +300,7 @@ GET /api/v1/ready { "ready": true, "service": "governsai-precheck", - "version": "0.0.1", + "version": "0.1.0", "checks": { "presidio": { "status": "ok", diff --git a/app/api.py b/app/api.py index 7d85954..4945566 100644 --- a/app/api.py +++ b/app/api.py @@ -111,7 +111,7 @@ async def health(): return { "ok": True, "service": "governsai-precheck", - "version": "0.0.1" + "version": "0.1.0" } @router.get("/v1/ready") @@ -191,7 +191,7 @@ async def ready(): return { "ready": overall_ready, "service": "governsai-precheck", - "version": "0.0.1", + "version": "0.1.0", "checks": checks, "timestamp": int(time.time()) } @@ -207,7 +207,7 @@ async def metrics(): """ # Set service info if not already set set_service_info( - version="0.0.1", + version="0.1.0", build_date=os.getenv("BUILD_DATE", "unknown"), git_commit=os.getenv("GIT_COMMIT", "unknown") ) diff --git a/app/main.py b/app/main.py index 58b0f17..6b0fb80 100644 --- a/app/main.py +++ b/app/main.py @@ -39,7 +39,7 @@ def create_app() -> FastAPI: _configure_logging() app = FastAPI( title="GovernsAI Precheck", - version="0.0.1", + version="0.1.0", description="Policy evaluation and PII redaction service for GovernsAI", lifespan=lifespan ) diff --git a/openapi.json b/openapi.json index 5ed7bc0..e8c83fe 100644 --- a/openapi.json +++ b/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"GovernsAI Precheck","description":"Policy evaluation and PII redaction service for GovernsAI","version":"0.0.1"},"paths":{"/v1/health":{"get":{"summary":"Health","description":"Health check endpoint","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/ready":{"get":{"summary":"Ready","description":"Readiness check endpoint\n\nPerforms comprehensive checks to ensure the service is ready to handle requests:\n- Presidio analyzer and anonymizer initialization\n- Policy file parsing and validation\n- Core dependencies availability","operationId":"ready_v1_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/metrics":{"get":{"summary":"Metrics","description":"Prometheus metrics endpoint\n\nReturns metrics in Prometheus text format for monitoring and alerting.\nIncludes counters, histograms, and gauges for request tracking, performance\nmonitoring, and system health.","operationId":"metrics_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/u/{user_id}/precheck":{"post":{"summary":"Precheck","description":"Precheck endpoint for policy evaluation and PII redaction","operationId":"precheck_v1_u__user_id__precheck_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"X-Governs-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Governs-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrePostCheckRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/u/{user_id}/postcheck":{"post":{"summary":"Postcheck","description":"Postcheck endpoint for post-execution validation","operationId":"postcheck_v1_u__user_id__postcheck_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"X-Governs-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Governs-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrePostCheckRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"DecisionResponse":{"properties":{"decision":{"type":"string","title":"Decision"},"payload_out":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload Out"},"reasons":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reasons"},"policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Id"},"ts":{"type":"integer","title":"Ts"}},"type":"object","required":["decision","ts"],"title":"DecisionResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PrePostCheckRequest":{"properties":{"tool":{"type":"string","title":"Tool"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"corr_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corr Id"}},"type":"object","required":["tool","payload"],"title":"PrePostCheckRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"GovernsAI Precheck","description":"Policy evaluation and PII redaction service for GovernsAI","version":"0.1.0"},"paths":{"/v1/health":{"get":{"summary":"Health","description":"Health check endpoint","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/ready":{"get":{"summary":"Ready","description":"Readiness check endpoint\n\nPerforms comprehensive checks to ensure the service is ready to handle requests:\n- Presidio analyzer and anonymizer initialization\n- Policy file parsing and validation\n- Core dependencies availability","operationId":"ready_v1_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/metrics":{"get":{"summary":"Metrics","description":"Prometheus metrics endpoint\n\nReturns metrics in Prometheus text format for monitoring and alerting.\nIncludes counters, histograms, and gauges for request tracking, performance\nmonitoring, and system health.","operationId":"metrics_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/u/{user_id}/precheck":{"post":{"summary":"Precheck","description":"Precheck endpoint for policy evaluation and PII redaction","operationId":"precheck_v1_u__user_id__precheck_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"X-Governs-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Governs-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrePostCheckRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/u/{user_id}/postcheck":{"post":{"summary":"Postcheck","description":"Postcheck endpoint for post-execution validation","operationId":"postcheck_v1_u__user_id__postcheck_post","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}},{"name":"X-Governs-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Governs-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrePostCheckRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"DecisionResponse":{"properties":{"decision":{"type":"string","title":"Decision"},"payload_out":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Payload Out"},"reasons":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Reasons"},"policy_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Id"},"ts":{"type":"integer","title":"Ts"}},"type":"object","required":["decision","ts"],"title":"DecisionResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PrePostCheckRequest":{"properties":{"tool":{"type":"string","title":"Tool"},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags"},"corr_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Corr Id"}},"type":"object","required":["tool","payload"],"title":"PrePostCheckRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0c9c6f1..86e1695 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "governsai-precheck" -version = "0.0.1" +version = "0.1.0" description = "Policy evaluation and PII redaction service for GovernsAI" authors = [{name = "GovernsAI", email = "team@governs.ai"}] readme = "README.md"