---
phase: 01-foundation
plan: 02
subsystem: api
tags: [python, sdk, pydantic, requests, hatchling, pyproject, enums, abc]

# Dependency graph
requires: []
provides:
  - pip-installable meshulash-guard package with src/ layout
  - Guard class with scan_input (POST to /api/security/scan), deanonymize, clear_cache
  - Action and Condition enums (REPLACE/BLOCK/LOG, ANY/ALL/K_OF/CONTEXTUAL)
  - ScanResult, ScannerResult, Detection Pydantic v2 frozen models
  - MeshulashError exception hierarchy (AuthError, ServerError, ConnectionError, ValidationError)
  - _build_session() requests.Session with retry on POST and no session-level Content-Type
  - BaseScanner and BaseNormalizer abstract base classes
affects:
  - 02-scanners (PIIScanner, TopicScanner, etc. implement BaseScanner)
  - 03-placeholder-vault (Guard._vault and deanonymize() patterns)
  - 04-docs (public API surface defined here)

# Tech tracking
tech-stack:
  added: [pydantic>=2.0, requests>=2.28, hatchling>=1.26, urllib3 (bundled)]
  patterns:
    - src/ layout for pip-installable Python package
    - requests.Session with session-level auth headers and retry adapter
    - Pydantic v2 frozen BaseModel for immutable result types
    - (str, Enum) mixin with __str__/__format__ override for Python 3.12+ compat
    - abc.ABC + @abstractmethod for scanner/normalizer contracts
    - Guard placeholder vault: client-side dict accumulating scan result placeholders

key-files:
  created:
    - src/meshulash_guard/__init__.py
    - src/meshulash_guard/enums.py
    - src/meshulash_guard/exceptions.py
    - src/meshulash_guard/models.py
    - src/meshulash_guard/_http.py
    - src/meshulash_guard/guard.py
    - src/meshulash_guard/scanners/__init__.py
    - src/meshulash_guard/scanners/base.py
    - pyproject.toml
    - README.md
  modified: []

key-decisions:
  - "license field must use {text = ...} table syntax not bare string in modern hatchling"
  - "Python 3.12+ changed (str, Enum) __str__ to return name not value — override __str__ and __format__ required"
  - "Content-Type NOT set at session level to avoid breaking future multipart uploads"
  - "scan_output raises NotImplementedError (v2 feature) — stub defined in v1"
  - "README.md required by hatchling for build to succeed"

patterns-established:
  - "Guard builds requests.Session at __init__ with X-Api-Key/X-Tenant-Id headers"
  - "Guard calls _validate_credentials() on init — fail fast on invalid key"
  - "scan_input() builds guardline_specs as {sdk_{class}_{i}: scanner.to_guardline_spec()}"
  - "ScanResult.model_validate(resp.json()) for parsing server responses"
  - "self._vault.update(result.placeholders) accumulates placeholders each scan call"
  - "BaseScanner.to_guardline_spec() is the core contract all Phase 2 scanners must implement"

# Metrics
duration: 4min
completed: 2026-02-26
---

# Phase 1 Plan 02: SDK Scaffold Summary

**pip-installable meshulash-guard package with Guard HTTP client, Pydantic v2 result types, exception hierarchy, retry-enabled requests.Session, and BaseScanner/BaseNormalizer abstract contracts**

## Performance

- **Duration:** 4 min
- **Started:** 2026-02-26T16:51:19Z
- **Completed:** 2026-02-26T16:56:07Z
- **Tasks:** 2
- **Files modified:** 10

## Accomplishments

- `pip install -e .` succeeds from repo root — package fully installable with pydantic and requests as deps
- `from meshulash_guard import Guard, Action, Condition, ScanResult` works end-to-end
- Guard class wired for HTTP: builds session with retry on POST, validates credentials on init, scan_input POSTs to /api/security/scan with guardline_specs built from scanners
- Phase 2 contracts established: BaseScanner.to_guardline_spec() and BaseNormalizer.to_normalizer_spec() enforced via abc.ABC

## Task Commits

Each task was committed atomically:

1. **Task 1: Create package structure with pyproject.toml, enums, and exceptions** - `2d28f85` (feat)
2. **Task 2: Create Pydantic models, HTTP client, Guard class, base classes, and finalize __init__.py** - `7dcce8d` (feat)

**Plan metadata:** (docs commit follows)

## Files Created/Modified

