policyengine-observability 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. policyengine_observability-0.3.0/.github/copilot-instructions.md +7 -0
  2. policyengine_observability-0.3.0/AGENTS.md +23 -0
  3. policyengine_observability-0.3.0/CHANGELOG.md +27 -0
  4. policyengine_observability-0.3.0/CLAUDE.md +21 -0
  5. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/PKG-INFO +9 -1
  6. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/README.md +8 -0
  7. policyengine_observability-0.3.0/docs/engineering/skills/README.md +16 -0
  8. policyengine_observability-0.3.0/docs/engineering/skills/github-prs.md +46 -0
  9. policyengine_observability-0.3.0/docs/engineering/skills/repository-guidance.md +80 -0
  10. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/runtime.py +6 -1
  11. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/pyproject.toml +1 -1
  12. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/tests/test_runtime.py +72 -0
  13. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/uv.lock +1 -1
  14. policyengine_observability-0.2.0/CHANGELOG.md +0 -9
  15. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/bump_version.py +0 -0
  16. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/check-changelog.sh +0 -0
  17. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/fetch_version.py +0 -0
  18. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/get-changelog-diff.sh +0 -0
  19. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/publish-git-tag.sh +0 -0
  20. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/workflows/pr.yml +0 -0
  21. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.github/workflows/push.yml +0 -0
  22. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/.gitignore +0 -0
  23. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/__init__.py +0 -0
  24. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/adapters/__init__.py +0 -0
  25. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/adapters/fastapi.py +0 -0
  26. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/adapters/flask.py +0 -0
  27. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/config.py +0 -0
  28. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/context.py +0 -0
  29. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/integrations/__init__.py +0 -0
  30. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/integrations/httpx.py +0 -0
  31. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/logging.py +0 -0
  32. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/policyengine_observability/segments.py +0 -0
  33. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/tests/test_fastapi_adapter.py +0 -0
  34. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/tests/test_flask_adapter.py +0 -0
  35. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/tests/test_public_api.py +0 -0
  36. {policyengine_observability-0.2.0 → policyengine_observability-0.3.0}/tests/test_release_scripts.py +0 -0
@@ -0,0 +1,7 @@
1
+ # Copilot Instructions
2
+
3
+ Repository-wide AI-facing engineering guidance lives in `AGENTS.md`.
4
+ Canonical skills live under `docs/engineering/skills/`.
5
+
6
+ Use those files as the source of truth. Keep this adapter thin and avoid
7
+ duplicating detailed testing, CI, formatting, release, or architecture rules.
@@ -0,0 +1,23 @@
1
+ # Agent Instructions
2
+
3
+ These instructions apply repository-wide.
4
+
5
+ ## Skills System
6
+
7
+ Canonical AI-facing engineering skills live under `docs/engineering/skills/`.
8
+ Use those files as the source of truth across Codex, Claude, Copilot, and other
9
+ AI tools.
10
+
11
+ Before opening, replacing, or sharing any pull request, read
12
+ `docs/engineering/skills/github-prs.md`.
13
+
14
+ Before making or reviewing repository-wide API, testing, documentation, release,
15
+ or package-boundary changes, read
16
+ `docs/engineering/skills/repository-guidance.md`.
17
+
18
+ ## Repository Boundaries
19
+
20
+ `policyengine-observability` is the shared PolicyEngine observability runtime.
21
+ Keep framework-specific behavior in adapters or integrations, keep the core
22
+ runtime usable outside HTTP requests, and keep observability failures from
23
+ breaking application code.
@@ -0,0 +1,27 @@
1
+ ## [0.3.0] - 2026-06-23
2
+
3
+ ### Added
4
+
5
+ - Add model-agnostic AI harness instructions and canonical engineering skills.
6
+
7
+ ### Fixed
8
+
9
+ - Preserve internal dispatch segment timings on the parent worker operation log.
10
+
11
+
12
+ ## [0.2.1] - 2026-06-22
13
+
14
+ ### Changed
15
+
16
+ - Document the release workflow in the README.
17
+
18
+
19
+ ## [0.2.0] - 2026-06-22
20
+
21
+ ### Added
22
+
23
+ - Add pull request and push CI/CD workflows with changelog, lint, coverage, versioning, tagging, and PyPI publishing gates.
24
+
25
+
26
+ # Changelog
27
+
@@ -0,0 +1,21 @@
1
+ # Claude Instructions
2
+
3
+ These instructions apply repository-wide.
4
+
5
+ ## Canonical Guidance
6
+
7
+ Repository-wide AI-facing engineering guidance lives in `AGENTS.md`.
8
+ Canonical skills live under `docs/engineering/skills/`.
9
+
10
+ Use those files as the source of truth. This file is a Claude adapter and should
11
+ stay thin; do not duplicate detailed testing, CI, formatting, release, or
12
+ architecture rules here.
13
+
14
+ ## Required Skill Lookup
15
+
16
+ Before opening, replacing, or sharing a PR, read
17
+ `docs/engineering/skills/github-prs.md`.
18
+
19
+ Before making or reviewing repository-wide API, testing, documentation, release,
20
+ or package-boundary changes, read
21
+ `docs/engineering/skills/repository-guidance.md`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine-observability
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Shared PolicyEngine observability runtime for logs, timings, metrics, and OpenTelemetry.
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  Requires-Python: >=3.12
@@ -50,3 +50,11 @@ The package intentionally keeps framework support in adapters:
50
50
 
