Skip to content

feat(policy): rag-policy package — PolicyEngine PDP + coverage linter (Step 1.1c) - #46

Merged
officialCodeWork merged 1 commit into
mainfrom
build/phase-1/step-1.1c-policy-engine-package
May 24, 2026
Merged

feat(policy): rag-policy package — PolicyEngine PDP + coverage linter (Step 1.1c)#46
officialCodeWork merged 1 commit into
mainfrom
build/phase-1/step-1.1c-policy-engine-package

Conversation

@officialCodeWork

Copy link
Copy Markdown
Owner

Summary

Step 1.1c of the Phase 1 architecture-refactor window. Lands the
Policy Decision Point ADR-0005 calls for: a single auditable surface
that every retrieval / ingest / egress code path consults, plus a CI
linter that fails when a new governance-relevant call site bypasses it.

  • New workspace package packages/policy/ (rag-policy v0.1.0,
    import root rag_policy). Depends on rag-core + rag-observability only
    (matches the dependency graph in CLAUDE.md).
  • PolicyEngine ABC — async evaluate(ctx, decision, subject) -> PolicyResult and async filter_pushdown(ctx, decision) -> FilterExpr.
  • NoopPolicyEngine — always-ALLOW reference impl; filter_pushdown
    still emits And(Eq("tenant_id", str(ctx.tenant_id))) so retrieval
    backends cannot cross-tenant leak even with the noop loaded.
  • PolicyWriter facade (mirrors AuditWriter) — delegates to the
    engine and emits a policy.decision structured-log entry via
    rag_observability.logging. Consumers should reach for the writer.
  • PolicyDecision enum: read_chunk / ingest_doc / egress_text
    / quota_check / rate_limit / execute_plan.
  • PolicyResult frozen union (allow / deny(reason) /
    transform(subject)) with predicate helpers.
  • FilterExpr mini-language (Eq, AnyIn, And, Or, Not,
    TrueExpr) — discriminated-union of frozen Pydantic models, returned
    by filter_pushdown and consumed by retrieval backends.
  • Coverage linter tests/policy/coverage.py greps for
    retrieve_ids / hydrate / bulk_index / stream_index /
    bulk_embed / .complete( call sites without an adjacent
    PolicyEngine / PolicyWriter marker. File-allowlist at the top;
    consumers (gateway, ingest) shrink the allowlist as they wire the PDP
    in. A future Step 1.1f tightens the linter from file-allowlist to
    call-pattern matching.

Step 6.4 (ACL egress verifier as a second layer) is rendered redundant
by this PDP — see ADR-0005. The TRACKER entry for 6.4 will flip to
"Superseded by ADR-0005" in the Phase 6 refactor pass.

Documentation

  • New docs/reference/rag-policy.md
    (Overview / Usage / Internals / Extension points).
  • docs/README.md index updated.
  • ADR-0005 and docs/architecture/policy-engine.md already describe the
    shipped shape — no changes needed there.

Test plan

  • `uv run ruff check . && uv run ruff format --check .` — clean
  • `uv run mypy packages/ apps/gateway/` — 83 source files, no issues
    (+7 from Step 1.1b)
  • `uv run python scripts/check_logging.py` — RAG001 clean
  • `uv run pytest tests/ packages/ --ignore=tests/integration -x -q` —
    528 passed, 1 skipped (20 new conformance + 1 coverage linter)
  • Coverage linter passes against current tree (allowlist correct
    for noop + abstracts + real backends).

TRACKER housekeeping rolled in

  • Step 1.1c flipped to ✅; Phase 1 done-count bumped (3 → 4); Next
    action set to 1.1d — Pipeline + Batcher primitives.
  • PR history table: added rows for #44 (Step 1.1a) and #45 (Step 1.1b)
    which were missed when those merged.

🤖 Generated with Claude Code

@officialCodeWork
officialCodeWork force-pushed the build/phase-1/step-1.1c-policy-engine-package branch from aff239a to dff5a01 Compare May 24, 2026 06:55
… (Step 1.1c)

New workspace package packages/policy/ (import root rag_policy v0.1.0).
Establishes the single Policy Decision Point ADR-0005 calls for: every
retrieval / ingest / egress code path consults PolicyEngine, and a CI
linter fails when a governance-relevant SPI call lands without an
adjacent consultation.

Surface
- PolicyEngine ABC: async evaluate(ctx, decision, subject) -> PolicyResult
  and async filter_pushdown(ctx, decision) -> FilterExpr.
- NoopPolicyEngine: always-ALLOW; filter_pushdown still emits a
  tenant-scoped And(Eq("tenant_id", ...)) so backends never cross-tenant
  leak even with the noop loaded.
- PolicyWriter facade (mirrors AuditWriter): delegates to engine, emits
  policy.decision structured log via rag-observability.
- PolicyDecision enum: read_chunk / ingest_doc / egress_text /
  quota_check / rate_limit / execute_plan.
- PolicyResult: frozen union (allow / deny(reason) / transform(subject))
  with is_allow / is_deny / is_transform / transformed_or helpers.
- QuotaSubject / RateLimitSubject for the non-Chunk decision subjects.
- FilterExpr mini-language (Eq, AnyIn, And, Or, Not, TrueExpr):
  discriminated-union of frozen Pydantic models; backends translate to
  native filter languages.

Coverage linter
- tests/policy/coverage.py greps for retrieve_ids / hydrate / bulk_index
  / stream_index / bulk_embed / .complete call sites without an adjacent
  PolicyEngine / PolicyWriter marker. File-allowlist at top; failures
  block CI. Consumers (gateway, ingest) shrink the allowlist as they
  wire the PDP in. Future Step 1.1f tightens to call-pattern matching.
- Collected via pytest python_files extended to include coverage.py.

Wiring
- packages/policy added to [tool.uv.workspace].members and to pytest
  pythonpath in root pyproject.toml.
- Dependencies: rag-core + rag-observability only (matches backends
  precedent; CLAUDE.md graph: policy -> core).

Tests + gates
- 20 conformance tests under tests/contract/test_policy_engine.py.
- 1 coverage-linter run under tests/policy/coverage.py.
- ruff + mypy --strict (83 source files, +7 from 1.1b) + RAG001 logging
  check all green; full non-integration suite 528 tests pass.

Docs
- New docs/reference/rag-policy.md (Overview / Usage / Internals /
  Extension points), docs/README.md index updated. ADR-0005 and
  docs/architecture/policy-engine.md already match the shipped shape.

TRACKER housekeeping
- Step 1.1c flipped to done; Next action set to 1.1d (Pipeline +
  Batcher primitives).
- PR history table: added rows for #44 (1.1a) and #45 (1.1b) which
  were missed when those steps merged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@officialCodeWork
officialCodeWork force-pushed the build/phase-1/step-1.1c-policy-engine-package branch from dff5a01 to 06b9aa9 Compare May 24, 2026 07:14
@officialCodeWork
officialCodeWork merged commit 4b8ae58 into main May 24, 2026
11 checks passed
officialCodeWork added a commit that referenced this pull request May 24, 2026
chore(tracker): sync PR links for steps 1.1a/1.1c + log #46#48
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.

1 participant