- `pyproject.toml` - PEP 517/518 package config with hatchling build backend, pydantic+requests deps
- `README.md` - Minimal readme required by hatchling for build metadata
- `src/meshulash_guard/__init__.py` - Public API: Guard, ScanResult, ScannerResult, Detection, Action, Condition, all exceptions, __version__
- `src/meshulash_guard/enums.py` - Action (REPLACE/BLOCK/LOG) and Condition (ANY/ALL/K_OF/CONTEXTUAL) with Python 3.12+ str compatibility
- `src/meshulash_guard/exceptions.py` - MeshulashError hierarchy with AuthError, ServerError (status_code), ConnectionError, ValidationError
- `src/meshulash_guard/models.py` - Detection, ScannerResult, ScanResult as frozen Pydantic v2 BaseModels
- `src/meshulash_guard/_http.py` - _build_session() with Retry(allowed_methods={"POST"}), no session-level Content-Type
- `src/meshulash_guard/guard.py` - Guard class: scan_input, scan_output (NotImplementedError), deanonymize, clear_cache
- `src/meshulash_guard/scanners/__init__.py` - Scanner subpackage stub (Phase 2 will add exports)
- `src/meshulash_guard/scanners/base.py` - BaseScanner and BaseNormalizer abstract classes

## Decisions Made

- **Python 3.12+ enum compatibility:** In Python 3.12, `str(Action.REPLACE)` changed to return `'Action.REPLACE'` instead of `'replace'`. Fixed by adding `_StrValueMixin` that overrides `__str__` and `__format__` to return `self.value`. The `==` comparison still works via the `str` mixin on all Python versions.
- **license field format:** Modern hatchling (1.29.0) requires `license = { text = "Proprietary" }` table syntax rather than bare string `license = "Proprietary"`. Changed to table syntax.
- **README.md required:** Hatchling raises `OSError: Readme file does not exist: README.md` during build if the readme field is specified without the file existing. Created minimal README.md.
- **scan_output is NotImplementedError:** Per plan spec — raises `NotImplementedError("scan_output() is coming in v2")`. Method signature defined so Phase 2 knows the contract.
- **Virtual environment for installation:** macOS externally-managed Python requires venv for pip install. Created `.venv/` at repo root.

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Python 3.12+ (str, Enum) str() behavior change breaks f-string assertions**

- **Found during:** Task 2 (verification run)
- **Issue:** Python 3.12 changed `str(StrEnum)` to return the enum name (`'Condition.ANY'`) not value (`'any'`). The plan's test `assert f'{Condition.ANY}' == 'any'` failed.
- **Fix:** Added `_StrValueMixin` base class that overrides `__str__` and `__format__` to return `self.value`. Both `Action` and `Condition` inherit from this mixin instead of plain `(str, Enum)`.
- **Files modified:** `src/meshulash_guard/enums.py`
- **Verification:** `f'{Condition.ANY}' == 'any'` and `str(Action.REPLACE) == 'replace'` both pass on Python 3.13.2
- **Committed in:** `7dcce8d` (Task 2 commit)

**2. [Rule 1 - Bug] pyproject.toml license field incompatible with modern hatchling**

- **Found during:** Task 2 (pip install -e .)**
- **Issue:** `license = "Proprietary"` raises `ValueError: Unknown license: 'proprietary'` in hatchling 1.29.0 which requires SPDX identifiers for bare string licenses.
- **Fix:** Changed to `license = { text = "Proprietary" }` table syntax which accepts arbitrary text.
- **Files modified:** `pyproject.toml`
- **Verification:** `pip install -e .` succeeds without warnings
- **Committed in:** `7dcce8d` (Task 2 commit)

**3. [Rule 3 - Blocking] Missing README.md blocked pip install**

- **Found during:** Task 2 (pip install -e .)**
- **Issue:** `pyproject.toml` references `readme = "README.md"` but no README.md existed. Hatchling raises `OSError` during build.
- **Fix:** Created `README.md` with minimal package description.
- **Files modified:** `README.md` (new file)
- **Verification:** `pip install -e .` succeeds
- **Committed in:** `7dcce8d` (Task 2 commit)

---

**Total deviations:** 3 auto-fixed (2 bugs, 1 blocking)
**Impact on plan:** All auto-fixes necessary for package to install correctly. No scope creep. The enum mixin fix is important for all future code that uses f-strings or str() with enum values.

## Issues Encountered

- macOS externally-managed Python environment prevents system-wide pip install — created `.venv/` at repo root. The `pip install -e .` in the plan must be run with `.venv/bin/pip install -e .` or after `source .venv/bin/activate`.

## User Setup Required

None - no external service configuration required. A `.venv/` directory was created at `/Users/shalev/Desktop/meshulash_guard/.venv/` to satisfy macOS externally-managed Python requirements.

## Next Phase Readiness

- All Phase 2 scanner contracts are in place: `BaseScanner.to_guardline_spec()` is the only method Phase 2 scanners must implement
- `guard.scan_input()` already builds guardline_specs from scanners using `{sdk_{class}_{i}: scanner.to_guardline_spec()}`
- `ScanResult.model_validate()` is wired — Phase 2 only needs the server endpoint to be up
- The `_vault` placeholder accumulation pattern is in place for Phase 3

---
*Phase: 01-foundation*
*Completed: 2026-02-26*