51
51
  OpenTelemetry imports are lazy. Timing and structured logging can run without
52
52
  an OTel backend; exporting traces/metrics is opt-in through configuration.
53
+
54
+ ## Release workflow
55
+
56
+ Changes should include a Towncrier fragment in `changelog.d/`. Pull requests
57
+ run changelog, Ruff, and coverage checks. Pushes to `main` run the same gates,
58
+ then publish a versioning commit that builds the changelog and bumps
59
+ `pyproject.toml`. That versioning commit publishes the package to PyPI through
60
+ trusted publishing, creates a matching git tag, and opens a GitHub release.
@@ -11,3 +11,11 @@ The package intentionally keeps framework support in adapters:
11
11
 
12
12
  OpenTelemetry imports are lazy. Timing and structured logging can run without
13
13
  an OTel backend; exporting traces/metrics is opt-in through configuration.
14
+
15
+ ## Release workflow
16
+
17
+ Changes should include a Towncrier fragment in `changelog.d/`. Pull requests
18
+ run changelog, Ruff, and coverage checks. Pushes to `main` run the same gates,
19
+ then publish a versioning commit that builds the changelog and bumps
20
+ `pyproject.toml`. That versioning commit publishes the package to PyPI through
21
+ trusted publishing, creates a matching git tag, and opens a GitHub release.
@@ -0,0 +1,16 @@
1
+ # Engineering Skills
2
+
3
+ This directory is the canonical source for AI-facing engineering rules.
4
+
5
+ Tool-specific instruction files such as `AGENTS.md`, `CLAUDE.md`, and
6
+ `.github/copilot-instructions.md` should point here instead of duplicating
7
+ implementation-specific guidance. When a rule changes, update the skill here
8
+ first, then keep adapters thin.
9
+
10
+ Current skills:
11
+
12
+ - `github-prs.md`: same-repository PR workflow, required pre-PR checks,
13
+ changelog-fragment requirements, final formatter/linter requirements, PR head
14
+ verification, and title conventions.
15
+ - `repository-guidance.md`: package structure, commands, runtime boundaries,
16
+ test expectations, release expectations, and repo-specific anti-patterns.
@@ -0,0 +1,46 @@
1
+ # GitHub PRs
2
+
3
+ These rules apply to every developer and AI agent opening pull requests in this
4
+ repository.
5
+
6
+ ## Same-Repository PRs
7
+
8
+ Open PRs from branches in `PolicyEngine/policyengine-observability`, not from
9
+ personal forks. Fork PRs are more likely to miss repository secrets and can
10
+ produce different CI behavior from branches in the canonical repository.
11
+
12
+ Before creating or sharing a PR:
13
+
14
+ 1. Confirm the canonical repository is reachable:
15
+ `gh repo view PolicyEngine/policyengine-observability --json nameWithOwner`.
16
+ 2. Open a GitHub issue for the work, or verify that an appropriate issue
17
+ already exists.
18
+ 3. Put `Fixes #ISSUE_NUMBER` as the first line of the PR description, using the
19
+ issue number from the issue created or found in the previous step.
20
+ 4. Add a Towncrier changelog fragment under `changelog.d/` using the issue
21
+ number or a clear slug and the appropriate configured type, for example
22
+ `changelog.d/ISSUE_NUMBER.fixed.md`.
23
+ 5. Before the final commit in the PR, run the formatter and linter:
24
+ `uv run --extra dev ruff format .` and
25
+ `uv run --extra dev ruff check .`. If formatting changes files, review and
26
+ stage those changes before committing.
27
+ 6. Run tests with coverage:
28
+ `uv run --extra dev --extra all coverage run -m pytest` and
29
+ `uv run --extra dev --extra all coverage report`.
30
+ 7. Push the current branch to the canonical repository:
31
+ `git push origin HEAD`.
32
+ 8. Create the PR as a draft from that same repository:
33
+ `gh pr create --draft --repo PolicyEngine/policyengine-observability --head "$(git branch --show-current)" --base main`.
34
+ 9. Verify the PR is draft and the head repository is canonical:
35
+ `gh pr view <PR> --repo PolicyEngine/policyengine-observability --json isDraft,headRepositoryOwner,headRepository`.
36
+ 10. Before sharing the PR, verify CI has been checked:
37
+ `gh pr checks <PR> --repo PolicyEngine/policyengine-observability`.
38
+
39
+ If you cannot push to the canonical repository, stop and ask for access. Do not
40
+ create a fork PR as a fallback. If you accidentally create one, close it and
41
+ replace it with a same-repository draft PR.
42
+
43
+ ## PR Title
44
+
45
+ Do not add `[codex]`, `[claude]`, `[copilot]`, or other agent labels to PR
46
+ titles. Use a plain descriptive title.
@@ -0,0 +1,80 @@
1
+ # Repository Guidance
2
+
3
+ Use this skill when making or reviewing repository-wide API, testing,
4
+ documentation, release, or package-boundary changes.
5
+
6
+ ## Commands
7
+
8
+ ```bash
9
+ uv sync --extra dev --extra all
10
+ uv run --extra dev ruff format .
11
+ uv run --extra dev ruff format --check .
12
+ uv run --extra dev ruff check .
13
+ uv run --extra dev --extra all coverage run -m pytest
14
+ uv run --extra dev --extra all coverage report
15
+ uv build
16
+ ```
17
+
18
+ To check for a changelog fragment locally:
19
+
20
+ ```bash
21
+ uv run --extra dev towncrier check --compare-with origin/main
22
+ ```
23
+
24
+ ## What Lives Here
25
+
26
+ - `policyengine_observability/config.py` resolves environment-driven runtime
27
+ configuration.
28
+ - `policyengine_observability/context.py` defines request and operation log
29
+ payload structures.
30
+ - `policyengine_observability/runtime.py` owns context management, segments,
31
+ structured logs, metrics, traces, events, and fail-open behavior.
32
+ - `policyengine_observability/adapters/` contains framework adapters such as
33
+ Flask and FastAPI.
34
+ - `policyengine_observability/integrations/` contains optional integrations
35
+ such as HTTP client instrumentation.
36
+ - `.github/` contains changelog, versioning, tagging, and PyPI publication
37
+ automation.
38
+ - `tests/` should cover runtime behavior, framework adapters, public exports,
39
+ and release scripts.
40
+
41
+ ## Design Boundaries
42
+
43
+ - Keep the core runtime framework-agnostic. HTTP frameworks belong in adapters.
44
+ - Keep the context-manager API usable from HTTP requests, worker functions,
45
+ CLI scripts, and tests.
46
+ - Keep OpenTelemetry optional and lazily imported. Timing and structured
47
+ logging must work without an OTel backend.
48
+ - Observability failures must fail open: record an internal observability error
49
+ when practical, but do not break the application operation being observed.
50
+ - Preserve structured log schemas. Make additive changes when possible; bump
51
+ schema versions for breaking payload changes.
52
+ - Keep metric attributes bounded and low-cardinality. Do not put raw paths,
53
+ full URLs, request bodies, or unbounded user-provided values into metric
54
+ labels.
55
+ - Keep segment names stable. Prefer registered segment enums in consuming
56
+ applications, while preserving safe string fallback behavior.
57
+
58
+ ## Testing
59
+
60
+ Add focused tests for runtime context behavior and failure paths whenever
61
+ changing `runtime.py`. Adapter changes should include framework-level tests that
62
+ exercise request setup, response headers, error paths, and teardown behavior.
63
+
64
+ Release automation changes should include tests for the helper scripts when the
65
+ logic is non-trivial.
66
+
67
+ ## Release Expectations
68
+
69
+ Every behavior change should include a Towncrier fragment in `changelog.d/`.
70
+ The push workflow builds the changelog, bumps the package version, tags the
71
+ release, and publishes to PyPI through trusted publishing.
72
+
73
+ ## Anti-Patterns
74
+
75
+ - Do not add a hard dependency on a specific observability vendor.
76
+ - Do not require OTel configuration for logs or timings to function.
77
+ - Do not duplicate framework behavior in the core runtime.
78
+ - Do not swallow application exceptions from observed code.
79
+ - Do not use `[codex]`, `[claude]`, `[copilot]`, or other agent labels in PR
80
+ titles.
@@ -306,6 +306,11 @@ class ObservabilityRuntime:
306
306
  context: RequestObservabilityContext,
