v1.0.663 — Sentinel Policy M1: immutable ingestion spine
Context
Sentinel Policy is a new Sentinel Signal Systems product (policy.sentinelsignal.io), a healthcare policy-change intelligence platform. The user supplied a 207-requirement architecture spec (saved as the sentinel-policy-architecture-plan project memory) and asked to go ahead with the implementation. Building all 12 milestones in one pass isn't realistic — the spec itself mandates milestone-gated delivery (AGENT-001: "do not skip ahead"), and M0's own exit criterion blocks any commercial/customer-facing data endpoint until a source-terms/licensing review happens, which this pass does not do. Scoped explicitly to M1 only: source registry, scheduler, secure bounded fetcher, immutable object archive, source-health model. Confirmed with the user before starting.
What shipped
A new policy/ subproject inside this same repo (not a separate monorepo), mirroring verify/'s structure and conventions 1:1: pyproject.toml, src/sentinel_policy/, Alembic migrations, Dockerfile, tests/. Package sentinel_policy.
- Data model (
db/models.py,alembic/versions/0001_initial.py):Source(SRC-001/002/004/006 - Object storage (
storage.py):ObjectStoreProtocol withS3ObjectStore(boto3, IONOS - Secure fetcher (
fetch.py, SECURITY-001/002/003, ING-002..007): per-source hostname - Adapter framework (
adapters/, ADP-001..007): theSourceAdapterProtocol exactly as - Ingest orchestration (
ingest.py):run_discovery()— the one function both the manual-trigger - Scheduler (
workers/loop.py, SRC-003): polls enabled sources whose cadence has elapsed, runs - Minimal internal API (
api/):GET /healthz,/readyz,/v1/build(public); admin-token-gated - Deploy wiring: new
{$POLICY_DOMAIN}Caddy block (mirrorsverify-web's exact shape); new
— identity, org, program, jurisdiction, allowlisted hostnames, adapter config, cadence, terms-review state, health status), SourceDiscoveryRun (ING-001), DiscoveredResource, DocumentAsset (RAW-001..006 — content-addressed by SHA-256, never overwritten), DocumentObservation (ING-002 — full HTTP metadata per fetch).
S3-compatible, reuses the same AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY credentials already provisioned for Postgres backups, pointed at a new bucket) and LocalFilesystemObjectStore (no external dependency — selected automatically when SENTINEL_POLICY_S3_BUCKET is unset, used for local dev and all tests). Put-by-hash, immutable — refuses to overwrite an existing key.
allowlist, SSRF defense (resolves DNS itself, rejects private/loopback/link-local addresses, re-validated on every redirect hop — not just the initial URL), size/timeout caps, MIME validation via header + content-sniffing, bounded retries with jitter (tenacity) on transient failures only, permanent errors (bad host, oversized, disallowed MIME, 4xx) never retried.
specified, plus one concrete, generic, non-source-specific implementation — DirectDocumentAdapter (fetches one configured literal URL). No CMS/Medicaid business logic; that's M2.
API route and the scheduler call, so their behavior can't drift apart. Implements ADP-004 idempotent rediscovery (identical bytes never create a duplicate DocumentAsset) without losing observation history (ING-007 — a new DocumentObservation row is still created every run), and ING-006 (permanent errors route the source to REVIEW_REQUIRED rather than retrying forever).
discovery through the same code path as a manual trigger. Idles safely with zero sources registered.
GET/POST /v1/sources, GET /v1/sources/{id}, GET /v1/sources/{id}/status, POST /v1/sources/{id}/discovery-runs. No public/customer-facing data of any kind ships this pass.
postgres-init-policy/policy-migrate/policy-web/policy-worker services in deploy/ionos/docker-compose.yml (mirrors the verify service trio and the proven multi-database-on-one-shared-Postgres pattern); deploy.sh now health-waits on policy-web after the main restart and extends the existing alembic-revision-id-length guard (the 0026 incident's failure mode) to cover policy/'s migrations too.
Verified live in the real Docker image (not just unit tests)
Built the actual policy/Dockerfile image, ran policy-migrate's entrypoint for real (Alembic upgrade head succeeded, created all 5 tables), then ran policy-web and, against a real HTTP fixture server reachable from inside the container:
- registered a source via the real API,
- triggered a real discovery run — fetched, hashed, and archived a real document,
- confirmed the archived bytes on disk match the source exactly (M1's stated exit criterion),
- re-triggered discovery and confirmed no duplicate asset was created (idempotency),
- confirmed the admin-token gate correctly rejects missing/wrong tokens and accepts the right one.
Verification
PYTHONPATH=policy/src:policy/tests python -m pytest policy/tests -q— 27 passed (adapter-fixturepython -m pytest tests/ -q— 334 passed (was 330; +4 new deploy-wiring regression tests).PYTHONPATH=verify/src:verify/tests python -m pytest verify/tests -q— 824 passed, confirmingdocker compose --env-file deploy/ionos/prod.env.example -f deploy/ionos/docker-compose.yml configbash -n deploy/ionos/deploy.sh— confirms the new deploy steps didn't break shell syntax.
fetch tests including SSRF-blocking and redirect-hop re-validation, storage idempotency, migration upgrade/downgrade/upgrade round-trip, full API contract tests).
zero changes to verify/ (one transient failure seen once, reproduced as unrelated pre-existing flakiness on isolated re-run).
— confirms the new services resolve, dependency ordering is correct (postgres → postgres-init-policy → policy-migrate → policy-web/policy-worker → caddy).
Not done / explicitly out of scope
No CMS/Medicaid/MAC adapter business logic (M2). No policy identity/versioning/sectionization (M3). No diff engine, LLM extraction, evidence/confidence scoring, human review console (M4-M8). No customer REST API, entitlements, billing, webhooks, email/CSV, or MCP tools (M9-M10) — all gated behind the M0 legal/source-terms/licensing review, which has not happened. Everything shipped this pass is admin-token-gated internal infrastructure with zero sources registered by default.
One real external dependency this pass could not fully close in code: a production IONOS S3 bucket for SENTINEL_POLICY_S3_BUCKET may need provisioning/credentials-permission confirmation before the object store is anything other than the local-filesystem fallback in the running container.