307
307
  ) -> None:
308
308
  try:
309
+ parent_operation = _OPERATION_CONTEXT.get()
310
+ timings = context.timings_ms
311
+ if context.internal_dispatch and parent_operation is not None:
312
+ timings = parent_operation.timings_ms
313
+ context.timings_ms = timings
309
314
  operation = OperationObservabilityContext(
310
315
  config=context.config,
311
316
  name=context.route,
@@ -316,7 +321,7 @@ class ObservabilityRuntime:
316
321
  "endpoint": context.endpoint,
317
322
  "path": context.path,
318
323
  },
319
- timings_ms=context.timings_ms,
324
+ timings_ms=timings,
320
325
  emit_log=False,
321
326
  record_metric=False,
322
327
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "policyengine-observability"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Shared PolicyEngine observability runtime for logs, timings, metrics, and OpenTelemetry."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "PolicyEngine", email = "hello@policyengine.org" }]
@@ -628,6 +628,78 @@ def test_request_lifecycle_records_headers_and_context_metrics() -> None:
628
628
  assert observed.requests.calls[0][1] == 1
629
629
 
630
630
 
631
+ def test_internal_dispatch_segments_merge_into_parent_operation() -> None:
632
+ observed = runtime()
633
+ handle = observed.start_operation(
634
+ "modal_worker_dispatch",
635
+ flavor="modal_worker",
636
+ )
637
+ parent_operation = handle["operation"]
638
+ context = RequestObservabilityContext(
639
+ config=observed.config,
640
+ request_id="request-1",
641
+ method="POST",
642
+ route="/calculate",
643
+ path="/calculate",
644
+ endpoint="calculate",
645
+ query_keys=[],
646
+ content_length_bytes=None,
647
+ inbound={},
648
+ internal_dispatch=True,
649
+ )
650
+
651
+ try:
652
+ observed.begin_request(context)
653
+ with observed.segment(SegmentName.LOAD):
654
+ pass
655
+ observed.finish_request(200)
656
+ observed.teardown_request(None)
657
+
658
+ assert context.timings_ms is parent_operation.timings_ms
659
+ assert "load" in parent_operation.timings_ms
660
+ assert observed.current_operation() is parent_operation
661
+ finally:
662
+ observed.end_operation(handle)
663
+
664
+ assert observed.current_context() is None
665
+ assert observed.current_operation() is None
666
+
667
+
668
+ def test_non_internal_request_timings_do_not_leak_to_parent_operation() -> (
669
+ None
670
+ ):
671
+ observed = runtime()
672
+ handle = observed.start_operation("job", flavor="worker")
673
+ parent_operation = handle["operation"]
674
+ context = RequestObservabilityContext(
675
+ config=observed.config,
676
+ request_id="request-1",
677
+ method="POST",
678
+ route="/calculate",
679
+ path="/calculate",
680
+ endpoint="calculate",
681
+ query_keys=[],
682
+ content_length_bytes=None,
683
+ inbound={},
684
+ )
685
+
686
+ try:
687
+ observed.begin_request(context)
688
+ with observed.segment(SegmentName.LOAD):
689
+ pass
690
+ observed.finish_request(200)
691
+ observed.teardown_request(None)
692
+
693
+ assert context.timings_ms is not parent_operation.timings_ms
694
+ assert "load" not in parent_operation.timings_ms
695
+ assert observed.current_operation() is parent_operation
696
+ finally:
697
+ observed.end_operation(handle)
698
+
699
+ assert observed.current_context() is None
700
+ assert observed.current_operation() is None
701
+
702
+
631
703
  def test_request_methods_noop_without_current_context() -> None:
632
704
  observed = runtime()
633
705
 
@@ -658,7 +658,7 @@ wheels = [
658
658
 
659
659
  [[package]]
660
660
  name = "policyengine-observability"
661
- version = "0.2.0"
661
+ version = "0.3.0"
662
662
  source = { editable = "." }
663
663
 
664
664
  [package.optional-dependencies]
@@ -1,9 +0,0 @@
1
- ## [0.2.0] - 2026-06-22
2
-
3
- ### Added
4
-
5
- - Add pull request and push CI/CD workflows with changelog, lint, coverage, versioning, tagging, and PyPI publishing gates.
6
-
7
-
8
- # Changelog
9
-